Report a bug
If you spot a problem with this page, click here to create a Bugzilla issue.
Improve this page
Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using a local clone.

Creating Windows DLLs

Contents
  1. References
Windows DLLs (aka shared libraries) are a method of sharing instances of executable code and data between processes. Although they perform the same role as shared libraries in other systems like Linux, OSX, and FreeBSD, they are implemented quite differently. The information in this article is specific to Windows DLLs.

Build a DLL

Code for the DLL

  1. Create the file mydll.d:
    module mydll;
    
    import core.sys.windows.dll;
    import core.stdc.stdio;
    
    mixin SimpleDllMain;
    
    export void entry()
    {
        printf("called mydll.entry()\n");
    }
    
  2. Compile and link the DLL:
    dmd -shared mydll
    
    which will create the files mydll.lib (the import library) and mydll.dll (the dll).
  3. Create the file mydll.di:
    module mydll;
    
    export void entry();
    
  4. Create the file myexe.d:
    module myexe;
    
    import mydll;
    
    int main()
    {
        mydll.entry();
        return 0;
    }
    
  5. Compile the myexe.d file and link is with the mydll.lib file to create the myexe.exe file:
    dmd myexe.d mydll.lib
    
  6. Run myexe:
    C:> myexe
    called mydll.entry()
    
  7. DllMain - Entry Point

    A Windows DLL must have an entry point, much like the main function in an executable. It looks like:
    module dllmain;  // nice to always name it this
    
    import core.sys.windows.windef : HINSTANCE, BOOL, DWORD, LPVOID;
    import core.sys.windows.winnt;
    import core.sys.windows.dll : dll_process_attach, dll_process_detach,
                                  dll_thread_attach, dll_thread_detach;
    
    __gshared HINSTANCE g_hInst;  // saved instance handle for the DLL
    
    /***********************************
     * DLL entry point.
     * Params:
     *      hInstance = instance handle for the DLL
     *      ulReason = why the DllMain is being called
     *      fImpLoad = null if Dll is explicitly loaded, !null if implicitly loaded
     * Returns:
     *      true for success, false for failure
     */
    extern (Windows)
    BOOL DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID fImpLoad)
    {
        switch (ulReason)
        {
            case DLL_PROCESS_ATTACH: // when the DLL is first loaded
                g_hInst = hInstance;  // save it for later
                return dll_process_attach(hInstance, true); // perform process-relative initialization
    
            case DLL_PROCESS_DETACH: // when DLL is being unloaded
                return dll_process_detach(hInstance, true); // perform process-relative teardown
    
            case DLL_THREAD_ATTACH:  // new thread initialization
                return dll_thread_attach(true, true); // perform thread-relative initialization
    
            case DLL_THREAD_DETACH:  // thread is ending
                return dll_thread_detach(true, true); // perform thread teardown
    
            default:
                assert(0);
        }
    }
    
    Or, since this is just boilerplate code, this will do nicely:
    module dllmain;
    
    import core.sys.windows.dll;
    
    mixin SimpleDllMain;
    
    The compiler recognizes DllMain, and emits a reference to __acrtused_dll which will pull in the DLL support code from the C runtime library. It will also cause the addition of the debug runtime library (for symbolic debug compiles) or the default runtime library (otherwise) to be searched by the linker.

    Exporting Definitions

    In order for an executable to reference a name in the DLL, that name must be exported by the DLL. For example, to export the symbol func from this module:
    module mydll;
    export int func() { return 3; }
    
    the compiler inserts the following Export Definition directive into the object file:
    EXPDEF expflag=x00, export '__D5mydll4funcFZi', internal '', ordinal=x0
    
    for OMF files, and the equivalent in MSCOFF object files. EXPDEF informs the linker that mydll.func is to be put in the export table of the DLL being linked. That's the only addition to the object file.

    Importing Declarations

    The EXE file, when a DLL is attached to it, needs to know how to call it. This is called importing a declaration from the DLL. Prepare an import file mydll.di:
    module mydll;
    export int func(); // note no function body
    
    module myexe;
    import mydll;
    
    int test() { return func(); }
    
    Compiling myexe.d uncovers the magic:
    extrn   __imp___D5mydll4funcFZi
    
    __D5myexe4testFZi       comdat
            call    dword ptr __imp___D5mydll4funcFZi
            ret
    
    A direct call is not made to mydll.func(), instead an indirect call to mydll.func() is made via a pointer to mydll.func(), and the pointer’s name is __imp___D5mydll4func.

Import Library

Exporting the definitions from the dll's object file, and hooking the exe file up to the dll's exports requires an additional file, the import library. The import library is automatically created by the linker when the dll is linked. This library then must be added to the link step when linking the executable file.

References

  1. Win32 DLLs in D
  2. Dynamic-Link Library Best Practices