|
Hi!
Recently, I've figured out a method to build a linked list of items
in an .exe before WinMain gets called. The method relies on the fact
that a global instance of a class will always call it's contructor
before WinMain is called. Some example code of what I'm doing:
cLinkedList g_List; // my linkedlist of data. it's global and currently empty
class cListBuilder // this class will add a new node in the linkedlist
// when a variable of this class is declared
{
cListBuilder()
{
cData* poData = new cData;
g_List.AddNode("NodeName", poData);
}
}
cListBuilder ListBuilder; // because this is a global, it's constructor is
// called first, right before WinMain, filling the
// linked list with one node.
int WinMain()
{
// by the time we get here, m_gList has one element
printf("NumNodes: %d\n", g_List.GetNumNodes());
}
I just made up the code from memory, so it might not compile out of the box.
Anyway, this works inside an .exe, but it doesn't when I try this method inside
a DLL. The implementation differences being that:
1) g_List is now declared in it's own seperate .cpp file (gList.cpp)
2) WinMain is replaced by DllMain (duh). The function is now seperated into
it's own file, DllMain.cpp. It calls "extern cLinkedList g_List" to have
access to the list.
3) the decleration of cListBuilder is in it's own .h as well. (cListBuilder.h) It
gets g_List by calling "extern cLinkedList g_List", the same way DllMain.cpp does
So now, the project looks like this:
-- gList.cpp -------
cLinkedList g_List;
-- End gList.cpp ---
-- cListBuilder.h ------
extern cLinkedList g_List; // the g_List *should* be coming from gList.cpp
class cListBuilder // this class will add a new node in the linkedlist
// when a variable of this class is declared
{
cListBuilder()
{
cData* poData = new cData;
g_List.AddNode("NodeName", poData);
}
}
static cListBuilder ListBuilder; // now declared static to have it persist
-- End cListBuilder.h --
-- DllMain.cpp ---------
extern cLinkedList g_List;
int DllMain()
{
// g_List *should* have one node in it when it reaches here.
}
-- End DllMain.cpp -----
That's pretty much how my project looks like right now. It's very
similar to what I was doing before in the .exe. The problem is
that by the time I reach DllMain, the linkedlist is empty even
though cListBuilder::cListBuilder() was called. I placed a break
point inside the constructor, and it definately stops there. The
constructor does add the new node to the linkedlist without fail.
But, like I said, by the time I reach DllMain, it's like I never
touched g_List. It's like the actions I did inside cListBuilder::
cListBuilder() are completely forgotten as soon as I go out of
the scope of cListBuilder.h. And the g_List's destructor never
gets called, either, so it's not like it actually empties the list.
The problem is compounded when I add more files like cListBuilder
that try to add more nodes inside g_List. For example, if I added
a new file to the project called cListBuilder2.h that looked like:
-- cListBuilder2.h ------
extern cLinkedList g_List; // the g_List *should* be coming from gList.cpp
class cListBuilder2
{
cListBuilder2()
{
cData* poData = new cData;
g_List.AddNode("NodeName2", poData);
}
}
static cListBuilder2 ListBuilder2;
-- End cListBuilder2.h --
So, because I now have ListBuilder and ListBuilder2 declared as global static
variables, they both would try to add a node into g_List. But the weird thing
is that if ListBuilder's constructor gets called first and adds a node, by the
time ListBuilder2's constructor is called, the list is magically empty again.
Then ListBuilder2 would add it's own node and then by the time we reach DllMain,
the list is empty once more.
I can't figure out why this doesn't work in a DLL when it works perfectly in
an .exe. I don't know how Dlls handle global variables differently than
executables.
I'm not quite sure if I actually explained the problem really well. It's a pretty
weird situation, if you ask me. So, if anybody has any insight into this, it would
be really helpful. I can try to clear this up some more, if I can.
Thanks for taking the time to read this monster post.
Steve
wrlddomprod@hotmail.com
|
|
|
|
|
Hi,
It's very late here, so I have to be brief: probably you hit the problem with order of constructors called. Your list and list builders are global. There's no way to guarantee that list c'tor will be invoked first. Just put the breakpoints in list constructor to check it out.
Hope it helps and goodnight
Tomasz Sowinski -- http://www.shooltz.com.pl
|
|
|
|
|
I think its a problem of visibility. Now that you have several declarations of g_List, there's some subtlety of the linkage that isn't tying them to one object. Almost as if there's no defining object, though intuitively I would think there is (the one without the extern).
To test, you might look at the address of the g_List at the points when g_List.AddNode is called. If you get different addrs, I'd say its not a DLL specific problem, but rather falls under the category of storage class specifiers.
-----
#include "just_a_theory_disclaimer.h"
|
|
|
|
|
Hey!
I checked it out. It's the same address all around. So they are pointing
to the same instance of g_List.
Also, it looks like the g_List's contructor only gets called before
DllMain(), even though it AddNode() gets called twice (in cListBuilder
and cListBuilder2). I just find that odd.
What I don't understand is how this method seems to work within an .exe
but not inside a DLL.
Another question would be, is there another way to accomplish this?
The whole goal of this is to have a linkedlist of information that
can be got from a DLL, but the linkedlist can only be built inside
.h files. Almost like building a linkedlist at compile time.
The only place I've seen this done is inside Monolith's Lithtech 3d engine.
They use a similar method to build an array of actor classes that can
be exported from a DLL so that the game client knows about what kind of
objects/enemies it can create. For them, to add a new class to the
array they just create the .cpp and the. h file for the new actor class and
call various macros in the actor's .h file. These macros, from what I've
understood, automatically add the new actor class to this actor "database".
They don't need to modify any other file, or modify any "factory" functions
to able to create an instance of the new actor class. I find their method
to be incredibly elegant, but I just can't totally figure out how they did it.
To find out what I mean, you can download the source code to Shogo in the
"files" section of PlanetShogo.com. Look at any of the actor classes inside
of the "server/objects" workspace, and you'll see what I mean.
Shawn
|
|
|
|
|
The problem is the 'order of construction' problem for global variables.
If the global/static 'cListBuilder1' and 'cListBuilder2' objects are constructed BEFORE the global 'g_list' object, then the calls to 'g_list.AddNode' are most likely failing (because g_list does not yet exist, so you can't add to it). Note that this means you can call a member function of an object that has not yet been constructed!! This is perfectly valid C++ code, but not likely to be what you want. You are probably lucky that you are getting just 'failed function call' - often this behaviour will lead to crashes.
Why would the 'listBuilder' objects be created before 'g_list'? Well, since they are all global objects in different compilation units, the order of construction is undefined - the compiler/linker can schedule the calls to the required constructors in any order they like - as long as all global objects get constructed before the 'main' is called. What makes this kind of problem even worse is that the order of construciton can change simply by adding a new global variable into some file in the project - in this case, the compiler/linker can totaly change the order of global constructions, causing previously "working" code to now fail.
The solution is to use "Create On First Use" semantics for the g_list object. This means you don;t make it a global - you create it the first time it is used (the first call to "AddNode" from a global 'cListBuilder' object constructor, for example. To implement this, put the variable into a function, like this :
cLinkedList& g_List()
{
static cLinkedList theList_;
return theList_;
}
Now, your constructors for the cListBuilder become :
class cListBuilder {
cListBuilder()
{
cData* poData = new CData;
g_list().AddNode("NodeName", poData);
}
};
Since the ListBuilder construtor is now calling a function 'g_list()' rather than a variable 'g_list' you do not have to worry about the order of construction. The rules for static variables inside functions means that the internal 'theList_' object is created only once, and only when the function is called. The function cannot be called without creating the static variable, unlike memeber functions of global variables (which can be called from constructors of other objects before the 'owning' object's constructor!).
I think the reason this was working in an EXE and not the DLL is just blind luck - although, you should note that if all 3 global varaibles are in the same compilation unit (eg, the same cpp file) then the order of construction is normally fixed at the order of the variable declarations (but I'm not sure if the language guarantees this!)
|
|
|
|
|
Aaaah, now I get it. I tried your suggestion and now it works like
a charm (woohoo!) Thanks for the help!
Steve The Plant
|
|
|
|
|
Hello all,
I know that when you export a function from a dll that uses MFC dynamicaly, you have to use the AFX_MANAGE_STATE() macro.
If you export a class from a dll that links to MFC dynamically , do you have to use the AFX_MANAGE_STATE() macro in each member function of the class? If so, what about protected and private functions that won't be called by the client app???
Thanks!
|
|
|
|
|
Hi all,
I'm in the process of writing a Text Viewer application. Notepad does not open large files, and Wordpad opens them but reads them all into RAM and brings the OS to its knees with the swap files, etc.
What I want to do is to open a text file and just read in the portion of the file that should be showing (and some extra for caching, but not terribly urgent right now). Then as the user runs the scroll bar, I would redraw the text that I need to redraw.
A future addition to this program will be for other file types, so I wil have a class to work with Text drawing, and then add other classes as necessary to draw TIFF's or our own file format, etc.
Unfortunately, as I work with this, I keep running into problems with MFC.
I'm working with MS Visual C++ 6.0, SP 4, MFC 6.
Using CScrollView I can print the file, and print preview, but when I try to scroll the file on the screen, it chops off the top, or the bottom, or chops the bottom off lines of text, etc.
Any thoughts about a good way to go about this? Is ScrollView too much of a "helper" if you're not planning on reading the whole file into RAM? Would CView be better? Should I just scrap MFC and write it using the APIs myself?
Any thoughts would be appreciated.
Jeff
|
|
|
|
|
Oops, additional info.
I'm currently working on Windows 98, but any solution would need to work on Windows NT and Windows 2000 as well.
Thanks for any info
Jeff
|
|
|
|
|
Thanks for who that answer me on this question.
But I have still problem with it.
my code is so :
-------------------------------------------------------
CFile file;
CFileException ex;
if(!file.open(str_pathName, CFile::modeRead, &ex))
{
//error
}
else
{
//open
}
.
-------------------------------------------------------------------------
when I run it I gets this error:
: error C2039: 'open' : is not a member of 'CFile'
c:\program files\microsoft visual studio\vc98\mfc\include\afx.h(1202) : see declaration of 'CFile'
Error executing cl.exe
-----------------------------------------------------------------------------
------ WHY ???????????????????????
|
|
|
|
|
Is Open, no open, with Capital 'O'
Cheers!!!!
Carlos Antollini.
|
|
|
|
|
detail , please!
|
|
|
|
|
Incorrect Case:
file.open(str_pathName, CFile::modeRead, &ex)
Should be:
file.Open(str_pathName, CFile::modeRead, &ex)
|
|
|
|
|
thank you very much !!!!!!!!!!!!!!!!!!!!!!!
|
|
|
|
|
Hi *.*
I have written an MFC application in MS Visual C++6. Now I want to compile it with a different compiler but the one from MS. Is there a way to compile the whole project with borland's c++ compiler?
Are there any other good (or even better) compilers you can recommend?
thanks
|
|
|
|
|
I once used the Borland CPP Builder 4 VC++ Project Conversion Utility on a simple VC6 MDI project just to test. Worked well. My version of Builder has MFC files that corespond to VC5, but that wasn't a big issue for the simple test app.
|
|
|
|
|
I'm looking for some documentation on serial port communication in VC++. Does MFC support it?
- john
|
|
|
|
|
Never mind, I'm a moron. However it wouldn't let me delete my previous message, so I'm just saying I figured it out (and feel stupid)
|
|
|
|
|
Now I really wonder what I missed. Is there a nice class for this?
Eric Hansen
ehansen@pmsi.cc
|
|
|
|
|
Is there a nice class for this?
Look at P.J. Naughter's Serial Port class right here on CodeProject.
|
|
|
|
|
I couldn't find any classes in MFC for Serial Port Comms back when I was looking (about one year ago) so I started to write one...but that project has been on hold for a LONG while now. Unfortunately I'm a bit short on time right now so I'm just going to cut up some of my code send it along, hope it helps (I've never posted code to one of these but I have a feeling this isn't going to be that easy to read ).
Opening a port:
if (portOpen)
{
return portHandle;
}
else
{
// Try to open the comm port
char str[30];
sprintf( str, "\\\\.\\COM%d", portNum );
HANDLE hCom;
DWORD dwError;
hCom = CreateFile( str,
GENERIC_READ | GENERIC_WRITE,
0, // comm devices must be opened w/exclusive-access
NULL, // no security attributes
OPEN_EXISTING, // comm devices must use OPEN_EXISTING
0, // not overlapped I/O
NULL // hTemplate must be NULL for comm devices
);
if (hCom == INVALID_HANDLE_VALUE)
{
dwError = GetLastError();
TRACE0("Invalid Comm Handle in openPort()\n");
// handle error
return NULL;
}
else
{
portOpen = true;
portHandle = hCom;
return hCom;
}
}
Example of writing to and reading a response from a comm port (setting baud rate etc):
DCB dcb;
fSuccess = GetCommState(portHndl, &dcb);
if (!fSuccess)
{
// Handle the error.
}
dcb.BaudRate = 9600;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
fSuccess = SetCommState(portHndl, &dcb);
if (!fSuccess)
{
// Handle the error.
}
//unsigned char lpData[140] = {1,0,0,16,85,0,1,0,0,0,0,0,0,0,0,103 };
DWORD numActual = 0;
OVERLAPPED osWrite = {0};
BOOL succ;
DWORD error;
succ = WriteFile(portHndl,
sendBuffer, //Data to write to comm port
sendBytes, //number of bytes to write
&numActual, //pointer to number of bytes written
&osWrite // pointer to structure for overlapped I/O
);
if (!succ)
{
error = GetLastError();
TRACE("Bad Comm Write: %d,%d\n",portHndl,error);
}
else
TRACE("Good Comm Write: %d,%d\n",portHndl,error);
succ = ReadFile(portHndl, // handle of file to read
receiveBuffer, // pointer to buffer that receives data
receiveBytes, // number of bytes to read
&numActual, // pointer to number of bytes read
&osWrite // pointer to structure for data
);
if (!succ)
error = GetLastError();
Eric Hansen
ehansen@pmsi.cc
|
|
|
|
|
I have found how to replace the file type combo in the File "Open" dialog with a new one graphically. I want to replace the standard combo box with CComboBoxEx so I can put icons next to the types. But once I do that - how do I communicate to the common dialog when the user selects a new file type? The original file type combo box of the common dialog is still available but invisible. Both combos have the same text. I have tried to programatically set the selection of the original combo to that of the extended combo thinking that that action would cause the appropriate updates but it does not work. Any ideas?
|
|
|
|
|
> I have tried to programatically set the selection of the original combo
> to that of the extended combo thinking that that action would cause
> the appropriate updates but it does not work.
I don't think hidden windows send notifications, so you could try changing the selection and the explicitly sending the CBN_SELCHANGE notification to the dialog using the original combo-box's window handle.
UINT nID = ::GetDlgCtrlID ( hOldCombo );
SendMessage ( WM_COMMAND, MAKELPARAM ( nID, CBN_SELCHANGE ), hOldCombo );
Not sure if this will work, since I've never tried it.
-Ben
"Its funny when you stop doing things not because they’re wrong, but because you might get caught." - Unknown
|
|
|
|
|
Looks like the reply here is for another topic. Anyone with any ideas for the original question?
|
|
|
|
|
It looks like the response here is for another thread. Getting back to this now and no luck yet. Any responses still greatly appreciated
|
|
|
|
|