CornedBee hit the nail on the head. I've used several of these approaches. For the DirectShow WndProc I used the cbWndExtra to hold a pointer (an address) and then I cast it to CDXShowWnd which then allows me access to my class. It's complicated and hard to explain how it works.
I've also used the message map approach and I must give tons of credit to Shakti for helping me on this one. We spent literally hours and hours on Teamspeak pouring over MFC source code figuring out how the message system works. We based our entire scripting engine on the message approach and it works like a charm.
The inherent problem is that you are trying to use C++ when Windows itself is really C and not C++. Even though Windows calls everything classes at random in their docs, in reality they are really C structs which are not pure C++ classes. So the goal is to get to C++ as quick as possible in a way that Windows won't puke on. You can use thunks, message maps, pointers, etc.
This took a long time to get past in my code and I struggled with the very same issue you are struggling with.
Samples of my approaches to the problem
Pointer approach - via [Get/Set]WindowLong
Here is a sample of how I approached this in CDXShowWnd which is a class that offers simple DirectShow support.
Code:
//Helper wnd proc for video windows
//Just calls user supplied version
LRESULT CALLBACK g_VideoWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
if (msg==WM_NCCREATE)
{
LPCREATESTRUCT cs = (LPCREATESTRUCT) lParam;
SetWindowLong(hwnd, GWL_USERDATA, (long) cs->lpCreateParams);
}
CDXShowWnd *pDShow = (CDXShowWnd *) GetWindowLong(hwnd, GWL_USERDATA);
if (pDShow)
{
return pDShow->VideoWndProc(hwnd,msg,wParam,lParam);
}
else
{
return ::DefWindowProc(hwnd,msg,wParam,lParam);
}
}
And here is the CD3DApp.h sample:
Code:
#include "CKeyboard.h"
#include "CDXAudio.h"
#include "CDXShowWnd.h"
const float INFINITY = FLT_MAX;
const float EPSILON = 0.001f;
LRESULT CALLBACK g_WndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam);
LRESULT CALLBACK g_VideoWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam);
...
The WndProc functions are not part of a class and so they work with Windows. However notice what is going on in g_VideoWndProc. I'm saving a pointer on creation of the window and then retrieving the pointer via GetWindowLong and casting it to CDXShowWnd. This now allows me access to my class and if the DirectShow window is valid, I simply call CDXShowWnd::VideoWndProc(). This is the transfer from global g_VideoWndProc() to the class based VideoWndProc(). Now this approach allows me to receive messages from Windows in my class-based WNDPROC which allows me to do pretty much anything I need to. So if the user presses a key during the video playback or perhaps a mouse button I can respond to this in an organized fashion and without a lot of strange obfuscated code. GetWindowLong() is extremely helpful and this approach would not be possible without it.
Message based approach
This approach was chosen because of it's simplicity (after implementation that is) and flexibility. Since this class is designed to run a script while the game engine is running Shakti and I needed real time object-oriented control over events. I won't go into a huge explanation of the system here b/c it is quite confusing, but it works pretty much exactly like MFC does.
Code:
#pragma once
#include <vector>
#include <utility>
#include "CBaseObject.h"
/////////////////////////////////
//Script message map structures//
/////////////////////////////////
//Forward delcaration of CGameObject;
class CBaseObject;
//SCRIPT_MSG typedef
typedef void (CBaseObject::*SCRIPT_MSG)(void);
//SCRIPT_MSGMAP_ENTRY
struct SCRIPT_MSGMAP_ENTRY
{
UINT nCommandID;
UINT nFuncSig;
SCRIPT_MSG pfn;
//SCRIPT_MSGMAP_ENTRY(UINT nCommandID,UINT nFuncSig,SCRIPT_MSG pfn):
//nCommandID(nCommandID),nFuncSig(nFuncSig),pfn(pfn) { }
};
//SCRIPT_MSGMAP
struct SCRIPT_MSGMAP
{
const SCRIPT_MSGMAP *pBaseMap;
std::vector<SCRIPT_MSGMAP_ENTRY *> vEntries;
};
enum SFuncSigs
{
SFSig_Param0_n =0,
SFSig_Param1_n, //int fxn(DWORD)
SFSig_Param2_n, //int fxn(DWORD,DWORD)
SFSig_Param3_n //int fxn(DWORD,DWORD,DWORD)
};
union ScriptMsgMapFuncs
{
SCRIPT_MSG pfn;
UINT (CBaseObject::*pfnParam1_n)(void);
UINT (CBaseObject::*pfnParam2_n)(DWORD);
UINT (CBaseObject::*pfnParam3_n)(DWORD,DWORD);
};
#define DECLARE_SCRIPT_MSGMAP() \
protected: \
static std::vector<SCRIPT_MSGMAP_ENTRY *> m_vEntries; \
static CBaseObject *m_pParent; \
public: \
static const void SetupMessageMap(void); \
static const void AddHandler(UINT nCommandID,UINT nFuncSig,SCRIPT_MSG memberFxn); \
static const void SetParent(CBaseObject *pParent);
#define BEGIN_SCRIPT_MSGMAP(theClass,baseClass) \
std::vector<SCRIPT_MSGMAP_ENTRY *> theClass::m_vEntries; \
CBaseObject *theClass::m_pParent=baseClass; \
const void theClass::AddHandler(UINT nCommandID,UINT nFuncSig,SCRIPT_MSG memberFxn) \
{ \
SCRIPT_MSGMAP_ENTRY *pEntry=new SCRIPT_MSGMAP_ENTRY; \
pEntry->nCommandID=nCommandID; \
pEntry->nFuncSig=nFuncSig; \
pEntry->pfn=memberFxn; \
m_vEntries.push_back(pEntry); \
} \
const void theClass::SetupMessageMap(void) {
#define ON_SCRIPT_CMD1(id,memberFxn) \
AddHandler(id,SFSig_Param1_n,(SCRIPT_MSG)memberFxn);
#define ON_SCRIPT_CMD2(id,memberFxn) \
AddHandler(id,SFSig_Param2_n,(SCRIPT_MSG)memberFxn);
#define ON_SCRIPT_CMD3(id,memberFxn) \
AddHandler(id,SFSig_Param3_n,(SCRIPT_MSG)memberFxn);
#define END_SCRIPT_MSGMAP }
/////////////////////
//Script structures//
/////////////////////
//Function structure filled out by script class
struct SCRIPTFUNC
{
UINT nCommandID; //Script command ID
UINT nParam1;
UINT nParam2;
UINT nParam3;
CString strDebugID;
};
//One command from script
struct ScriptCommand
{
bool bExecuted;
SCRIPTFUNC *pFuncInfo;
ScriptCommand *pNext;
};
//One if block from script
struct ScriptIfBlock
{
bool bExecuted;
ScriptCommand *pCommands;
ScriptIfBlock *pNestedIfs;
ScriptIfBlock *pNext;
};
//One complete event from script
struct ScriptEvent
{
bool bExecuted;
ScriptIfBlock *pBlocks;
ScriptEvent *pNestedEvents;
ScriptEvent *pNext;
};
//One trigger from script
struct ScriptTrigger
{
bool bExecuted;
ScriptEvent *pEvents;
ScriptTrigger *pNestedTriggers;
ScriptTrigger *pNext;
};
//One quest from script
struct ScriptQuest
{
ScriptTrigger *pTriggers;
ScriptQuest *pNestedQuests;
ScriptQuest *pNext;
};
//One type of attack from script
//An attack is a list of commands to be executed
struct AttackScript
{
ScriptEvent *pAttack;
AttackScript *pNestedAttacks;
AttackScript *pNext;
};
This is our message map approach and it works quite well and is very fast. Since everything operates on messages in the engine, it is 100% ready for some multiplayer message code to be added. Just create a controlling class and some supporting classes and multiplayer is a simple addition to the system.
The script code is a nightmare and violates everything you know about writing good headers, but it is powerful once you get it working.