www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Passing member function pointer to external C functions.

reply Dawid =?ISO-8859-2?Q?Ci=EA=BFarkiewicz?= <arael fov.pl> writes:
I'm trying to register member function of one of my class as expat handler.
Of course expat functions are taking pointer to a function and are declared
in extern(C) scope. I understand that member functions are in fact normal
functions invisibly taking "this" argument and so is this possible to do
such thing in D? If answer is "yes": how and "WOW I love this language"; if
answer is "no": what would be the best workaround.

For clarifying this is what I'm trying to do:

extern(C){

 void XML_SetElementHandler(XML_Parser p, XML_StartElementHandler start,
   XML_EndElementHandler end);

 typedef void function(void *userData, XML_Char *name, XML_Char **atts)
   XML_StartElementHandler;

 typedef void function(void *userData, XML_Char *name)
   XML_EndElementHandler;
}

class Foo{

 this(){
 _parser = XML_ParserCreate(null);
 XML_SetElementHandler(_parser,
  cast(XML_StartElementHandler)&this.startHandle,
  cast(XML_EndElementHandler)&this.endHandle);
 }

extern(C){
 void startHandle(void *userData,XML_Char *name,XML_Char **atts){
  printf("started: %sn",name);
 }
 void endHandle(void *userData,XML_Char *name){
  printf("ended: %sn",name);
 }

}

I get:
e2ir: cannot cast from void delegate(void*userData,char*name,char**atts) to
void(C *)(void*userData,char*name,char**atts)
e2ir: cannot cast from void delegate(void*userData,char*name) to void(C *
(void*userData,char*name)

Regards,
-- 
Dawid Ciężarkiewicz | arael
jid: arael fov.pl
Jan 28 2005
next sibling parent reply Kris <Kris_member pathlink.com> writes:
I think you can do that by making the class members static ...

- Kris

In article <cteolm$12va$1 digitaldaemon.com>, Dawid
=?ISO-8859-2?Q?Ci=EA=BFarkiewicz?= says...
I'm trying to register member function of one of my class as expat handler.
Of course expat functions are taking pointer to a function and are declared
in extern(C) scope. I understand that member functions are in fact normal
functions invisibly taking "this" argument and so is this possible to do
such thing in D? If answer is "yes": how and "WOW I love this language"; if
answer is "no": what would be the best workaround.

For clarifying this is what I'm trying to do:

extern(C){

 void XML_SetElementHandler(XML_Parser p, XML_StartElementHandler start,
   XML_EndElementHandler end);

 typedef void function(void *userData, XML_Char *name, XML_Char **atts)
   XML_StartElementHandler;

 typedef void function(void *userData, XML_Char *name)
   XML_EndElementHandler;
}

class Foo{

 this(){
 _parser = XML_ParserCreate(null);
 XML_SetElementHandler(_parser,
  cast(XML_StartElementHandler)&this.startHandle,
  cast(XML_EndElementHandler)&this.endHandle);
 }

extern(C){
 void startHandle(void *userData,XML_Char *name,XML_Char **atts){
  printf("started: %sn",name);
 }
 void endHandle(void *userData,XML_Char *name){
  printf("ended: %sn",name);
 }

}

I get:
e2ir: cannot cast from void delegate(void*userData,char*name,char**atts) to
void(C *)(void*userData,char*name,char**atts)
e2ir: cannot cast from void delegate(void*userData,char*name) to void(C *
(void*userData,char*name)

Regards,
-- 
Dawid Ciężarkiewicz | arael
jid: arael fov.pl

Jan 28 2005
parent Dawid =?ISO-8859-2?Q?Ci=EA=BFarkiewicz?= <arael fov.pl> writes:
Kris wrote:

 I think you can do that by making the class members static ...

Well, right but this way I lose information stored in "this" and I don't know on which object I work. I was trying to write multithreaded server application that communicates with xml. Each connection has to be separately parsed. I need tons of parser - each knowing almost nothing about other. -- Dawid Ciężarkiewicz | arael jid: arael fov.pl
Jan 29 2005
prev sibling next sibling parent reply pragma <pragma_member pathlink.com> writes:
These are the culprit lines from your post:

  cast(XML_StartElementHandler)&this.startHandle,
  cast(XML_EndElementHandler)&this.endHandle);

I understand what you're trying to do, as I thought the same thing myself: since D passes 'this' as the first argument to a method, why not pass along my class's methods to Expat since they happen to match the same spec. This would work amazingly well in C++ but D doesn't like it (obviously). There's a lot more going on in those lines than meets the eye: 1) D converts "&this.startHandle" into a *delegate*, which contains a reference to 'this' as well as the address of "startHandle". 2) D does not provide a means for casting a delegate into a function, so the cast to XML_StartElementHandler fails miserably. A more complete explaination of the underpinnings of delegates is here: http://www.prowiki.org/wiki4d/wiki.cgi?DocComments/Function Now what you're asking for is a hack to crack the function pointer out of a delegate. I don't advocate this in the slightest, since it's probing around a grey area that may or may not be changed in the D ABI.
 import std.stdio;
 
