Download Reference Manual
The Developer's Library for D
About Wiki Forums Source Search Contact

DLL woes

Moderators: larsivi kris

Posted: 11/05/08 18:21:30

Hi all, not sure if this is the right forum, but well. I am playing around with an XML parser that builds a list of DWT widgets and then calls the user code, that is packed in a DLL, because that allows for easy binding of strings to program symbols. Here I encounter the problem when passing objects to functions in the DLL. The simplest test case I could produce boils down to this:

myclasses.d:

module myclasses;
class base { char[] toString(){ return "I am base"; } }
class c1: base {   char[] toString(){ return "I am c1";} }
class c2: base {   char[] toString(){ return "I am c2";} }

mymain.d:

import tango.sys.SharedLib;

import myclasses;

void main(){
  base[] instances;
  // populate instances
  instances.length=3;
  instances[0] = new base;
  instances[1] = new c1;
  instances[2] = new c2;
  // no problem
  assert( cast(c1) instances[1] !is null);
  assert( cast(c2) instances[2] !is null);

  SharedLib lib = SharedLib.load(`mydll.dll`);
  assert( lib !is null);
  
  extern(C) void function(base[]) my_c_check;
  
  void*  ptr;
  void** point;
  ptr = lib.getSymbol("my_c_check");
  point = cast(void** ) &my_c_check;
  *point = ptr;

  my_c_check( instances );
}

and, finally, mydll.d:

import myclasses;
import tango.sys.win32.Types;
import tango.util.log.Trace;
// The core DLL init code, taken from tango wiki.
extern (C) bool  rt_init( void delegate( Exception ) dg = null );
extern (C) bool  rt_term( void delegate( Exception ) dg = null ); 

HINSTANCE g_hInst;

extern (Windows) BOOL 
DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved){
  switch (ulReason){
  case DLL_PROCESS_ATTACH:
    rt_init();
    break;
    
  case DLL_PROCESS_DETACH:
    rt_term();
    break;
    
  case DLL_THREAD_ATTACH:
  case DLL_THREAD_DETACH:
    // Multiple threads not supported yet
    return false;
  }
  g_hInst=hInstance;
  return true;
}
// End of core DLL Init

extern(C) int my_c_check( base[] instances){
  Trace.formatln("into my_c_check");
  
  assert( instances.length ==3 );
  Trace.formatln("length check OK");
  assert( instances[0] !is null);
  assert( instances[1] !is null);
  assert( instances[2] !is null);
  Trace.formatln("instances check OK");
  Trace.formatln("instances[0] says: {}", instances[0].toString() );
  Trace.formatln("instances[1] says: {}", instances[1].toString() );
  Trace.formatln("instances[2] says: {}", instances[2].toString() );
  // Now assertions fail.
  assert( cast(c1) instances[1] !is null);
  assert( cast(c2) instances[2] !is null);

  return 0;
}

It works fine until the last two asserts: The casting does not work, why? I checked the casts to void, the addresses are correct. Also the instances identify themselves correctly. WTF? It also does not help to drop the (C) decoration, this only turns the name of the exported symbol.

Any thoughts?

Ciao

Tom

Author Message

Posted: 11/05/08 18:26:05

Maybe the operator priority works like this "assert( cast(c1) (instances[1] !is null));" ? Maybe you could try to "assert( (cast(c1) instances[1]) !is null);"?

Posted: 11/05/08 20:58:19

I could try, but exactly the same construct works in mymain.d. I am afraid this is not the reason :-/

Ciao

Tom

Posted: 11/06/08 17:31:26

What you have to understand is that D is not yet made to be used with DLLs properly. A lot of the stuff automatically handled by the D runtime needs to be handled by you explicitly.

What is happening is your DLL and your main code have 2 separate copies of the TypeInfo tree. What is happening is that you are attempting to cast from a type in one tree to the same type in another tree. Since they are not technically equivalent the cast fails.

I'm not sure exactly how you would fix this without D's compiler being smarter about how it stores the TypeInfo tree. It isn't talked about on D's web site. I'd ask the question to the D newsgroup, as it's really specific to the D compiler, not Tango.

Good luck.

Posted: 11/06/08 19:05:38

it should work with (*nix) shared libs, but not with dlls

Posted: 11/06/08 20:00:01

@Steve: Fine if I have to handle it, if I only knew how. Actually, adding

  Trace.formatln("instances[0] is: {}", instances[0].classinfo.name );
  Trace.formatln("instances[1] is: {}", instances[1].classinfo.name );
  Trace.formatln("instances[2] is: {}", instances[2].classinfo.name );

in the dll gives

instances[0] is: myclasses.base
instances[1] is: myclasses.c1
instances[2] is: myclasses.c2

So, I am at my wit's end on how to help the compiler. But you are probably right that this is a compiler bug/issue. And, finally, you give me the impression I am not trying something completely insane :-)

Ciao

Tom

Posted: 11/06/08 20:34:02

The issue is not that your TypeInfo tree has the wrong names, the issue is that you have two identical TypeInfo trees, and the way the compiler checks to see if one derives from another is by looking at a single tree.

Try this, print out the address of the classinfo reference of the instance, and the classinfo reference of the type you expect it to be:

Trace.formatln("instance class info is at {}", cast(void *)instances[0].classinfo);
Trace.formatln("base class info is at {}", cast(void*)(new base).classinfo);

These will be two different instances. Because your dll built its classinfo from its own TypeInfo tree, and your main program built its classinfo from its own tree. The compiler doesn't compare anything but the address to see if your instance can be casted.

Posted: 11/06/08 20:59:55

OK, thanks for the explanation. Let's see if there will something happen from the posting on the Digital Mars site. In the mean time I'll check how/if this works under Linux.

Ciao

Tom