Thread: Classes

  1. #16
    Sanity is for the weak! beene's Avatar
    Join Date
    Jul 2006
    Posts
    321
    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.
    Last edited by beene; 04-23-2007 at 09:34 AM.

  2. #17
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Nothing, the g_ prefix indicates that it's a global variable.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  3. #18
    Sanity is for the weak! beene's Avatar
    Join Date
    Jul 2006
    Posts
    321
    But i thought that you only use the . operator to access member variables.

    Code:
    g_TerrainApp.Setup();

  4. #19
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    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.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  5. #20
    Sanity is for the weak! beene's Avatar
    Join Date
    Jul 2006
    Posts
    321
    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?

  6. #21
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    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.)
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  7. #22
    Sanity is for the weak! beene's Avatar
    Join Date
    Jul 2006
    Posts
    321
    Oh. So the profs like you and Bubba and other members of CBoard have Application classes also for your main windows.

  8. #23
    Sanity is for the weak! beene's Avatar
    Join Date
    Jul 2006
    Posts
    321
    Quote Originally Posted by CornedBee View Post
    WNDPROC is a pointer to a normal function, but you're trying to pass a member function for it. These two are fundamentally different.
    Working around this problem is a common and interesting problem. There are many approaches to it, and I'm sure searching the Windows board for the topic will yield something useful.
    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
    Code:
    #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*/
    };
    CInitD3D.cpp
    Code:
    #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;
    }
    Thanks

  9. #24
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    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:
    wc.lpfnWndProc = (WNDPROC)WndProc;
    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.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  10. #25
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    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&#37; 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.
    Last edited by VirtualAce; 04-23-2007 at 11:53 PM.

  11. #26
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    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.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Can you Initialize all classes once with New?
    By peacerosetx in forum C++ Programming
    Replies: 12
    Last Post: 07-02-2008, 10:47 AM
  2. im extreamly new help
    By rigo305 in forum C++ Programming
    Replies: 27
    Last Post: 04-23-2004, 11:22 PM
  3. Exporting VC++ classes for use with VB
    By Helix in forum Windows Programming
    Replies: 2
    Last Post: 12-29-2003, 05:38 PM
  4. Prime Number Generator... Help !?!!
    By Halo in forum C++ Programming
    Replies: 9
    Last Post: 10-20-2003, 07:26 PM
  5. include question
    By Wanted420 in forum C++ Programming
    Replies: 8
    Last Post: 10-17-2003, 03:49 AM