For background information on what DLLs are and how they work Chapter 11 of Jeffrey Richter's book Advanced Windows is indispensible.
This guide will show how to create DLLs of various types with D.
import std.c.windows.windows; HINSTANCE g_hInst; extern (C) { void gc_init(); void gc_term(); void _minit(); void _moduleCtor(); void _moduleUnitTests(); } extern (Windows) BOOL DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved) { switch (ulReason) { case DLL_PROCESS_ATTACH: gc_init(); // initialize GC _minit(); // initialize module list _moduleCtor(); // run module constructors _moduleUnitTests(); // run module unit tests break; case DLL_PROCESS_DETACH: gc_term(); // shut down GC break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: // Multiple threads not supported yet return false; } g_hInst=hInstance; return true; }Notes:
LIBRARY MYDLL DESCRIPTION 'My DLL written in D' EXETYPE NT CODE PRELOAD DISCARDABLE DATA PRELOAD SINGLE EXPORTS DllGetClassObject @2 DllCanUnloadNow @3 DllRegisterServer @4 DllUnregisterServer @5The functions in the EXPORTS list are for illustration. Replace them with the actual exported functions from MYDLL. Alternatively, use implib. Here's an example of a simple DLL with a function print() which prints a string:
module mydll; export void dllprint() { printf("hello dll world\n"); }
LIBRARY "mydll.dll" EXETYPE NT SUBSYSTEM WINDOWS CODE SHARED EXECUTE DATA WRITEPut the code above that contains DllMain() into a file dll.d. Compile and link the dll with the following command:
dmd -ofmydll.dll mydll2.d dll.d mydll.def implib/system mydll.lib mydll.dllwhich will create mydll.dll and mydll.lib. Now for a program, test.d, which will use the dll:
import mydll; int main() { mydll.dllprint(); return 0; }Create a clone of mydll2.d that doesn't have the function bodies:
export void dllprint();Compile and link with the command:
dmd test.d mydll.liband run:
C:>test hello dll world C:>
There are many approaches to solving this problem:
For understanding COM, Kraig Brockshmidt's Inside OLE is an indispensible resource.
COM objects are analogous to D interfaces. Any COM object can be expressed as a D interface, and every D object with an interface X can be exposed as a COM object X. This means that D is compatible with COM objects implemented in other languages.
While not strictly necessary, the Phobos library provides an Object useful as a super class for all D COM objects, called ComObject. ComObject provides a default implementation for QueryInterface(), AddRef(), and Release().
Windows COM objects use the Windows calling convention, which is not the default for D, so COM functions need to have the attribute extern (Windows). So, to write a COM object:
import std.c.windows.com; class MyCOMobject : ComObject { extern (Windows): ... }The sample code includes an example COM client program and server DLL.
The underlying difficulty is what to do about garbage collection (gc). Each EXE and DLL will have their own gc instance. While these gc's can coexist without stepping on each other, it's redundant and inefficient to have multiple gc's running. The idea explored here is to pick one gc and have the DLLs redirect their gc's to use that one. The one gc used here will be the one in the EXE file, although it's also possible to make a seperate DLL just for the gc.
The example will show both how to statically load a DLL, and to dynamically load/unload it.
Starting with the code for the DLL, mydll.d:
/* * MyDll demonstration of how to write D DLLs. */ import std.c.stdio; import std.c.stdlib; import std.string; import std.c.windows.windows; import std.gc; HINSTANCE g_hInst; extern (C) { void _minit(); void _moduleCtor(); void _moduleDtor(); void _moduleUnitTests(); } extern (Windows) BOOL DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved) { switch (ulReason) { case DLL_PROCESS_ATTACH: printf("DLL_PROCESS_ATTACH\n"); break; case DLL_PROCESS_DETACH: printf("DLL_PROCESS_DETACH\n"); std.c.stdio._fcloseallp = null; // so stdio doesn't get closed break; case DLL_THREAD_ATTACH: printf("DLL_THREAD_ATTACH\n"); return false; case DLL_THREAD_DETACH: printf("DLL_THREAD_DETACH\n"); return false; } g_hInst = hInstance; return true; } export void MyDLL_Initialize(void* gc) { printf("MyDLL_Initialize()\n"); std.gc.setGCHandle(gc); _minit(); _moduleCtor(); // _moduleUnitTests(); } export void MyDLL_Terminate() { printf("MyDLL_Terminate()\n"); _moduleDtor(); // run module destructors std.gc.endGCHandle(); } static this() { printf("static this for mydll\n"); } static ~this() { printf("static ~this for mydll\n"); } /* --------------------------------------------------------- */ class MyClass { char[] concat(char[] a, char[] b) { return a ~ " " ~ b; } void free(char[] s) { delete s; } } export MyClass getMyClass() { return new MyClass(); }
LIBRARY MYDLL DESCRIPTION 'MyDll demonstration DLL' EXETYPE NT CODE PRELOAD DISCARDABLE DATA PRELOAD SINGLE-g turns on debug info generation, and -L/map generates a map file mydll.map.
import std.stdio; import std.gc; import mydll; //version=DYNAMIC_LOAD; version (DYNAMIC_LOAD) { import std.c.windows.windows; alias void function(void*) MyDLL_Initialize_fp; alias void function() MyDLL_Terminate_fp; alias MyClass function() getMyClass_fp; int main() { HMODULE h; FARPROC fp; MyDLL_Initialize_fp mydll_initialize; MyDLL_Terminate_fp mydll_terminate; getMyClass_fp getMyClass; MyClass c; printf("Start Dynamic Link...\n"); h = LoadLibraryA("mydll.dll"); if (h == null) { printf("error loading mydll.dll\n"); return 1; } fp = GetProcAddress(h, "D5mydll16MyDLL_InitializeFPvZv"); if (fp == null) { printf("error loading symbol MyDLL_Initialize()\n"); return 1; } mydll_initialize = cast(MyDLL_Initialize_fp) fp; (*mydll_initialize)(std.gc.getGCHandle()); fp = GetProcAddress(h, "D5mydll10getMyClassFZC5mydll7MyClass"); if (fp == null) { printf("error loading symbol getMyClass()\n"); return 1; } getMyClass = cast(getMyClass_fp) fp; c = (*getMyClass)(); foo(c); fp = GetProcAddress(h, "D5mydll15MyDLL_TerminateFZv"); if (fp == null) { printf("error loading symbol MyDLL_Terminate()\n"); return 1; } mydll_terminate = cast(MyDLL_Terminate_fp) fp; (*mydll_terminate)(); if (FreeLibrary(h) == FALSE) { printf("error freeing mydll.dll\n"); return 1; } printf("End...\n"); return 0; } } else { // static link the DLL int main() { printf("Start Static Link...\n"); MyDLL_Initialize(std.gc.getGCHandle()); foo(getMyClass()); MyDLL_Terminate(); printf("End...\n"); return 0; } } void foo(MyClass c) { char[] s; s = c.concat("Hello", "world!"); writefln(s); c.free(s); delete c; }Let's start with the statically linked version, which is simpler. It's compiled and linked with the command:
dmd test mydll.lib -gNote how it is linked with mydll.lib, the import library for mydll.dll. The code is straightforward, it initializes mydll.lib with a call to MyDLL_Initialize(), passing the handle to test.exe's gc. Then, we can use the DLL and call its functions just as if it were part of test.exe. In foo(), gc memory is allocated and freed both by test.exe and mydll.dll. When we're done using the DLL, it is terminated with MyDLL_Terminate().
Running it looks like this:
DLL_PROCESS_ATTACH Start Static Link... MyDLL_Initialize() static this for mydll Hello world! MyDLL_Terminate() static ~this for mydll End...The dynamically linked version is a little harder to set up. Compile and link it with the command:
dmd test -version=DYNAMIC_LOAD -gThe import library mydll.lib is not needed. The DLL is loaded with a call to LoadLibraryA(), and each exported function has to be retrieved via a call to GetProcAddress(). An easy way to get the decorated name to pass to GetProcAddress() is to copy and paste it from the generated mydll.map file under the Export heading. Once this is done, we can use the member functions of the DLL classes as if they were part of test.exe. When done, release the DLL with FreeLibrary().
Running it looks like this:
Start Dynamic Link... DLL_PROCESS_ATTACH MyDLL_Initialize() static this for mydll Hello world! MyDLL_Terminate() static ~this for mydll DLL_PROCESS_DETACH End...