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

Leave Comments, Critiques, and Suggestions Here?

Creating and using DLL files in Windows

Setting your Environment

In order to compile DLL file you'll have to modify your sc.ini file, add this to sc.ini file

[Environment]
LIB="%@P%\..\lib"
DFLAGS="-I%@P%\..\import" -version=Tango -defaultlib=tango-dmd.lib -debuglib=tango-dmd.lib tango-dmd.lib
LINKCMD=%@P%\link.exe

main difference is that vanilla sc.ini file has "-L+tango-dmd.lib", while in order to compile and link DLL you got to leave out "-L+". So, if you want to create DLL files, it would be best to have two sc.ini files - one for usual compilation and one for DLL's.

Creating a simple DLL

Here is the source for simple DLL file:

module mydll;
import tango.sys.win32.Types;

import tango.io.Stdout;
import tango.stdc.stdio;

// The core DLL init code.
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:
		tango.stdc.stdio._fcloseallp = null;
		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

export extern(C) void dllprint() { Stdout.formatln("hello dll world\n"); }

As you can see, this is a usual DLL structure, we have a rt_init and rt_term functions which are called when DLL is loaded and when it is detached, nothing fancy in here. Be sure to include import tango.sys.win32.Types; as it is needed for Windows types information. That is all you need for this program.

In this example DLL we have a simple function dllprint which prints "hello dll world" when called. It has a external C linkage and we imported tango.io.Stdout in order to use Stdout.formatln function. So, basically we have a hello world function in this dll.

Compiling your DLL

In order to compile this example to dll, we also need a .def file which defines our dll. Here it is:

LIBRARY "mydll.dll"
EXETYPE NT
SUBSYSTEM WINDOWS
CODE SHARED EXECUTE
DATA WRITE
EXPORTS
	dllprint

You keep track, in your DLL, of EXPORTS where you define your function names and LIBRARY "mydll.dll" where name of the dll resides. Pretty simple, huh? So now, we have two files - mydll.d and mydll.def. On with the compilation.

To compile and link this file you have to set your environment as previously described and type this line to compile it:

dmd -ofmydll.dll mydll.d mydll.def

Using your sample DLL

import tango.sys.SharedLib;
import tango.util.log.Trace;

// declaring our function pointer
typedef extern (C) void function() tdllprint;
tdllprint dllprint;

void main() {
    if (auto lib = SharedLib.load(`mydll.dll`)) {
        Trace.formatln("Library successfully loaded");

        void* ptr = lib.getSymbol("dllprint");
        if (ptr) {
            Trace.formatln("Symbol dllprint found. Address = 0x{:x}", ptr);
            
            // binding function address from DLL to our function pointer
            void **point = cast(void **)&dllprint;
            *point = ptr;
            
            // using our function
            dllprint();
            
        } else {
            Trace.formatln("Symbol dllprint not found");
        }

        lib.unload();
    } else {
        Trace.formatln("Could not load the library");
    }

    assert (0 == SharedLib.numLoadedLibs);
}

As you can see, binding a function from DLL is trivial. Note that you should compile this example with default environment, that is with "-L+" in your sc.ini - as you compile other normal programs.