 struct DelegateStruct {
 	void *ptr;
 	void *func;
 };
 
 class Test{
 	extern(C) int myMethod(int a){
 		writefln("A is %d",a);	
 		return a+1;
 	}
 }
 
 extern(C) typedef int delegate(int) TestDelegate;
 extern(C) typedef int function(void*,int) TestFunctionHandle;
 
 int testFunction(void* thisPtr,TestFunctionHandle handle,int a){
 	return handle(thisPtr,a);
 }
 
 void main(){
 	Test t = new Test();
 	writefln("calling directly");
 	writefln("result is: %d",t.myMethod(42));
 	TestDelegate del = &t.myMethod;
 	DelegateStruct ds = *cast(DelegateStruct*)cast(void**)&del;
 	TestFunctionHandle handle = cast(TestFunctionHandle)ds.func;
 	writefln("calling indirectly");
 	writefln("result is: %d",testFunction(cast(void*)t,handle,42));	
 }

I'm sure there's probably a way to use a template and/or mixin to make this much more clean. The secret here is the liberal use of "extern(C)". The default D linkage likes to throw the last argument around as EAX, which happens to be "this" for object.method calls (at least for DMD, I don't know what GDC is up to). - EricAnderton at yahoo
Jan 28 2005
parent reply Dawid =?ISO-8859-2?Q?Ci=EA=BFarkiewicz?= <arael fov.pl> writes:
pragma wrote:

 I'm sure there's probably a way to use a template and/or mixin to make
 this much more clean.
 
 The secret here is the liberal use of "extern(C)".  The default D linkage
 likes to throw the last argument around as EAX, which happens to be "this"
 for object.method calls (at least for DMD, I don't know what GDC is up
 to).

This is nice hack, but I was thinking about something more portable, stable etc. As I understand whole hack is set on fact that EAX isn't changed and stores "this" pointer, right? Can I really depend on this in multithreaded environment using external C callback? I mean - I will register such function as handler, then many things will happen (EAX will definitely change) and then expat will call my handler and I don't think it will set EAX to something usable. If I understand this will end with random "this" and segfault. I'm not sure if I understood you so sorry if I'm jabbering. -- Dawid Ciężarkiewicz | arael jid: arael fov.pl
Jan 29 2005
parent pragma <pragma_member pathlink.com> writes:
In article <ctfqta$287m$1 digitaldaemon.com>, Dawid
=?ISO-8859-2?Q?Ci=EA=BFarkiewicz?= says...
pragma wrote:

 I'm sure there's probably a way to use a template and/or mixin to make
 this much more clean.
 
 The secret here is the liberal use of "extern(C)".  The default D linkage
 likes to throw the last argument around as EAX, which happens to be "this"
 for object.method calls (at least for DMD, I don't know what GDC is up
 to).

This is nice hack, but I was thinking about something more portable, stable etc. As I understand whole hack is set on fact that EAX isn't changed and stores "this" pointer, right?

As long as you use "extern(C)" in all the places illustrated, D will compile those methods/functions as 'stdcall' which is the C standard (no EAX mojo). If you try removing "extern(C)", so the compiler uses the D linkage instead, it segfaults all over the place. A hack is a hack. I don't advocate using it unless it gives you a sound advantage that can't be met otherwise. If it passes muster with testing on all your target platforms, then techically its sound code. I for one never code with a hack unless it achives a requirement, like better performance or a reduced memory footprint; and even then its a last resort. Plus I document the heck out of it (flagging it with "//HACK:") to warn other developers of what, why and how. - EricAnderton at yahoo
Jan 29 2005
prev sibling next sibling parent Dawid =?ISO-8859-2?Q?Ci=EA=BFarkiewicz?= <arael fov.pl> writes:
Dawid Ciężarkiewicz wrote:

 I'm trying to register member function of one of my class as expat
 handler. Of course expat functions are taking pointer to a function and
 are declared in extern(C) scope. I understand that member functions are in
 fact normal functions invisibly taking "this" argument and so is this
 possible to do such thing in D? If answer is "yes": how and "WOW I love
 this language"; if answer is "no": what would be the best workaround.

I've found my personal solution. Because every class instance that need his own parser is new thread I register static member function as handler that use "getThis()" to run good instance of member function. Like this: static void StartHandler(void *userData,XML_Char *name,XML_Char **atts){ (cast(ConnectionHandler)getThis()).startHandle(userData,name,atts); } -- Dawid Ciężarkiewicz | arael jid: arael fov.pl
Jan 29 2005
prev sibling next sibling parent reply "Andrew Fedoniouk" <news terrainformatica.com> writes:
Hi, Dawid,

Why not to use something simple as below?
(It's just a scetch, never compiled it)

class MyHandler
{
 void startHandler(XML_Char *name,XML_Char **atts)
 {
  printf("started: %sn",name);
 }
 void endHandler(XML_Char *name)
 {
  printf("ended: %sn",name);
 }

 extern(C) static void _startHandler(void *userData,XML_Char *name,XML_Char 
**atts)
 {
  (cast(MyHandler)(userData)).startHandler(name,atts);
 }
 extern(C) static void _endHandler(void *userData,XML_Char *name)
 {
  (cast(MyHandler)(userData)).endHandler(name);
 }
 void setup(XML_Parser p)
 {
     XML_SetUserData(p, cast(void*) this);
     XML_SetElementHandler(p,_startHandle,_endHandle);
 }
}

You can wrap it into template if you want to use it in multiple places.
Jan 29 2005
parent reply Dawid =?ISO-8859-2?Q?Ci=EA=BFarkiewicz?= <arael fov.pl> writes:
Andrew Fedoniouk wrote:

 Hi, Dawid,
 
 Why not to use something simple as below?

Brilinat! I didn't know that user that is working like this. Thank you. This is very good answer to my problem. -- Dawid Ciężarkiewicz | arael jid: arael fov.pl
Jan 30 2005
parent reply "Andrew Fedoniouk" <news terrainformatica.com> writes:
 Brilinat! I didn't know that user that is working like this. Thank you. 
 This
 is very good answer to my problem.

I didn't get the exact meaning of this phrase. Is it something good (Coś dobry) ? :) (just curious) Andrew Fedoniouk. http://terrainformatica.com
Jan 30 2005
parent reply Dawid =?ISO-8859-2?Q?Ci=EA=BFarkiewicz?= <arael fov.pl> writes:
Andrew Fedoniouk wrote:

 Brilinat! I didn't know that user that is working like this. Thank you.
 This
 is very good answer to my problem.

I didn't get the exact meaning of this phrase. Is it something good (Coś dobry) ? :) (just curious)

Oh, sorry - my English is far from being perfect. I meant: That is right solution that satisfies me. And from where did you get "coś dobry". It sounds funny - should be "coś dobrego". I niebardzo to było dobre, bo się pomyliłem. Za to Twoje rozwiązanie było poprawne. Szkoda tylko, że nawet pochwalić Cię nie potrafiłem. ;) Well, my language is beautiful. :) Regards, -- Dawid Ciężarkiewicz | arael jid: arael fov.pl
Jan 31 2005
parent h3r3tic <foo bar.baz> writes:
Dawid Ciężarkiewicz wrote:
 And from where did you get "coś dobry". It sounds funny - should be "coś
 dobrego".

/me agrees it's funny
 I niebardzo to było dobre, bo się pomyliłem. Za to Twoje rozwiązanie było
 poprawne. Szkoda tylko, że nawet pochwalić Cię nie potrafiłem. ;)

