MFC - style entrypoints

Started by
7 comments, last by xeos 23 years, 8 months ago
This question might be better placed in a programming-only web forum, but I know some people here are great ... :-) I''ve been playing arround with MFC, and I''m currently designing something similar for game development, with a little less overhead, and most of it''s going great. However, I was wondering about the CWinApp class, and, more specifically, it''s entry point. Win32 programming is not my favourite, but I was wondering if it is relatively easy to implement a similar system. For those who many not be familiar with this, what I''m trying to do is have an application class which only requires you to create a global instance of a derived class, and not have to worry about all the Win32 stuff (the WinMain entry point, the window procedure list etc.). The user can then call member functions from their derived functions. Is this easy? Are there websites where I can find this? Should I just "borrow" the MFC source? Any suggestions will be greatly appreciated. Jean, XEOS Digital Development
XEOS Digital Development - Supporting the independant and OpenSource game developers!
Advertisement
You may want to try and ''borrow'' the MFC code. Try stepping into your program in debug mode and you''ll see exactly how and where the app starts its life. Mimmicking the MFC message map may prove to be a little more involved, though.

Have you looked at WFC? (Windows Foundation Classes)

You might like it better.. It''s a little more light-weight.

You can find it here:
http://ourworld.compuserve.com/homepages/Sam_Blackburn/wfc.htm

HTH,
// CHRIS
// CHRIS [win32mfc]
Hmmm... I''ve been looking at the MCF source which comes with Visual C++ 6, and I''ve found a few things.

The first function the debugger steps into is _tWinMain. It then calls AxfWinMain and some other stuff...

What I''d like to be able to do is emulate this. Should I simply derive the required classes from the MFC classes, or should I forget the whole idea? Or is there another option?

All suggestions will be appreciated!

Jean,
XEOS Digital Development
XEOS Digital Development - Supporting the independant and OpenSource game developers!
Have you tried looking at the Active Template Library? CWindowImpl will solve all your problems.
Perhaps I need to simplify what I''m trying to do :-)

I want to have the majority of the Win32 krap (WinMain, WindProc etc.) swept up in my DLL as an application class, with the events handled by overridden member functions. I then want the user to be able to create an instance of this class, override the On[event] member functions, and have the program automatically jump to the WinMain function.

Now that you see the problem, is this a good solution?
* In class constructor, call my class InitInstance equivalent function
* From the InitInstance, call my WinMain function (C-style function)
* Have my WindProc, Exception Handler etc. all as C functions, which call my overridden functions.
* Have my program go about it''s business.

Will this mean I won''t need a WinMain function in my application? Have I forgotten something?

Thanks to everybody who''s read this and especially to those who''ve helped. Unfortunately the WFC didn''t have what I was looking for, and ATL has a little too much overhead for my likeing!

If you can help, please don''t hesitate to reply!

Jean,
XEOS Digital Development
XEOS Digital Development - Supporting the independant and OpenSource game developers!
I tried to make a library like this, and I had a fair amount of trouble getting it to work.


1. Entry-point

This is the very first function that is called in your program or DLL, and the OS calls it directly. Usually, in this function the standard libs are initialized (probably because they use some global variables). Then the entry-point function gets the startup parameters and passes them to WinMain.

WinMain is _not_ the entry-point. It is, however, where your program/library actually starts to execute. It is usually best not to override the entry-point for a project like this. It would be far easier (and no less practical) to override WinMain with your own version.

Let me describe how MFC handles the application object first.


2. Application object

The application object is a class, so it has a constructor and a destructor. The application object is a global object, which ensures that the whole program can access it for the life of the program.

Programmers typically define two other functions, one for initialization and one for uninitialization. That is why MFC has InitInstance and ExitInstance in the application class (which is called CWinApp). Also, to keep you out of their WinMain (which there is usually no point in looking at it, and it is confusing at first), they add a third function to the application called Run() which handles the main loop.


Let''s write a little program that makes use of all of this:


class application
{
public:
application();
virtual ~application();

void initialize();
void uninitialize();
void run();
};

application::application()
{
}

application::~application()
{
}

void application::initialize()
{
}

void application::run()
{
}

void application::uninitialize()
{
}

application myapplication; // the global object

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCommandLine, int nCommandShow)
{
// myapplication''s constructor has already been called

// initialize, run, and uninitialize the program
myapplication.initialize();
myapplication.run();
myapplication.uninitialize();

return 0;

// myapplication''s destructor will be called later
}



That program should run fine, and you can step through the functions to make sure they are called correctly.

However, the program does nothing useful. In order to make the class somewhat extensible, we can make the initialize(), run(), and terminate() functions into virtual functions, and then the user only needs to derive from the application class and override those functions. Now, when the WinMain is called, it will execute his code via the virtual functions.

You can keep extending this as much as you want, maybe adding data members of the application class to hold the WinMain parameters (hInstance, etc.).


3. Windows

This part is tricky. First, you have to use a class to wrap a HWND, which is hard enough. Writing the WndProc and making sure the message map works is no walk in the park, but it''s nice once it''s done.

It''s the WndProc that gives the most trouble. How is it done? I have a solution that I think is much simpler than MFC and is very flexible. Plus, it''s not a hack - it uses documented functions in the Win32 API.

First, Windows calls the WndProc for us, so we can''t have a "this" pointer and we can''t tell which C++ object the message is for. There''s an easy solution to this, however. Windows allows us to store 4 bytes in the HWND, and 4 bytes is exactly the size of a pointer in Windows! So we store a pointer to the object in the HWND when we create the object, and then simply extract the pointer from the HWND when we need to call that object''s message handler.

