forked from dlang/dlang.org
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdll.dd
632 lines (514 loc) · 15.8 KB
/
dll.dd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
Ddoc
$(D_S Writing Win32 DLLs in D,
$(P 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.
)
$(P For background information on what DLLs are and how they work
Chapter 11 of Jeffrey Richter's book
$(LINK2 http://www.amazon.com/exec/obidos/ASIN/1572315482/classicempire,
Advanced Windows) is indispensible.
)
$(P This guide will show how to create DLLs of various types with D.)
$(UL
$(LI <a href="#Cinterface">DLLs with a C interface</a>)
$(LI <a href="#com">DLLs that are COM servers</a>)
$(LI <a href="#Dcode">D code calling D code in DLLs</a>)
)
<h2><a name="Cinterface">DLLs with a C Interface</a></h2>
$(P A DLL presenting a C interface can connect to any other code
in a language that supports calling C functions in a DLL.
)
$(P DLLs can be created in D in roughly the same way as in C.
A $(TT DllMain())
is required, looking like:
)
--------------------------------
import std.c.windows.windows;
import core.sys.windows.dll;
__gshared HINSTANCE g_hInst;
extern (Windows)
BOOL DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved)
{
switch (ulReason)
{
case DLL_PROCESS_ATTACH:
g_hInst = hInstance;
dll_process_attach( hInstance, true );
break;
case DLL_PROCESS_DETACH:
dll_process_detach( hInstance, true );
break;
case DLL_THREAD_ATTACH:
dll_thread_attach( true, true );
break;
case DLL_THREAD_DETACH:
dll_thread_detach( true, true );
break;
}
return true;
}
-------------------------------
$(P Notes:)
$(UL
$(LI DllMain simply forwards to the appropriate helper functions. These setup
the runtime, create thread objects for interaction with the garbage collector
and initialize thread local storage data.)
$(LI The DLL does not share its runtime or memory with other DLLs.)
$(LI The first boolean argument to the dll-helper functions specify whether all threads
should be controlled by the garbage collector. You might need more control over
this behaviour if there are threads in the process that must not be suspended.
In this case pass false to disable the automatic handling of all threads.)
$(LI The presence of $(TT DllMain()) is recognized by the compiler
causing it to emit a reference to
$(LINK2 http://www.digitalmars.com/ctg/acrtused.html, __acrtused_dll)
and the $(TT phobos.lib) runtime library.)
)
Link with a .def
(<a href="http://www.digitalmars.com/ctg/ctgDefFiles.html">Module Definition File</a>)
along the lines of:
$(MODDEFFILE
LIBRARY MYDLL
DESCRIPTION 'My DLL written in D'
EXETYPE NT
CODE PRELOAD DISCARDABLE
DATA WRITE
EXPORTS
DllGetClassObject @2
DllCanUnloadNow @3
DllRegisterServer @4
DllUnregisterServer @5
)
$(P The functions in the EXPORTS list are for illustration.
Replace them with the actual exported functions from MYDLL.
Alternatively, use
$(LINK2 http://www.digitalmars.com/ctg/implib.html, implib).
Here's an example of a simple DLL with a function print()
which prints a string:
)
<h4>mydll.d:</h4>
-------------------------------
module mydll;
import std.c.stdio;
export void dllprint() { printf("hello dll world\n"); }
-------------------------------
$(P Note: We use $(CODE printf)s in these examples
instead of $(CODE writefln)
to make the examples as
simple as possible.)
<h4>mydll.def:</h4>
$(MODDEFFILE
LIBRARY "mydll.dll"
EXETYPE NT
SUBSYSTEM WINDOWS
CODE SHARED EXECUTE
DATA WRITE
)
$(P Put the code above that contains $(CODE DllMain()) into a file
$(TT dll.d).
Compile and link the dll with the following command:
)
$(CONSOLE
C:>dmd -ofmydll.dll -L/IMPLIB mydll.d dll.d mydll.def
C:>
)
$(P which will create mydll.dll and mydll.lib.
Now for a program, test.d, which will use the dll:
)
<h4>test.d:</h4>
-------------------------------
import mydll;
int main()
{
mydll.dllprint();
return 0;
}
-------------------------------
$(P Create an interface file mydll.di that doesn't have the function bodies:)
<h4>mydll.di:</h4>
-------------------------------
export void dllprint();
-------------------------------
Compile and link with the command:
$(CONSOLE
C:>dmd test.d mydll.lib
C:>
)
and run:
$(CONSOLE
C:>test
hello dll world
C:>
)
<h3>Memory Allocation</h3>
$(P 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.
)
$(P There are many approaches to solving this problem:)
$(UL
$(LI 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.)
$(LI 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.)
$(LI Notify the GC about external references to a memory block by
calling GC.addRange.)
$(LI Use operating system primitives like VirtualAlloc() to allocate
memory to be transferred between DLLs.)
$(LI 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.)
)
<h2><a name="com">COM Programming</a></h2>
Many Windows API interfaces are in terms of COM (Common Object Model)
objects (also called OLE or ActiveX objects). A COM object is an object
who's first field is a pointer to a vtbl[], and the first 3 entries
in that vtbl[] are for QueryInterface(), AddRef(), and Release().
<p>
For understanding COM, Kraig Brockshmidt's
<a href="http://www.amazon.com/exec/obidos/ASIN/1556158432/classicempire">
Inside OLE</a>
is an indispensible resource.
<p>
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.
<p>
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().
<p>
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.
<h2><a name="Dcode">D code calling D code in DLLs</a></h2>
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.
<p>
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
separate DLL just for the gc.
<p>
The example will show both how to statically load a DLL, and
to dynamically load/unload it.
<p>
Starting with the code for the DLL, mydll.d:
-------------------------------
/*
* MyDll demonstration of how to write D DLLs.
*/
import core.runtime;
import std.c.stdio;
import std.c.stdlib;
import std.string;
import std.c.windows.windows;
HINSTANCE g_hInst;
extern (C)
{
void gc_setProxy(void* p);
void gc_clrProxy();
}
extern (Windows)
BOOL $(B DllMain)(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved)
{
switch (ulReason)
{
case DLL_PROCESS_ATTACH:
printf("DLL_PROCESS_ATTACH\n");
Runtime.initialize();
break;
case DLL_PROCESS_DETACH:
printf("DLL_PROCESS_DETACH\n");
Runtime.terminate();
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 $(B MyDLL_Initialize)(void* gc)
{
printf("MyDLL_Initialize()\n");
gc_setProxy(gc);
}
export void $(B MyDLL_Terminate)()
{
printf("MyDLL_Terminate()\n");
gc_clrProxy();
}
$(B static this)()
{
printf("static this for mydll\n");
}
$(B static ~this)()
{
printf("static ~this for mydll\n");
}
/* --------------------------------------------------------- */
class $(B MyClass)
{
char[] $(B concat)(char[] a, char[] b)
{
return a ~ " " ~ b;
}
void $(B free)(char[] s)
{
delete s;
}
}
export MyClass $(B getMyClass)()
{
return new MyClass();
}
-------------------------------
<dl>
<dt>$(B DllMain)
<dd>This is the main entry point for any D DLL. It gets called
by the C startup code
(for DMC++, the source is $(TT \dm\src\win32\dllstart.c)).
The $(B printf)'s are placed there so one can trace how it gets
called.
Notice that the initialization and termination code seen in
the earlier DllMain sample code is in this version as well.
This is because the same DLL should be usable from both C and
D programs, so the same initialization process should work
for both.
<p>
<dt>$(B MyDLL_Initialize)
<dd>
When the DLL is dynamically linked via $(B Runtime.loadLibrary)()
the runtime makes sure that any initialization steps required
by the D program are executed after the library is loaded. If
the library is statically linked, this routine is not called by
the program, so to make sure the DLL is initialized properly we
have to do some of the work ourselves. And because the library
is being statically linked, we need a function specific to this
DLL to perform the initialization.
This function takes one argument, a handle to the
caller's gc. We'll see how that handle is obtained later.
To pass this handle to the runtime and override the DLL's built-in
gc we'll call $(B gc_setProxy)().
The function is $(B export)ed as that is how a function is made
visible outside of a DLL.
<p>
<dt>$(B MyDLL_Terminate)
<dd>Correspondingly, this function terminates the DLL, and is
called prior to unloading it.
It has only one job: informing the runtime that the DLL will
no longer be using the caller's gc via $(B gc_clrProxy)().
This is critical, as the DLL will be unmapped from memory,
and if the gc continues to scan its data areas it will cause
segment faults.
<p>
<dt>$(B static this, static ~this)
<dd>These are examples of the module's static constructor
and destructor,
here with a print in each to verify that they are running
and when.
<p>
<dt>$(B MyClass)
<dd>This is an example of a class that can be exported from
and used by the caller of a DLL. The $(B concat) member
function allocates some gc memory, and $(B free) frees gc
memory.
<p>
<dt>$(B getMyClass)
<dd>An exported factory that allocates an instance of $(B MyClass)
and returns a reference to it.
<p>
</dl>
To build the $(TT mydll.dll) DLL:
$(OL
$(LI$(B $(TT dmd -c mydll -g))
<br>Compiles $(TT mydll.d) into $(TT mydll.obj).
$(B -g) turns on debug info generation.
)
$(LI $(B $(TT dmd mydll.obj mydll.def -g -L/map))
<br>Links $(TT mydll.obj) into a DLL named $(TT mydll.dll).
$(TT mydll.def) is the
<a href="http://www.digitalmars.com/ctg/ctgDefFiles.html">Module Definition File</a>,
and has the contents:
$(MODDEFFILE
LIBRARY MYDLL
DESCRIPTION 'MyDll demonstration DLL'
EXETYPE NT
CODE PRELOAD DISCARDABLE
DATA PRELOAD SINGLE
)
$(B -g) turns on debug info generation, and
$(B -L/map) generates a map file $(TT mydll.map).
)
$(LI $(B $(TT implib /noi /system mydll.lib mydll.dll))
<br>Creates an
<a href="http://www.digitalmars.com/ctg/implib.html">import library</a>
$(TT mydll.lib) suitable
for linking in with an application that will be statically
loading $(TT mydll.dll).
)
)
$(P Here's $(TT test.d), a sample application that makes use of
$(TT mydll.dll). There are two versions, one statically binds to
the DLL, and the other dynamically loads it.
)
-------------------------------
import core.runtime;
import std.stdio;
import std.gc;
import mydll;
//version=DYNAMIC_LOAD;
version (DYNAMIC_LOAD)
{
import std.c.windows.windows;
alias MyClass function() getMyClass_fp;
int main()
{ HMODULE h;
FARPROC fp;
getMyClass_fp getMyClass;
MyClass c;
printf("Start Dynamic Link...\n");
h = cast(HMODULE) Runtime.loadLibrary("mydll.dll");
if (h is null)
{
printf("error loading mydll.dll\n");
return 1;
}
fp = GetProcAddress(h, "D5mydll10getMyClassFZC5mydll7MyClass");
if (fp is null)
{ printf("error loading symbol getMyClass()\n");
return 1;
}
getMyClass = cast(getMyClass_fp) fp;
c = (*getMyClass)();
foo(c);
if (!Runtime.unloadLibrary(h))
{ printf("error freeing mydll.dll\n");
return 1;
}
printf("End...\n");
return 0;
}
}
else
{ // static link the DLL
extern (C)
{
void* gc_getProxy();
}
int main()
{
printf("Start Static Link...\n");
MyDLL_Initialize(gc_getProxy());
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;
}
-------------------------------
$(P Let's start with the statically linked version, which is simpler.
It's compiled and linked with the command:
)
$(CONSOLE
C:>dmd test mydll.lib -g
)
$(P Note how it is linked with $(TT mydll.lib), the import library
for $(TT mydll.dll).
The code is straightforward, it initializes $(TT mydll.lib) with
a call to $(B MyDLL_Initialize)(), passing the handle
to $(TT test.exe)'s gc.
Then, we can use the DLL and call its functions just as if
it were part of $(TT test.exe). In $(B foo)(), gc memory
is allocated and freed both by $(TT test.exe) and $(TT mydll.dll).
When we're done using the DLL, it is terminated with
$(B MyDLL_Terminate)().
)
$(P Running it looks like this:)
$(CONSOLE
C:>test
DLL_PROCESS_ATTACH
Start Static Link...
MyDLL_Initialize()
static this for mydll
Hello world!
MyDLL_Terminate()
static ~this for mydll
End...
C:>
)
$(P The dynamically linked version is a little harder to set up.
Compile and link it with the command:
)
$(CONSOLE
C:>dmd test -version=DYNAMIC_LOAD -g
)
$(P The import library $(TT mydll.lib) is not needed.
The DLL is loaded with a call to
$(B Runtime.loadLibrary)(),
and each exported function has to be retrieved via
a call to
$(B GetProcAddress)().
An easy way to get the decorated name to pass to $(B GetProcAddress)()
is to copy and paste it from the generated $(TT mydll.map) file
under the $(B Export) heading.
Once this is done, we can use the member functions of the
DLL classes as if they were part of $(TT test.exe).
When done, release the DLL with
$(B Runtime.unloadLibrary)().
)
$(P Running it looks like this:)
$(CONSOLE
C:>test
Start Dynamic Link...
DLL_PROCESS_ATTACH
static this for mydll
Hello world!
static ~this for mydll
DLL_PROCESS_DETACH
End...
C:>
)
)
Macros:
TITLE=Writing Win32 DLLs
WIKI=DLLs
CATEGORY_HOWTOS=$0