🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: borland.public.cppbuilder.nativeapi
Article #4703

Re: Passing Class Functions To CreateThread, etc...

#4703
From: Remy Lebeau (TeamB)
Date: Mon, 05 Dec 2005 19:33
187 lines
7610 bytes
"Alistair" <NoSpamPlease@Somewhere.Com> wrote in message
news:4393f9da$1@newsgroups.borland.com...

> If I have a callback parameter of a function that does NOT
> take a parameter, i.e. for passing 'this' to it.
>
> How would you get a class member function into the call back,
> that is defined as a non-member call back like CreateThread?

Borland handles this topic in a very elegant manner for the VCL.  The VCL
has a MakeObjectInstance() function that dynamically generates proxy
functions for class methods to act as window procedures.  A window procedure
has the same problem that you describe - no access to the class 'this'
pointer (well, technically, Get/SetWindowLong(GWL_USERDATA) can be used for
that, but we'll ignore that in this discussion).

Basically, what MakeObjectInstance() does is allocate a block of virtual
executable memory via VirtualAlloc() and then stores the class 'this'
pointer and method pointer into that memory block, along with a few assembly
instructions that call a helper proxy function.  The proxy function extracts
the pointers from the memory block and then manipulates the call stack and
CPU registers in order to call the class method correctly on the proper
class instance, as if it had been called directly.  When the class is
finished being used, the memory block can be freed.

The assembly instructions are located at the beginning of the memory block
so that the memory pointer can be passed to any API as a callback and the
API can call into it like any other function.  When the memory is executed,
the proxy function is called automatically, which then calls the class
object method.  A callback function is really nothing more than a jump to a
memory address anyway.  The method can even return values back through the
proxy to the original caller if desired.

As for the assembly instructions themselves, which instructions to use
exactly are directly dependant on which parameters need to be passed to the
method as well as the calling convention of the method.  Different
parameters for different conventions are passed via the stack or CPU
registers differently.  As an example, MakeObjectInstance() takes a VCL
WndProc() method:

    void __fastcall WndProc(TMessage &Message);

And produces a proxy function that behavles like it has the signature of a
Win32 WNDPROC callback:

    LRESULT __stdcall WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM
lParam);

The code that uses the callback only knows about the WindowProc().  When
called, the proxy function pops the uMsg, wParam, and lParam values from the
call stack and rearranges them into a TMessage, then places the class 'this'
pointer into the EAX CPU register and a pointer to the TMessage in the EDX
CPU register (the __fastcall convention uses CPU registered for parameters
when possible, whereas the __stdcall convention uses the stack only), and
then finally jumps to the memory address of the class method.  The method
has access to the class 'this' pointer because of the EAX CPU register, and
can do everything it would be able to do had you called the method normally.
When the method exits, the TMessage::Result member is passed to the EAX
register as the return value for the WindowProc().  That value is then
returned to the original caller.

With this approach, you can allocate multiple proxy functions, each one
specific to a particular object, and they allow you to use object methods as
callback functions, where the caller has no concept of the object being used
behind the scenes.

