How to convert a member function pointer to a normal function pointer

Daniel Keep daniel.keep.lists at gmail.com
Thu May 3 08:32:22 PDT 2007



smithfox wrote:
> How to convert a member function pointer to a normal function pointer?
> Just like Object Pascal Language's Classes.MakeObjectInstance(xxx);
> 
> I think it is a classic problem when we try to write object-oriented codes based on an unalterable C framework(such as window gui sdk);
> 
> Thanks

As Jarrett mentioned, most C apis should allow you to pass a void* or
something to your callback, which you can use to construct a delegate.
Since you didn't actually mention any specific functions, I can't really
tell if that's the case here.

Here's a snippet I used for my expat SAX parser code, which had to set
up extern(C) callbacks that called into object delegates.  If you have a
member function called "expat_StartElement", you would construct a
regular old C callback function pointer that calls that member using
"&xcb!(expat_StartElement)".

> /**
>  * This template will be used to create handler functions.
>  *
>  * The reason we need this is that expat expects a regular extern(C)
>  * function that is passed a single void* argument.  Sadly, all we
>  * have are delegates that don't quite work like that.
>  *
>  * This template will basically wrap a particular argument list, and
>  * use the user-data void* as the object reference.
>  */
> private
> extern(C)
> ReturnType!(T) xcb(alias T)(
>         void* data,
>         ParameterTypeTuple!(T) args)
> {
>     static const name = eval!(membername((&T).stringof));
>
>     auto reader = cast(ExpatReader)data;
>     static if( is( ReturnType!(T) == void ) )
>         mixin("reader."~name~"(args);");
>     else
>         return mixin("reader."~name~"(args)");
> }
>
> private
> template eval(char[] strT)
> {
>     const eval = strT;
> }
>
> private
> char[] membername(char[] str)
> {
>     auto sppos = ctfind(str, ' ');
>     auto prpos = ctfind(str, '(');
>     if( prpos == -1 )
>         prpos = str.length;
>     return str[sppos+1..prpos];
> }
>
> private
> int ctfind(char[] str, dchar ch)
> {
>     foreach( i, c ; str )
>         if( c == ch )
>             return i;
>     return -1;
> }

Obviously, you need to know what methods you want to hook up at compile
time :)

If, for some reason, the callback doesn't let you pass a pointer to it,
or you need to rig this up at compile time, then things get a little
tricker.  If you don't mind writing *severely* non-portable code, you
can always generate a machine code shim at runtime.

If you grab a copy of the NASM manual, that will tell you the byte
encoding for most of the x86 instructions, and you can fill in any holes
with a copy of the Pentium 4 manual.  Then, all you need to do is
generate machine code to put the delegate's ptr into EAX, then call the
delegate; things get messy if you need to take function arguments.  :P

Here's a very simple example I got working today.  Of course, if there's
*any* other way of doing this, I'd go with that first, since
runtime-generated code can be an absolute bastard to debug.  ;)

ubyte[] gen_wrapper(void delegate() dg)
{
    scope buf = new MemoryStream;
    scope ass = new X86Assembler(buf);
    with( ass )
    {
        // dg.ptr *MUST* go into EAX.  dg.funcptr can go wherever you
        // like, just so long as you can use it with a call instruction.
        mov(Reg32.EAX, cast(size_t)dg.ptr);
        mov(Reg32.EDX, cast(size_t)dg.funcptr);
        call(Reg32.EDX);
        ret();

        // This really is redundant, but it makes me feel better :)
        int_(3);
    }
    return buf.data.dup;
}

void main()
{
    void hello()
    {
        writefln("Hello, world!");
    }

    auto hello_dg = &hello;

    // ArrayEx is just a wrapper around VirtualAlloc/VirtualFree.  I'm
    // using those to make sure the memory I use is set to allow both
    // writing and execution.
    ArrayEx!(ubyte) buf;
    scope(exit) delete buf;
    {
        auto tbuf = gen_wrapper(hello_dg);
        buf = new ArrayEx!(ubyte)(tbuf, Protection.ReadWriteExecute);
        delete tbuf;
    }

    writefln("Bytecode:");
    for( size_t i=0; i<buf.length; i++ )
    {
        if( i%16 != 0 )
            writef(" ");
        writef("%02x", buf[i]);
        if( (i+1)%16 == 0 )
            writef("\n");
    }
    writefln("");

    auto hello_fn = cast(void function())buf.ptr;
    writefln("Before hello_dg()");
    hello_dg();
    writefln("Between hello_dg() and hello_fn()");
    hello_fn();
    writefln("After hello_fn()");
}

Outputs:

Bytecode:
b8 00 00 00 00 ba ec 24 40 00 ff d2 c3 cd 03
Before hello_dg()
Hello, world!
Between hello_dg() and hello_fn()
Hello, world!
After hello_fn()

Hope that this was, if not at least helpful, a little interesting :)

	-- Daniel "compilers are for sissies"

-- 
int getRandomNumber()
{
    return 4; // chosen by fair dice roll.
              // guaranteed to be random.
}

http://xkcd.com/

v2sw5+8Yhw5ln4+5pr6OFPma8u6+7Lw4Tm6+7l6+7D
i28a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP  http://hackerkey.com/



More information about the Digitalmars-d mailing list