Here''s how it works:


// ...in window::create()...
handle = CreateWindow(..., this); // "this" is the last parameter

LRESULT CALLBACK WndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
Window* pWindow = NULL;

if( nMsg == WM_CREATE )
{
// Extract the window pointer parameter.
CREATESTRUCT* pCS = (CREATESTRUCT*) lParam;
pWindow = (Window*) pCS->lpCreateParams;

if( pWindow )
{
// Save it in the window handle.
SetWindowLong(hWnd,
GWL_USERDATA,
(LONG) pWindow);

// Store the window handle in the object.
pWindow->m_hWnd = hWnd;
}
}
else
{
// Extract the object from the window handle.
pWindow = (Window*) GetWindowLong(hWnd,
GWL_USERDATA);
}

if( pWindow )
{
// Let the object handle it.
return pWindow->OnMessage(nMsg, wParam, lParam);
}
else
{
// Just do the default if the window is not created yet.
return DefWindowProc(hWnd,
nMsg,
wParam,
lParam);
}
}



That bears some explanation. First thing we do is check for the WM_CREATE message. This is the special part, where we save a pointer to the object in the HWND. We do that by using SetWindowLong.

In the second part of the WndProc function, we retrieve the pointer we had stored earlier. If the pointer is valid, WM_CREATE has already been sent and all went well with SetWindowLong. In that case, we call the window object''s on_message() function and pass it the parameters, and we''re done. If the pointer is not valid, there might be another message that is sent before WM_CREATE, and we just ignore that by letting DefWindowProc handle it.

WM_CREATE is both sent and handled before the Win32 API function CreateWindow() returns, so we are guaranteed that a valid pointer will be stored in the HWND before our own window::create() function returns to the user of the library.


So now let''s build a little window class that does all of those things for us, and sends all the message''s to the on_message() function.


class window
{
public:
window();
virtual ~window();

virtual void create();
virtual void destroy();

virtual LRESULT on_message(UINT nMsg, WPARAM wParam, LPARAM lParam);

// static makes it act kind of like a global function
static LRESULT CALLBACK procedure(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam);

protected:
HWND hWnd;
};

window::window()
{
hWnd = NULL;
}

window::~window()
{
if( hWnd != NULL )
destroy();
}

void window::create()
{
// let''s see if I can remember it correctly
// (remember to register the window class first)
// (you remembered to add the hInstance data member to application, right?)
CreateWindow("my window class", "window", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, myapplication.hInstance, this);
}

void window::destroy()
{
DestroyWindow(hWnd);
}

LRESULT window::on_message(UINT nMsg, WPARAM wParam, LPARAM lParam)
{
// Just do the default for now...
DefWindowProc(hWnd, nMsg, wParam, lParam);
}

// this takes the place of WndProc
LRESULT CALLBACK window::procedure(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
Window* pWindow = NULL;

if( nMsg == WM_CREATE )
{
// Extract the window pointer parameter.
CREATESTRUCT* pCS = (CREATESTRUCT*) lParam;
pWindow = (Window*) pCS->lpCreateParams;

if( pWindow )
{
// Save it in the window handle.
SetWindowLong(hWnd,
GWL_USERDATA,
(LONG) pWindow);

// Store the window handle in the object.
pWindow->m_hWnd = hWnd;
}
}
else
{
// Extract the object from the window handle.
pWindow = (Window*) GetWindowLong(hWnd,
GWL_USERDATA);
}

if( pWindow )
{
// Let the object handle it.
return pWindow->OnMessage(nMsg, wParam, lParam);
}
else
{
// Just do the default if the window is not created yet.
return DefWindowProc(hWnd,
nMsg,
wParam,
lParam);
}
}



That''s a really scaled-down version of what you could really do with that class, but it works!


I can post how to start on the virtual functions/message handlers later if you want...

Happy Coding!



- null_pointer
Sabre Multimedia
Hey, that''s a great idea null_pointer! I never thought of using the app-specific data in the HWND to store the this pointer . I recently wrote a glWindow class to encapsulate setting up a window that uses openGL, and the method I used was a STL map object, mapping HWNDs to glWindow* pointers. When the static glWindow::WindowProc function gets called, it looks up whatever glWindow object maps to the specific HWND and calls that object''s OnMessage function. I didn''t really feel like implementing message mapping so OnMessage is simply a non-static version of WindowProc .

One problem I found with this approach.. STL''s map class has so many sub classes and template parameters that it can result in some super long identifiers. VC++ has a limit of 255 characters for identifiers in the debug data, and any more than that causes warnings. When I compiled my program I was getting dozens of ''identifier truncated'' warnings. The only thing I could do about the problem was disabling that specific warning.

-RWarden (roberte@maui.net)
Thanks!


- null_pointer
Sabre Multimedia
If you want to learn how to write your own class library similiar to MFC, get Windows++ by Paul DiLascia. It''s kinda old (he makes comments bout how we''re in the time of 33mhz 486''s ) but it''s a very good read and you can learn about how to roll your own. You can also download the source code at his web site (http://www.dilascia.com/) I''ve already started modifying this for game development, and it helps a lot.

Also check out MFC Internals. Talks about whats going on inside MFC and how it works. It''s pretty good also.
----------------------
Modian

This topic is closed to new replies.

Advertisement