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:

mydll2.d:

module mydll;
exportvoid dllprint() { printf("hello dll world\n"); }

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

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 a clone of mydll2.d that doesn't have the function bodies:

mydll.d:

exportvoid 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.

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.

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().

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:

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. 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.

The example will show both how to statically load a DLL, and
to dynamically load/unload it.

This is the main entry point for any D DLL. It gets called
by the C startup code
(for DMC++, the source is \dm\src\win32\dllstart.c).
The 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 isn't there.
This is because the initialization will depend on who is loading
the DLL, and how it is loaded (statically or dynamically).
There isn't much to do here.
The only oddity is the setting of std.c.stdio._fcloseallp to
null. If this is not set to null, the C runtime will flush
and close all the standard I/O buffers (like stdout,
stderr, etc.)
shutting off further output. Setting it to null defers the
responsibility for that to the caller of the DLL.

MyDLL_Initialize

So instead we'll have our own DLL initialization routine so
exactly when it is called can be controlled.
It must be called after the caller has initialized itself,
the Phobos runtime library, and the module constructors
(this would normally be by the time main() was entered).
This function takes one argument, a handle to the
caller's gc. We'll see how that handle is obtained later.
Instead of gc_init() being called to initialize
the DLL's gc, std.gc.setGCHandle() is called and passed the
handle to which gc to use.
This step informs the caller's gc
which data areas of the DLL to scan.
Afterwards follows the call to the _minit() to initialize the
module tables, and _moduleCtor() to run the module constructors.
_moduleUnitTests() is optional and runs the DLL's unit tests.
The function is exported as that is how a function is made
visible outside of a DLL.

MyDLL_Terminate

Correspondingly, this function terminates the DLL, and is
called prior to unloading it.
It has two jobs; calling the DLL's module destructors via
_moduleDtor() and informing the runtime that
the DLL will no longer be using the caller's gc via
std.gc.endGCHandle().
That last step 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.

static this, static ~this

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.

MyClass

This is an example of a class that can be exported from
and used by the caller of a DLL. The concat member
function allocates some gc memory, and free frees gc
memory.

getMyClass

An exported factory that allocates an instance of MyClass
and returns a reference to it.

dmd mydll.obj \dmd\lib\gcstub.obj mydll.def -g -L/mapLinks mydll.obj into a DLL named mydll.dll.
gcstub.obj is not required, but it prevents the bulk
of the gc code from being linked in, since it will not be used
anyway. It saves about 12Kb.
mydll.def is the
Module Definition File,
and has the contents:

Let's start with the statically linked version, which is simpler.
It's compiled and linked with the command:

C:>dmd test mydll.lib -g

Note 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().

The dynamically linked version is a little harder to set up.
Compile and link it with the command:

C:>dmd test -version=DYNAMIC_LOAD -g

The 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().