Difference between revisions of "Win32 DLLs in D"

From D Wiki
Jump to: navigation, search
(Migrating from http://dlang.org/dll.html)
 
(this old code has been wrong for ages it is better to delete it so people stop copy/pasting and just use the druntime helpers instead)
(25 intermediate revisions by 8 users not shown)
Line 1: Line 1:
 
DLLs (Dynamic Link Libraries) are one of the foundations of system programming for Windows. The D programming language enables the creation of several different types of DLLs.
 
DLLs (Dynamic Link Libraries) are one of the foundations of system programming for Windows. The D programming language enables the creation of several different types of DLLs.
 +
 +
For background information on what DLLs are and how they work Chapter 11 of Jeffrey Richter's book [http://www.amazon.com/exec/obidos/ASIN/1572315482/classicempire Advanced Windows] is indispensible.
 +
 +
This guide will show how to create DLLs of various types with D.
 +
 +
== Compiling a DLL ==
 +
Use the [https://dlang.org/dmd-windows.html#switch-shared -shared] switch to tell the compiler that the generated code is to be put into a DLL. Code compiled for an EXE file will use the optimization assumption that '''_tls_index==0'''. Such code in a DLL will crash.
 +
 +
== DLLs with a C Interface ==
 +
 +
A DLL presenting a C interface can connect to any other code in a language that supports calling C functions in a DLL.
 +
 +
DLLs can be created in D in roughly the same way as in C. A DllMain() is required, but you can use the mixin template SimpleDllMain to insert it:
 +
 +
<syntaxhighlight lang="D">
 +
import core.sys.windows.windows;
 +
import core.sys.windows.dll;
 +
 +
mixin SimpleDllMain;
 +
 +
</syntaxhighlight>
 +
 +
Notes:
 +
 +
* Under the hood, this DllMain simply forwards to the appropriate helper functions found in core.sys.windows.dll. These setup the runtime, create thread objects for interaction with the garbage collector and initialize thread local storage data.
 +
* The DLL does not share its runtime or memory with other DLLs.
 +
* The presence of '''DllMain()''' is recognized by the compiler causing it to emit a reference to [http://www.digitalmars.com/ctg/acrtused.html __acrtused_dll] and the '''phobos.lib''' runtime library.
 +
 +
You must export any functions you want the user to access. Do this by using the '''export''' keyword in your code.
 +
 +
Alternatively, you can link with a .def ([http://www.digitalmars.com/ctg/ctgDefFiles.html Module Definition File]) along the lines of:
 +
 +
<pre>
 +
LIBRARY        MYDLL
 +
DESCRIPTION    'My DLL written in D'
 +
 +
EXETYPE NT
 +
CODE            PRELOAD DISCARDABLE
 +
DATA            WRITE
 +
 +
EXPORTS
 +
DllGetClassObject      @2
 +
DllCanUnloadNow        @3
 +
DllRegisterServer      @4
 +
DllUnregisterServer    @5
 +
</pre>
 +
 +
The functions in the EXPORTS list are for illustration. Replace them with the actual exported functions from MYDLL. Alternatively, use [http://www.digitalmars.com/ctg/implib.html implib]. Here's an example of a simple DLL with a function print() which prints a string:
 +
 +
=== mydll.d: ===
 +
<syntaxhighlight lang="D">
 +
module mydll;
 +
import std.c.stdio;
 +
export void dllprint() { printf("hello dll world\n"); }
 +
</syntaxhighlight>
 +
 +
Note: We use '''printf'''s in these examples instead of '''writefln''' to make the examples as simple as possible.
 +
 +
=== mydll.def: ===
 +
 +
<pre>
 +
LIBRARY "mydll.dll"
 +
EXETYPE NT
 +
SUBSYSTEM WINDOWS
 +
CODE SHARED EXECUTE
 +
DATA WRITE
 +
</pre>
 +
 +
Put the code above that contains '''DllMain()''' into a file '''dll.d'''. Compile and link the dll with the following command:
 +
 +
<pre>
 +
C:>dmd -ofmydll.dll -L/IMPLIB mydll.d dll.d mydll.def
 +
C:>
 +
</pre>
 +
 +
which will create mydll.dll and mydll.lib. Now for a program, test.d, which will use the dll:
 +
 +
=== test.d: ===
 +
<syntaxhighlight lang="D">
 +
import mydll;
 +
 +
int main()
 +
{
 +
  mydll.dllprint();
 +
  return 0;
 +
}
 +
</syntaxhighlight>
 +
 +
Create an interface file mydll.di that doesn't have the function bodies.
 +
 +
=== mydll.di: ===
 +
<syntaxhighlight lang="D">
 +
export void dllprint();
 +
</syntaxhighlight>
 +
 +
Compile and link with the command:
 +
 +
<pre>
 +
C:>dmd test.d mydll.lib
 +
C:>
 +
</pre>
 +
 +
and run:
 +
 +
<pre>
 +
C:>test
 +
hello dll world
 +
C:>
 +
</pre>
 +
 +
=== Memory Allocation ===
 +
D DLLs use garbage collected memory management. The question is what happens when pointers to allocated data cross DLL boundaries? If the DLL presents a C interface, one would assume the reason for that is to connect with code written in other languages. Those other languages will not know anything about D's memory management. Thus, the C interface will have to shield the DLL's callers from needing to know anything about it.
 +
 +
There are many approaches to solving this problem:
 +
 +
* Do not return pointers to D gc allocated memory to the caller of the DLL. Instead, have the caller allocate a buffer, and have the DLL fill in that buffer.
 +
* Retain a pointer to the data within the D DLL so the GC will not free it. Establish a protocol where the caller informs the D DLL when it is safe to free the data.
 +
* Notify the GC about external references to a memory block by calling GC.addRange.
 +
* Use operating system primitives like VirtualAlloc() to allocate memory to be transferred between DLLs.
 +
* Use std.c.stdlib.malloc() (or another non-gc allocator) when allocating data to be returned to the caller. Export a function that will be used by the caller to free the data.
 +
 +
== COM Programming ==
 +
 +
COM interfaces are all derived from '''std.c.windows.com.IUnknown'''.
 +
 +
See [[COM Programming]] for more details.
 +
 +
== D code calling D code in DLLs ==
 +
Having DLLs in D be able to talk to each other as if they were statically linked together is, of course, very desirable as code between applications can be shared, and different DLLs can be independently developed.
 +
 +
The underlying difficulty is what to do about garbage collection (gc). Each EXE and DLL will have their own gc instance. When dynamically loading, it is important to load the dll with `Runtime.loadLibrary` instead of the system `LoadLibrary` call to ensure the druntime functions are called and the dll must use the druntime dll helper functions (e.g. mixin SimpleDllMain), the same as the C interface example.
 +
 +
----
 +
 +
[[Category::Windows]]
 +
[[Category::DLL]]
 +
[[Category::know-how]]
 +
[[Category::Development]]
 +
[[Category:HowTo]]

Revision as of 11:49, 2 May 2021

DLLs (Dynamic Link Libraries) are one of the foundations of system programming for Windows. The D programming language enables the creation of several different types of DLLs.

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.

Compiling a DLL

Use the -shared switch to tell the compiler that the generated code is to be put into a DLL. Code compiled for an EXE file will use the optimization assumption that _tls_index==0. Such code in a DLL will crash.

DLLs with a C Interface

A DLL presenting a C interface can connect to any other code in a language that supports calling C functions in a DLL.

DLLs can be created in D in roughly the same way as in C. A DllMain() is required, but you can use the mixin template SimpleDllMain to insert it:

import core.sys.windows.windows;
import core.sys.windows.dll;

mixin SimpleDllMain;

Notes:

  • Under the hood, this DllMain simply forwards to the appropriate helper functions found in core.sys.windows.dll. These setup the runtime, create thread objects for interaction with the garbage collector and initialize thread local storage data.
  • The DLL does not share its runtime or memory with other DLLs.
  • The presence of DllMain() is recognized by the compiler causing it to emit a reference to __acrtused_dll and the phobos.lib runtime library.

You must export any functions you want the user to access. Do this by using the export keyword in your code.

Alternatively, you can link with a .def (Module Definition File) along the lines of:

LIBRARY         MYDLL
DESCRIPTION     'My DLL written in D'

EXETYPE		NT
CODE            PRELOAD DISCARDABLE
DATA            WRITE

EXPORTS
		DllGetClassObject       @2
		DllCanUnloadNow         @3
		DllRegisterServer       @4
		DllUnregisterServer     @5

The 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:

mydll.d:

module mydll;
import std.c.stdio;
export void dllprint() { printf("hello dll world\n"); }

Note: We use printfs in these examples instead of writefln to make the examples as simple as possible.

mydll.def:

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

Put the code above that contains DllMain() into a file dll.d. Compile and link the dll with the following command:

C:>dmd -ofmydll.dll -L/IMPLIB mydll.d dll.d mydll.def
C:>

which will create mydll.dll and mydll.lib. Now for a program, test.d, which will use the dll:

test.d:

import mydll;

int main()
{
   mydll.dllprint();
   return 0;
}

Create an interface file mydll.di that doesn't have the function bodies.

mydll.di:

export void dllprint();

Compile and link with the command:

C:>dmd test.d mydll.lib
C:>

and run:

C:>test
hello dll world
C:>

Memory Allocation

D DLLs use garbage collected memory management. The question is what happens when pointers to allocated data cross DLL boundaries? If the DLL presents a C interface, one would assume the reason for that is to connect with code written in other languages. Those other languages will not know anything about D's memory management. Thus, the C interface will have to shield the DLL's callers from needing to know anything about it.

There are many approaches to solving this problem:

  • Do not return pointers to D gc allocated memory to the caller of the DLL. Instead, have the caller allocate a buffer, and have the DLL fill in that buffer.
  • Retain a pointer to the data within the D DLL so the GC will not free it. Establish a protocol where the caller informs the D DLL when it is safe to free the data.
  • Notify the GC about external references to a memory block by calling GC.addRange.
  • Use operating system primitives like VirtualAlloc() to allocate memory to be transferred between DLLs.
  • Use std.c.stdlib.malloc() (or another non-gc allocator) when allocating data to be returned to the caller. Export a function that will be used by the caller to free the data.

COM Programming

COM interfaces are all derived from std.c.windows.com.IUnknown.

See COM Programming for more details.

D code calling D code in DLLs

Having DLLs in D be able to talk to each other as if they were statically linked together is, of course, very desirable as code between applications can be shared, and different DLLs can be independently developed.

The underlying difficulty is what to do about garbage collection (gc). Each EXE and DLL will have their own gc instance. When dynamically loading, it is important to load the dll with `Runtime.loadLibrary` instead of the system `LoadLibrary` call to ensure the druntime functions are called and the dll must use the druntime dll helper functions (e.g. mixin SimpleDllMain), the same as the C interface example.


Windows DLL know-how Development