Now, with the dirty details said, you can go to http://www.deja.com and do a
search for past discussions on this topic.  Sample code implementations have
been posted before, and you can search online for details about how the
various calling conventions manage the call stack and parameter values.
Below is a (simplified) C++ translation of MakeObjectInstance() from the VCL
to get you started:

    typedef void (__fastcall *TWndMethod)(TMessage &Message);

    #pragma pack(push, 1)
    struct TObjectInstance
    {
        BYTE Code;
        UINT Offset;
        TWndMethod Method;
    };

    struct TInstanceBlock
    {
        BYTE Code[2];
        void *WndProcPtr;
        TObjectInstance Instance;
    };
    #pragma pack(pop)

    /*
    Standard window procedure
    In    ECX = Address of method pointer
    Out   EAX = Result
    */

    LRESULT __stdcall StdWndProc(HWND Window, UINT Message, WPARAM WParam,
LPARAM LParam)
    {
        asm
        {
            XOR     EAX, EAX
            PUSH    EAX
            PUSH    LParam
            PUSH    WParam
            PUSH    Message
            MOV     EDX, ESP
            MOV     EAX, [ECX+4]
            CALL    [ECX]
            ADD     ESP, 12
            POP     EAX
        }
    }


    UINT __fastcall CalcJmpOffset(void *Src, void *Dest)
    {
        return ((UINT)Dest) - (((UINT)Src) + 5);
    }

    void* __fastcal MakeObjectInstance(TWndMethod Method)
    {
        TInstanceBlock *Block = (TInstanceBlock*) ::VirtualAlloc(NULL,
sizeof(TInstanceBlock), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        if( Block )
        {
            Block->Code[0] = 0x59; // POP ECX
            Block->Code[1] = 0xE9; // JMP StdWndProc
            Block->WndProcPtr = (void*) CalcJmpOffset(&Block->WndProcPtr,
&StdWndProc);

            Block.Instance.Code = 0xE8;  // CALL NEAR PTR Offset
            Block.Instance.Offset = CalcJmpOffset(Instance, Block->Code);

            Block.Instance.Method = Method;
            return &Block->Instance;
        }

        return NULL;
    }

    void __fastcall FreeObjectInstance(void *ObjectInstance)
    {
        if( ObjectInstance != NULL )
            ::VirtualFree((void*)(((UINT)ObjectInstance)-6),
sizeof(TInstanceBlock), 0);
    }



To use it:

    class TMyObject
    {
    public:
        void *Callback;

        TMyObject()
        {
            Callback = MakeObjectInstance(DoIt);
        }

        ~TMyObject()
        {
            if( Callback )
                FreeObjectInstance(Callback);
        }

        void __fastcall DoIt(TMessage &Message)
        {
            // use Message as needed...
            // set Message.Result accordingly...
        }
    };


    TMyObject *obj = new TMyObject;
    //...
    if( obj->Callback )
        SomeCodeThatNeedsACallback(obj->Callback);
    //...
    delete obj;


Gambit
Technical Details
Message-ID: <4394964c$1@newsgroups.borland.com>
Path: arc1-ca.pugleaf.net!g2news1.google.com!news4.google.com!border1.nntp.dca.giganews.com!nntp.giganews.com!newsfeed.hal-mli.net!feeder1.hal-mli.net!news-out.superfeed.net!propagator2-sterling.newsfeeds.com!in.nntp.be!caladan.landsraad.net!newsgroups.borland.com!not-for-mail
References: <4392dfb4$1@newsgroups.borland.com> <m2zmngr8wf.fsf@madbox2.Arbeitsgruppe> <4393f9da$1@newsgroups.borland.com>

Show Full Headers
Complete Article Headers
X-Google-Thread: 10cc67,9984129712b6d0f1
X-Google-Attributes: gid10cc67,public
X-Google-Language: ENGLISH,ASCII-7-bit
From: "Remy Lebeau \(TeamB\)" <no.spam@no.spam.com>
Newsgroups: borland.public.cppbuilder.nativeapi
References: <4392dfb4$1@newsgroups.borland.com> <m2zmngr8wf.fsf@madbox2.Arbeitsgruppe> <4393f9da$1@newsgroups.borland.com>
Subject: Re: Passing Class Functions To CreateThread, etc...
Date: Mon, 5 Dec 2005 11:33:56 -0800
Lines: 189
X-Priority: 3
X-MSMail-Priority: Normal
X-Newsreader: Microsoft Outlook Express 6.00.2800.1409
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2800.1409
NNTP-Posting-Host: 68.69.17.41
Message-ID: <4394964c$1@newsgroups.borland.com>
X-Trace: newsgroups.borland.com 1133811276 68.69.17.41 (5 Dec 2005 11:34:36 -0700)
Path: arc1-ca.pugleaf.net!g2news1.google.com!news4.google.com!border1.nntp.dca.giganews.com!nntp.giganews.com!newsfeed.hal-mli.net!feeder1.hal-mli.net!news-out.superfeed.net!propagator2-sterling.newsfeeds.com!in.nntp.be!caladan.landsraad.net!newsgroups.borland.com!not-for-mail