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