www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Shared static constructors from C# EXE

reply Thalamus <norobots foo.com> writes:
Hi everyone,

I looked in the forums and Google in general but I didn't find a 
similar question, and I'm stumped. I have a scenario where a set 
of classes must be registered with a class mapper and then 
instantiated via a factory. The classes themselves are agnostic 
of one another, and all interaction with these classes after 
instantiation is via interfaces. I've succeeded in having the 
classes register themselves at runtime by using shared static 
constructors, which is really seamless! This works just fine when 
the EXE is written in D, but only a subset of shared static 
constructors get called when the EXE is written in C#.

A few more details:

ClassMapper class includes a shared static constructor that 
initializes a class map associative array, and it exposes a 
public Map() function an Instantiate(TypeInfo_Class classType, 
etc.) function.
Class A and Class B both have shared static constructors that 
call Map() to register themselves.

The above is built into a D EXE, and ClassMapper.map is fully 
populated before main() starts.

-OR-

The above is built into a D DLL with a CreateClassB() function 
exposed via .def, etc. A C# EXE invokes CreateClassB() via 
p/invoke.

The call works just fine, and ClassB and anything class B calls, 
recursively, are all registered with ClassMapper.map just fine, 
because their shared static constructors are invoked. But, Class 
A is not in the map because its shared static constructor is 
never called.

I don't control the EXE itself and the code I write to interface 
with it must be either C# or JavaScript, but this repros with a 
test C# driver EXE as well. The interfacing C# code can only be 
aware of the exposed D DLL functions defined in .def and 
shouldn't be aware directly of Class A, B, or ClassMapper, the 
factory specifically, etc..

Has anyone seen this before, or have an idea for a workaround? 
The best workaround I've come up with so far is to use a registry 
config file instead of calls to Map() so the ClassMapper can 
populate itself. It's not a very elegant solution, but it was the 
best I was able to come up with for C++ too. (In C# I've used 
class attributes, which I had thought was elegant. D's shared 
static constructors are even more so, though.)

thanks much!
Thalamus
Feb 25 2016
parent reply Guillaume Piolat <contact gam3sfrommars.fr> writes:
On Thursday, 25 February 2016 at 14:01:30 UTC, Thalamus wrote:
 I don't control the EXE itself and the code I write to 
 interface with it must be either C# or JavaScript, but this 
 repros with a test C# driver EXE as well. The interfacing C# 
 code can only be aware of the exposed D DLL functions defined 
 in .def and shouldn't be aware directly of Class A, B, or 
 ClassMapper, the factory specifically, etc..
Make sure your DLL must initialize the D runtime, which is where shared static constructors should get called. http://wiki.dlang.org/Win32_DLLs_in_D Alternatively you can call Runtime.initialize() yourself in your entry point.
Feb 25 2016
parent reply Thalamus <norobots foo.com> writes:
On Thursday, 25 February 2016 at 14:07:21 UTC, Guillaume Piolat 
wrote:
 On Thursday, 25 February 2016 at 14:01:30 UTC, Thalamus wrote:
 I don't control the EXE itself and the code I write to 
 interface with it must be either C# or JavaScript, but this 
 repros with a test C# driver EXE as well. The interfacing C# 
 code can only be aware of the exposed D DLL functions defined 
 in .def and shouldn't be aware directly of Class A, B, or 
 ClassMapper, the factory specifically, etc..
Make sure your DLL must initialize the D runtime, which is where shared static constructors should get called. http://wiki.dlang.org/Win32_DLLs_in_D Alternatively you can call Runtime.initialize() yourself in your entry point.
Hi Guillaume, Thanks for responding so quickly! I had found that wiki page before and I'd been following the "DLLs with a C Interface" section closely. I had forgotten to add -shared when building the DLL, but the behavior didn't change when I added it. So, I added a call to Runtime.initialize() as the first line of the endpoint I'm exposing. (I also made sure that this was the only endpoint invoked and that it was only invoked once just to be cautious.) I can see Runtime.initialize() being called, but the Class A shared static constructor still is not called when run from the C# EXE. Do you have any other ideas? In the meantime, I'm working on putting together a minimal repro source, but the scenario is a bit complicated so there's a lot of details to whittle away. thanks! Gene
Feb 25 2016
parent reply Benjamin Thaut <code benjamin-thaut.de> writes:
On Thursday, 25 February 2016 at 14:42:14 UTC, Thalamus wrote:
 your entry point.
Hi Guillaume, Thanks for responding so quickly! I had found that wiki page before and I'd been following the "DLLs with a C Interface" section closely. I had forgotten to add -shared when building the DLL, but the behavior didn't change when I added it. So, I added a call to Runtime.initialize() as the first line of the endpoint I'm exposing. (I also made sure that this was the only endpoint invoked and that it was only invoked once just to be cautious.) I can see Runtime.initialize() being called, but the Class A shared static constructor still is not called when run from the C# EXE. Do you have any other ideas? In the meantime, I'm working on putting together a minimal repro source, but the scenario is a bit complicated so there's a lot of details to whittle away. thanks! Gene
You shouldn't be calling Runtime.initialize() manually. Just do the following in one of your source files: import core.sys.windows.dll; mixin SimpleDllMain; This will generate a DllMain that will correctly initialize and deinitialize druntime. Kind Regards Benjamin Thaut
Feb 25 2016
parent reply Thalamus <norobots foo.com> writes:
On Thursday, 25 February 2016 at 16:05:37 UTC, Benjamin Thaut 
wrote:
 On Thursday, 25 February 2016 at 14:42:14 UTC, Thalamus wrote:
 your entry point.
