MFCGuy
asked on
static variables
Hi. I have a static variable question.
I have a class called Globals that contains a static
CDatabase object as follows:
Globals.h
---------
#ifndef Globals_H
#define Globals_H
class Globals
{
public:
Globals();
//Operations
public:
CDatabase* GetDatabase(){return &m_Database;}
private:
static CDatabase m_Database;
};
#endif //Globals_H
Globals.cpp
-----------
#include "stdafx.h"
#include "Globals.h"
CDatabase Globals::m_Database;
Globals::Globals()
{
}
I then have a .exe that contains a member variable of
type Globals called m_Globals. I call m_Globals.GetDatabase()->O pen(...) to open the CDatabase.
I also have a set of .DLLs, all with AFX_EXT_CLASS defined
classes.
After opening the database as seen above, I construct a class contained in a .DLL called Person. Person creates
another instance of the Globals class called globals
and constructs a CRecordset-derived class using
MyRecordset(globals.GetDat abase()) in an attempt to
load itself from the database.
At this point,
globals.GetDatabase() returns a different CDatabase object,
instead of the one I originally used for the
CDatabase::Open() call shown above.
I have verified that all code works perfectly if the code
is all in a single .EXE, so I am assuming that it has
something to do with crossing a static variable
across .EXE and .DLL boundaries.
What am I doing wrong?
Thank you,
MFCGuy
I have a class called Globals that contains a static
CDatabase object as follows:
Globals.h
---------
#ifndef Globals_H
#define Globals_H
class Globals
{
public:
Globals();
//Operations
public:
CDatabase* GetDatabase(){return &m_Database;}
private:
static CDatabase m_Database;
};
#endif //Globals_H
Globals.cpp
-----------
#include "stdafx.h"
#include "Globals.h"
CDatabase Globals::m_Database;
Globals::Globals()
{
}
I then have a .exe that contains a member variable of
type Globals called m_Globals. I call m_Globals.GetDatabase()->O
I also have a set of .DLLs, all with AFX_EXT_CLASS defined
classes.
After opening the database as seen above, I construct a class contained in a .DLL called Person. Person creates
another instance of the Globals class called globals
and constructs a CRecordset-derived class using
MyRecordset(globals.GetDat
load itself from the database.
At this point,
globals.GetDatabase() returns a different CDatabase object,
instead of the one I originally used for the
CDatabase::Open() call shown above.
I have verified that all code works perfectly if the code
is all in a single .EXE, so I am assuming that it has
something to do with crossing a static variable
across .EXE and .DLL boundaries.
What am I doing wrong?
Thank you,
MFCGuy
ASKER
I am not accessing the same instance, I want the same m_Database across all instances of the Globals class so that I can open a CDatabase object once and use it for a large number of CRecorset classes throughout the lifetime of my program.
Here's the order of the code I am performing:
1.I have a member variable in my .EXE App class called m_pGlobals
of type Globals*, which I access in MyApp::InitInstance to call
m_Globals->GetDatabase().O pen(...)
This all works fine.
2.I then (in InitInstance) instanciate an instance of class Person using:
Person person; //Person is an AFX_EXT_CLASS from Person.dll
3. I call person.Load() which creates a local Globals as such:
void Person::Load() //in Person.dll
{
Globals globals;
//here is where GetDatabase returns a different m_Database than
//the one returned in 1.
MyRecordset recSet(globals.GetDatabase ());
recSet.Load(Person->GetEma ilAddress( ));
}
I must reiterate that everything works fine when I tested it all
contained in a single .EXE, only when I introduce the calls to .DLL functions does a problem occur. My question is:
Can you have staic member variables across .EXE / .DLL boundaries? I assumed since they are in the same memory space, you could but apparently I am doing something wrong.
Here's the order of the code I am performing:
1.I have a member variable in my .EXE App class called m_pGlobals
of type Globals*, which I access in MyApp::InitInstance to call
m_Globals->GetDatabase().O
This all works fine.
2.I then (in InitInstance) instanciate an instance of class Person using:
Person person; //Person is an AFX_EXT_CLASS from Person.dll
3. I call person.Load() which creates a local Globals as such:
void Person::Load() //in Person.dll
{
Globals globals;
//here is where GetDatabase returns a different m_Database than
//the one returned in 1.
MyRecordset recSet(globals.GetDatabase
recSet.Load(Person->GetEma
}
I must reiterate that everything works fine when I tested it all
contained in a single .EXE, only when I introduce the calls to .DLL functions does a problem occur. My question is:
Can you have staic member variables across .EXE / .DLL boundaries? I assumed since they are in the same memory space, you could but apparently I am doing something wrong.
ASKER
In 1., I meant to type:
m_pGlobals->GetDatabase(). Open(...)
m_pGlobals->GetDatabase().
ASKER
see comment
Is the Person object static by any chance?
Your Globals class has not been marked as AFX_EXT_CLASS, so it won't be exported correctly in your DLL's.
Alternatively, you can try adding AFX_DATA to your static declaration - make sure it is appropriately defined before-hand. Look for it in the article "Export and Import Using AFX_EXT_CLASS" in the on-line help
Alternatively, you can try adding AFX_DATA to your static declaration - make sure it is appropriately defined before-hand. Look for it in the article "Export and Import Using AFX_EXT_CLASS" in the on-line help
ASKER
1. The Person object is not static. Why do you ask?
2. Currently, in working this problem, I do have Globals declared as AFX_EXT_CLASS and it is in its own .DLL now - Globals.dll
Unfortunately, the same problem is still occuring.
I know I am close because when I call Globals.dll for the second time (from Person.dll), the debugger jumps into the Globals c'tor, everything works fine, but when it returns to Person.dll, BAGlobals::m_Database is not the same one I just saw in the debugger 1 line before - it is reset to an invalid CDatabase object.
2. Currently, in working this problem, I do have Globals declared as AFX_EXT_CLASS and it is in its own .DLL now - Globals.dll
Unfortunately, the same problem is still occuring.
I know I am close because when I call Globals.dll for the second time (from Person.dll), the debugger jumps into the Globals c'tor, everything works fine, but when it returns to Person.dll, BAGlobals::m_Database is not the same one I just saw in the debugger 1 line before - it is reset to an invalid CDatabase object.
Do you want to say that Globals constructor called twice?
ASKER
An update on the problem:
1. I have a class called Globals, contained in Globals.dll, written as such:
Globals.h
---------
class AFX_EXT_CLASS Globals
{
public:
Globals();
//Operations
public:
CDatabase* GetDatabase(){return m_Database;}
private:
static CDatabase* m_Database;
};
Globals.cpp
-----------
#include "stdafx.h"
#include "Globals.h"
//create a shared memory space for the static variable
#pragma data_seg("Shared")
CDatabase* Globals::m_Database = NULL;
#pragma data_seg()
Globals::Globals()
{
if(m_Database == NULL)
m_Database = new CDatabase;
if(!m_Database->IsOpen())
m_Database->Open(...);
}
2. I then have another .DLL, Person.dll, that in order to compile and link with Globals.dll, I must declare BAGlobals::m_Database in Person.cpp like this:
Person.cpp
---------------
#include "Person.h"
CDatabase* BAGlobals::m_Database;
..... rest of Person.dll
When Person.dll constructs an instance of Globals, I can follow the debugger into the construction of the Globals class and everything looks great. When the Globals c'tor ends and control returns to Person.dll, the newly constructed Globals class is NULL. I believe this is because of the declaration in Person.cpp that I noted above.
My question now is:
If you have a static variable in one .DLL (Globals.dll), how do you compile and link to it without having to include Globals.cpp in your new project (Person.dll)?
Any help (or code) you can provide would be much appreciated.
MFCGuy
1. I have a class called Globals, contained in Globals.dll, written as such:
Globals.h
---------
class AFX_EXT_CLASS Globals
{
public:
Globals();
//Operations
public:
CDatabase* GetDatabase(){return m_Database;}
private:
static CDatabase* m_Database;
};
Globals.cpp
-----------
#include "stdafx.h"
#include "Globals.h"
//create a shared memory space for the static variable
#pragma data_seg("Shared")
CDatabase* Globals::m_Database = NULL;
#pragma data_seg()
Globals::Globals()
{
if(m_Database == NULL)
m_Database = new CDatabase;
if(!m_Database->IsOpen())
m_Database->Open(...);
}
2. I then have another .DLL, Person.dll, that in order to compile and link with Globals.dll, I must declare BAGlobals::m_Database in Person.cpp like this:
Person.cpp
---------------
#include "Person.h"
CDatabase* BAGlobals::m_Database;
..... rest of Person.dll
When Person.dll constructs an instance of Globals, I can follow the debugger into the construction of the Globals class and everything looks great. When the Globals c'tor ends and control returns to Person.dll, the newly constructed Globals class is NULL. I believe this is because of the declaration in Person.cpp that I noted above.
My question now is:
If you have a static variable in one .DLL (Globals.dll), how do you compile and link to it without having to include Globals.cpp in your new project (Person.dll)?
Any help (or code) you can provide would be much appreciated.
MFCGuy
ASKER
To Avaulin:
I don't understand your question.
Thanks for your response,
MFCGuy
I don't understand your question.
Thanks for your response,
MFCGuy
Can you send sources to me (av@protec.kiev.ua)?
ASKER
What sources do you want? I provided the code as seen above.
Globals is contained in an MFC Extension DLL called GlobalsDLL.
Person is contained in an MFC Extension DLL called PersonDLL.
I can not send you the MSVC projects (they are internal company property anyway) as I simply extracted a very small part to post this question.
Thank you for any assistance you can provide.
Globals is contained in an MFC Extension DLL called GlobalsDLL.
Person is contained in an MFC Extension DLL called PersonDLL.
I can not send you the MSVC projects (they are internal company property anyway) as I simply extracted a very small part to post this question.
Thank you for any assistance you can provide.
Try this in your header file for Globals:
#ifdef __EXPORTGLOBALS
#define DLLINTERFACE __declspec(dllexport)
#else
#define DLLINTERFACE __declspec(dllimport)
#endif
class DLLINTERFACE Globals
{
....
};
#undef DLLINTERFACE // Do this in case your mixing exports and imports so there's no conflict
in you globals.cpp (inside the DLL project):
#define __EXPORTGLOBALS
#include "globals.h"
This way you will be sure that you class is exported in your DLL and imported in your applications.
#ifdef __EXPORTGLOBALS
#define DLLINTERFACE __declspec(dllexport)
#else
#define DLLINTERFACE __declspec(dllimport)
#endif
class DLLINTERFACE Globals
{
....
};
#undef DLLINTERFACE // Do this in case your mixing exports and imports so there's no conflict
in you globals.cpp (inside the DLL project):
#define __EXPORTGLOBALS
#include "globals.h"
This way you will be sure that you class is exported in your DLL and imported in your applications.
ASKER
1. How is what you decsribed different from using AFX_EXT_CLASS as I indicated previously?
2. How does this address a static variable problem?
Thank you,
MFCGuy
2. How does this address a static variable problem?
Thank you,
MFCGuy
If I have got this right: you have an .EXE using two MFC Extension .DLLs, and one of these .DLLs also use the other .DLL. Thats tricky indeed. What I have come up with is this:
In Project/Setting for the :DLL using the other one, define a macro, lets say it's called _PERSDLL, and then in the header file Globals.h insert the following code before defining the class CGlobals:
#if defined(_PERSDLL)
#define MY_EXT_CLASS _declspec(dllimport)
#else
#define MY_EXT_CLASS AFX_EXT_CLASS
#endif
class MY_EXT_CLASS CGlobals
{
...
};
That should do it.
/Stefan
In Project/Setting for the :DLL using the other one, define a macro, lets say it's called _PERSDLL, and then in the header file Globals.h insert the following code before defining the class CGlobals:
#if defined(_PERSDLL)
#define MY_EXT_CLASS _declspec(dllimport)
#else
#define MY_EXT_CLASS AFX_EXT_CLASS
#endif
class MY_EXT_CLASS CGlobals
{
...
};
That should do it.
/Stefan
ASKER
Sorry, that's the same answer as Melange.
It does not work. The real problem I am having is with accessing the static variable CGlobals::m_Database as I indicated near the bottom of the list of answers.
Thank you for your answer. Do know the answer to my static variable question?
Regards,
MFCGuy
It does not work. The real problem I am having is with accessing the static variable CGlobals::m_Database as I indicated near the bottom of the list of answers.
Thank you for your answer. Do know the answer to my static variable question?
Regards,
MFCGuy
I must be missing some important piece of information here. My test app, named Statics, have no problem with the static member variable.
I made Statics as an ordinary MFC MDI app with database support (headers only). Then I made two sub projects to Statics, GlobDLL and PersDLL, as MFC Extension DLL:s. I changed the path where the DLL:s put their .DLL files to the Statics Debug directory. I defined the macro _PERSDLL in the PersDLL Project Settings. I also included the GlobDLL and PersDLL directories as additional include directories for the Statics project, and ..\GlobDLL for the PersDLL project.
The CPersonRecordset class was created using an Access 97 test database.
In GlobDLL I put the CGlobals class:
-------------------- Globals.h -------------------------- ---------- ----
#if defined(_PERSDLL)
class _declspec(dllimport) CGlobals
#else
class AFX_EXT_CLASS CGlobals
#endif
{
public:
CGlobals();
virtual ~CGlobals();
CDatabase* GetDatabase() { return m_pDatabase; } // This could be made static...
private:
static CDatabase* m_pDatabase;
static int m_cInstance;
};
----------------------- Globals.cpp -------------------------- -------
#include "StdAfx.h"
#include "Globals.h"
////////////////////////// ////////// ////////// ////////// ////////// ////
// Construction/Destruction
////////////////////////// ////////// ////////// ////////// ////////// ////
#pragma data_seg("Shared")
CDatabase* CGlobals::m_pDatabase = NULL;
int CGlobals::m_cInstance = 0;
#pragma data_seg()
CGlobals::CGlobals()
{
TRACE("CGlobals::CGlobals( )\n");
if (m_pDatabase == NULL)
m_pDatabase = new CDatabase;
if (!m_pDatabase->IsOpen())
m_pDatabase->Open(_T("Stat ics"));
TRACE("m_pDatabase = %p\n", m_pDatabase);
afxDump << m_pDatabase << "\n";
m_cInstance++;
}
CGlobals::~CGlobals()
{
TRACE("CGlobals::~CGlobals ()\n");
m_cInstance--;
if (m_cInstance == 0)
{
m_pDatabase->Close();
delete m_pDatabase;
m_pDatabase = NULL;
}
}
In PersDLL I put the CPerson and CPersonRecordset classes:
-------------------- Person.h -------------------------- ---------- ----
#include <Globals.h>
class AFX_EXT_CLASS CPerson
{
public:
void Load();
CPerson();
virtual ~CPerson();
protected:
};
-------------------- Person.cpp -------------------------- ---------- --
#include "stdafx.h"
#include "Person.h"
#include "PersonRecordset.h"
CPerson::CPerson()
{
TRACE("CPerson::CPerson()\ n");
}
CPerson::~CPerson()
{
TRACE("CPerson::~CPerson() \n");
}
void CPerson::Load()
{
TRACE("void CPerson::Load()\n");
CGlobals Globals;
TRACE("Before: Globals.GetDatabase() = %p\n", Globals.GetDatabase());
CPersonRecordset rs(Globals.GetDatabase());
if (!rs.IsOpen())
rs.Open();
TRACE("After: Globals.GetDatabase() = %p\n", Globals.GetDatabase());
afxDump << Globals.GetDatabase() << "\n";
if (!rs.IsEOF() && !rs.IsBOF())
{
TRACE("%s %s %s\n", rs.m_PERSID, rs.m_NAME, rs.m_EMAIL);
}
}
In Statics, finally:
-------------------- Statics.h -------------------------- ---------- ----
#include <Globals.h>
class CStaticsApp : public CWinApp
{
...
protected:
CGlobals* m_pGlobals;
};
-------------------- Statics.cpp -------------------------- ---------- ---
...
#include <Person.h>
...
CStaticsApp::CStaticsApp()
{
m_pGlobals = NULL;
}
...
BOOL CStaticsApp::InitInstance( )
{
...
pMainFrame->ShowWindow(m_n CmdShow);
pMainFrame->UpdateWindow() ;
m_pGlobals = new CGlobals;
TRACE("m_pGlobals->GetData base() = %p\n", m_pGlobals->GetDatabase()) ;
OnTestLoad();
return TRUE;
}
...
int CStaticsApp::ExitInstance( )
{
delete m_pGlobals;
m_pGlobals = NULL;
return CWinApp::ExitInstance();
}
void CStaticsApp::OnTestLoad()
{
TRACE("void CStaticsApp::OnTestLoad() Begin\n");
CPerson person;
person.Load();
TRACE("void CStaticsApp::OnTestLoad() End\n");
}
...
The pointer returned by CGlobals::GetDatabase() is always the same.
What is the info I am missing ? I suppose that you are not using multiple inheritance.
/Stefan
I made Statics as an ordinary MFC MDI app with database support (headers only). Then I made two sub projects to Statics, GlobDLL and PersDLL, as MFC Extension DLL:s. I changed the path where the DLL:s put their .DLL files to the Statics Debug directory. I defined the macro _PERSDLL in the PersDLL Project Settings. I also included the GlobDLL and PersDLL directories as additional include directories for the Statics project, and ..\GlobDLL for the PersDLL project.
The CPersonRecordset class was created using an Access 97 test database.
In GlobDLL I put the CGlobals class:
-------------------- Globals.h --------------------------
#if defined(_PERSDLL)
class _declspec(dllimport) CGlobals
#else
class AFX_EXT_CLASS CGlobals
#endif
{
public:
CGlobals();
virtual ~CGlobals();
CDatabase* GetDatabase() { return m_pDatabase; } // This could be made static...
private:
static CDatabase* m_pDatabase;
static int m_cInstance;
};
----------------------- Globals.cpp --------------------------
#include "StdAfx.h"
#include "Globals.h"
//////////////////////////
// Construction/Destruction
//////////////////////////
#pragma data_seg("Shared")
CDatabase* CGlobals::m_pDatabase = NULL;
int CGlobals::m_cInstance = 0;
#pragma data_seg()
CGlobals::CGlobals()
{
TRACE("CGlobals::CGlobals(
if (m_pDatabase == NULL)
m_pDatabase = new CDatabase;
if (!m_pDatabase->IsOpen())
m_pDatabase->Open(_T("Stat
TRACE("m_pDatabase = %p\n", m_pDatabase);
afxDump << m_pDatabase << "\n";
m_cInstance++;
}
CGlobals::~CGlobals()
{
TRACE("CGlobals::~CGlobals
m_cInstance--;
if (m_cInstance == 0)
{
m_pDatabase->Close();
delete m_pDatabase;
m_pDatabase = NULL;
}
}
In PersDLL I put the CPerson and CPersonRecordset classes:
-------------------- Person.h --------------------------
#include <Globals.h>
class AFX_EXT_CLASS CPerson
{
public:
void Load();
CPerson();
virtual ~CPerson();
protected:
};
-------------------- Person.cpp --------------------------
#include "stdafx.h"
#include "Person.h"
#include "PersonRecordset.h"
CPerson::CPerson()
{
TRACE("CPerson::CPerson()\
}
CPerson::~CPerson()
{
TRACE("CPerson::~CPerson()
}
void CPerson::Load()
{
TRACE("void CPerson::Load()\n");
CGlobals Globals;
TRACE("Before: Globals.GetDatabase() = %p\n", Globals.GetDatabase());
CPersonRecordset rs(Globals.GetDatabase());
if (!rs.IsOpen())
rs.Open();
TRACE("After: Globals.GetDatabase() = %p\n", Globals.GetDatabase());
afxDump << Globals.GetDatabase() << "\n";
if (!rs.IsEOF() && !rs.IsBOF())
{
TRACE("%s %s %s\n", rs.m_PERSID, rs.m_NAME, rs.m_EMAIL);
}
}
In Statics, finally:
-------------------- Statics.h --------------------------
#include <Globals.h>
class CStaticsApp : public CWinApp
{
...
protected:
CGlobals* m_pGlobals;
};
-------------------- Statics.cpp --------------------------
...
#include <Person.h>
...
CStaticsApp::CStaticsApp()
{
m_pGlobals = NULL;
}
...
BOOL CStaticsApp::InitInstance(
{
...
pMainFrame->ShowWindow(m_n
pMainFrame->UpdateWindow()
m_pGlobals = new CGlobals;
TRACE("m_pGlobals->GetData
OnTestLoad();
return TRUE;
}
...
int CStaticsApp::ExitInstance(
{
delete m_pGlobals;
m_pGlobals = NULL;
return CWinApp::ExitInstance();
}
void CStaticsApp::OnTestLoad()
{
TRACE("void CStaticsApp::OnTestLoad() Begin\n");
CPerson person;
person.Load();
TRACE("void CStaticsApp::OnTestLoad() End\n");
}
...
The pointer returned by CGlobals::GetDatabase() is always the same.
What is the info I am missing ? I suppose that you are not using multiple inheritance.
/Stefan
ASKER
Ok, I was totally wrong.
What you specified will work, I never removed
CDatabase* BAGlobals::m_Database;
from Person.cpp so it was still giving me a NULL pointer every time.
Please submit another response as an answer so I may give you the points.
Also, on this topic, if I have an application with a large number of Business Object classes, is it better to have, say 20 .DLLs, each with a logical subset of those classes, or is it better to make 20 .LIBs that are then linked into subsequent .EXEs? Or is better to have a single .DLL holding all of the Business Objects (this options would seem to make it difficult to break up enhancement work across a team of programmers)?
Thank you for your help,
MFCGuy
What you specified will work, I never removed
CDatabase* BAGlobals::m_Database;
from Person.cpp so it was still giving me a NULL pointer every time.
Please submit another response as an answer so I may give you the points.
Also, on this topic, if I have an application with a large number of Business Object classes, is it better to have, say 20 .DLLs, each with a logical subset of those classes, or is it better to make 20 .LIBs that are then linked into subsequent .EXEs? Or is better to have a single .DLL holding all of the Business Objects (this options would seem to make it difficult to break up enhancement work across a team of programmers)?
Thank you for your help,
MFCGuy
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Thanks!
Either , your access to objects has been implemented wrongly or your calling conventions from the exe to dll have some problem. Check that out.
If you want me to write a dummy example and mail it to you give me your e-mail address.