No teraz to na pewno kolesie zrozumieją, bueheheheh :>
 Well, my language is beautiful. :)

I wouldn't quite agree :> I've been forced to speak it, sb help me
Jan 31 2005
prev sibling parent Russ Lewis <spamhole-2001-07-16 deming-os.org> writes:
Dawid Ciężarkiewicz wrote:
 I'm trying to register member function of one of my class as expat handler.
 Of course expat functions are taking pointer to a function and are declared
 in extern(C) scope. I understand that member functions are in fact normal
 functions invisibly taking "this" argument and so is this possible to do
 such thing in D? If answer is "yes": how and "WOW I love this language"; if
 answer is "no": what would be the best workaround.
 
 For clarifying this is what I'm trying to do:
 
 extern(C){
 
  void XML_SetElementHandler(XML_Parser p, XML_StartElementHandler start,
    XML_EndElementHandler end);
 
  typedef void function(void *userData, XML_Char *name, XML_Char **atts)
    XML_StartElementHandler;
 
  typedef void function(void *userData, XML_Char *name)
    XML_EndElementHandler;
 }
 
 class Foo{
 
  this(){
  _parser = XML_ParserCreate(null);
  XML_SetElementHandler(_parser,
   cast(XML_StartElementHandler)&this.startHandle,
   cast(XML_EndElementHandler)&this.endHandle);
  }
 
 extern(C){
  void startHandle(void *userData,XML_Char *name,XML_Char **atts){
   printf("started: %sn",name);
  }
  void endHandle(void *userData,XML_Char *name){
   printf("ended: %sn",name);
  }
 
 }

You need to create an extern(C) wrapper for the class function, and store the 'this' pointer in a struct, which you use as the userdata. Here's the bare bones of an example, with some nice translation of C strings to D strings thrown in, to boot: // override this class for your own uses... class ExpatParser { void startHandler(XML_char[] name,XML_Char[][] attrs) { return; } void endHandler (XML_char[] name) { return; } this() { _parser = XML_ParserCreate(null); ExpatParserUserData *ptr = new ExpatParserUserData[1]; ptr.ref = this; XML_SetUserData(_parser, cast(void*)ptr); XML_SetElementHandler(_parser, &startHandler_c, &endHandler_c); }; }; struct ExpatParserUserData { ExpatParser ref; }; extern(C) void startHandler_c(void *userdata_void, XML_Char *name_c, XML_Char **attrs_c) { ExpatParserUserData *userdata = cast(ExpatParserUserData*)userdata_void; XML_Char[] name = name_c[0..strlen(name_c)]; XML_Char[][] attrs; for( ; *attrs_c != null; attrs_c++) attrs ~= (*attrs_c)[0..strlen(*attrs_c)]; userdata.ref.startHandler(name,attrs); } you can write your own endHandler_c() the same way...
Jan 29 2005