Hi Guillaume, Thanks for responding so quickly! I had found that wiki page before and I'd been following the "DLLs with a C Interface" section closely. I had forgotten to add -shared when building the DLL, but the behavior didn't change when I added it. So, I added a call to Runtime.initialize() as the first line of the endpoint I'm exposing. (I also made sure that this was the only endpoint invoked and that it was only invoked once just to be cautious.) I can see Runtime.initialize() being called, but the Class A shared static constructor still is not called when run from the C# EXE. Do you have any other ideas? In the meantime, I'm working on putting together a minimal repro source, but the scenario is a bit complicated so there's a lot of details to whittle away. thanks! Gene
You shouldn't be calling Runtime.initialize() manually. Just do the following in one of your source files: import core.sys.windows.dll; mixin SimpleDllMain; This will generate a DllMain that will correctly initialize and deinitialize druntime. Kind Regards Benjamin Thaut
Thanks Benjamin. When I went to whittle this down to its barest essentials, though, the repro is pretty simple. It involves LIBs, but not Dlls, and it doesn't require anything but a single D EXE. NOTE: if attempting to repro any of this, you must do a clean build after changing build.cmd or main.d before you'll see a change in behavior. I have the shared static ctors output files to make it really easy to see. If you have Class A: module ClassA; import std.file; import std.stdio; export class ClassA { shared static this() { File file = File(r"c:\A.txt", "w"); file.writeln("Called A's shared static constructor."); file.flush(); file.close(); } } and you have Class B: module ClassB; import std.file; import std.stdio; export class ClassB { shared static this() { File file = File(r"c:\B.txt", "w"); file.writeln("Called B's shared static constructor."); file.flush(); file.close(); } } and you have main.d: void main() { } And you build it in one step into an EXE: dmd -m64 -debug ClassA.d ClassB.d main.d -ofDriver.exe Then you run Driver.exe, both A.txt and B.txt are created. But, if you build it as a LIB and then link the LIB to the EXE: dmd -c -lib -m64 -debug ClassA.d ClassB.d -ofInit.lib dmd -m64 -debug Init.lib main.d -ofDriver.exe When you run Driver.exe, neither are created. If you then add "import ClassA" to main.d and clean build, only A.txt will be created, or instead if you add "import ClassB", then only B.txt is created. Also, if either of these is included, Driver.exp and Driver.lib are emitted by the build, whereas otherwise they aren't. It looks from this like a class in a linked LIB that is not directly imported will not have its shared static constructor called. Am I missing something obvious? :) Long term I will need all this not only in separate LIBs but in separate DLLs. My scenario is roughly like this (-> indicate dependencies): ClassA : IClass -> ClassManagement ClassB : IClass -> ClassManagement EntryPoint -> ClassManagement, ClassB, IClass Then EntryPoint asks ClassManagement to give it an instance of ClassB's complement (ClassA). ClassManagement only knows anything about any of these classes via TypeInfo_Class object mappings, and it uses Object.factory to instantiate them. EntryPoint and its dependencies then work with that object via the IClass interface. I've gotten this to work in C# easily and C++ with some effort. (Class map population was, of course, very different for each, though.) I can probably figure out a way to make this work for now, but if there's a way to ensure shared static ctors are run in this scenario without importing modules across separation boundaries, it would be a very good thing. thanks! Gene
Feb 25 2016
parent reply Benjamin Thaut <code benjamin-thaut.de> writes:
On Thursday, 25 February 2016 at 17:46:18 UTC, Thalamus wrote:
 On Thursday, 25 February 2016 at 16:05:37 UTC, Benjamin Thaut 
 wrote:
 [...]
Thanks Benjamin. When I went to whittle this down to its barest essentials, though, the repro is pretty simple. It involves LIBs, but not Dlls, and it doesn't require anything but a single D EXE. [...]
The problem is that when you build with -lib the resulting library is optimized for linker stripping. E.g. everything that is not directly used will not be pulled into the final executable. If you want everything in the final exectuable you shouldn't be using a .lib file. Instead compile with -c resutling in a .obj file and link the resulting .obj file into your executable. Then everything will end up in the final executable even if its not directly used. Kind Regards Benjamin Thaut
Feb 26 2016
parent Thalamus <norobots foo.com> writes:
On Friday, 26 February 2016 at 08:37:35 UTC, Benjamin Thaut wrote:
 On Thursday, 25 February 2016 at 17:46:18 UTC, Thalamus wrote:
 On Thursday, 25 February 2016 at 16:05:37 UTC, Benjamin Thaut 
 wrote:
 [...]
Thanks Benjamin. When I went to whittle this down to its barest essentials, though, the repro is pretty simple. It involves LIBs, but not Dlls, and it doesn't require anything but a single D EXE. [...]
The problem is that when you build with -lib the resulting library is optimized for linker stripping. E.g. everything that is not directly used will not be pulled into the final executable. If you want everything in the final exectuable you shouldn't be using a .lib file. Instead compile with -c resutling in a .obj file and link the resulting .obj file into your executable. Then everything will end up in the final executable even if its not directly used. Kind Regards Benjamin Thaut
Thanks Benjamin! I knew there had to be some simple thing I didn't know I needed to do here. It worked like a charm in the simple repro. In my actual code, I'm running into _deh_beg already defined and a few more linker errors, but I haven't had the chance to delve there yet. thanks again!
Feb 26 2016