Thanks Bubba. What did you make your g_TerrainApp an instance of?
Just curious.
Doesn't that also mean that you didn't declare your functions public?
Why not?
Sorry about all of the questions, i'm just curious and eager to learn.
Printable View
Thanks Bubba. What did you make your g_TerrainApp an instance of?
Just curious.
Doesn't that also mean that you didn't declare your functions public?
Why not?
Sorry about all of the questions, i'm just curious and eager to learn.
Nothing, the g_ prefix indicates that it's a global variable.
But i thought that you only use the . operator to access member variables.
Code:g_TerrainApp.Setup();
No, it's used to access all members. In this case, it's used to access the Setup member function of g_TerrainApp. That doesn't mean that g_TerrainApp has to be a member of anything. In fact, if it were, you'd need a . (or a ->) in front of it.
But i didn't say that g_TerrainApp wasn't global.
I asked if it was an instance of something, and what it was an instance of?
D'oh! I completely confused "instance" and "member" there. Sorry.
I can guess even this, though. g_TerrainApp is, guessing by the name, an instance of CTerrainApp, which derives from the CApp (or whatever his main application class is called) and introduces the functionality specific to this application. (Another guess: it's a terrain loader demo.)
Oh. So the profs like you and Bubba and other members of CBoard have Application classes also for your main windows.
So how would i approach it if i don't want to declare it static?
I have reformatted the code so here it is:
CDevice.h
CInitD3D.cppCode:
#include <d3dx9.h>
class CDevice /*Create a new class called CDevice*/
{
public:
CDevice(); /*Class constructor*/
virtual ~CDevice(); /*Class destructor*/
protected:
bool CInitD3D( /*Function to initialize Direct3D*/
HINSTANCE hInstance, /*Application Instance*/
int width, int height, /*Window width and height in pixels*/
bool windowed, /*Windowed?*/
D3DDEVTYPE DeviceType, /*HAL or REF?*/
IDirect3DDevice9** Device); /*The Device*/
int EnterMsgLoop(
bool (*ptr_display)(float TimeDelta));
LRESULT CALLBACK WndProc( /*Windows Procedure*/
HWND hwnd, /*Application handle*/
UINT Msg, /*Message handle*/
WPARAM wParam,
LPARAM lParam);
bool CSetup(); /*Setup function*/
void CCleanUp(); /*Cleanup function*/
};
ThanksCode:#include "CDevice.h"
/*Defining the function the Initializes D3D*/
bool CDevice::CInitD3D( /*The function that initializes D3D*/
HINSTANCE hInstance, /*Application instance*/
int width, int height, /*Window width and height in pixels*/
bool windowed, /*Windowed?*/
D3DDEVTYPE DeviceType, /*HAL or REF?*/
IDirect3DDevice9** Device) /*The Device*/
{
WNDCLASS wc; /*Make wc an instance of WNDCLASS*/
/*Class style*/
wc.style = CS_HREDRAW | CS_VREDRAW;
/*Pointer to Windows Procedure*/
wc.lpfnWndProc = (WNDPROC)WndProc;
return 1;
}
Basically, you have to pass a global or static member function as the WNDPROC. There is no way around that.
Therefore, you need to make this WndProc a stub that somehow looks up the pointer to the controlling object and calls the real member WndProc on that.
As I said, there are various approaches to this. The easiest, and for simple cases probably the best, is to just pass the object pointer as the user data to CreateWindow, and in the stub intercept WM_NCCREATE, get the user data and store it in the per-window space - either in GWLP_USERDATA or in memory provided by cbWndExtra of the WNDCLASS struct.
The downside of this approach is that it only works for windows under your control, not for, e.g., any of the common controls. The reason is that you can't rely on the per-window space for these.
Libraries that have to work for anything therefore take different approaches. MFC keeps per-thread maps that map HWNDs to CWnds. It uses a window creation hook to make sure the mapping is created at the earliest possible time. ATL, I believe, uses thunks - it dynamically generates code for the WNDPROCs that has the CWindow pointer directly embedded. I'm not sure how it works exactly and how it handles multiple windows with the same class, though.
On a side note:
Never cast function pointers. Either using it as-is works or not. If it doesn't, then casting will merely invoke undefined behaviour. There are very, very few exceptions to this.Quote:
wc.lpfnWndProc = (WNDPROC)WndProc;
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.
And here is the CD3DApp.h sample: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);
}
}
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.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);
...
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.
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.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;
};
The script code is a nightmare and violates everything you know about writing good headers, but it is powerful once you get it working.
A note about the pointer approach: to make your code compatible with 64-bit systems, you should use [GS]etWindowLongPtr and the offset constant GWLP_USERDATA. You should also cast the pointer passed to SWLP to LONG_PTR instead of long. Everything else will truncate a 64-bit pointer to 32 bits and very likely lead to a crash.