好吧,我决定今天花一些时间来解决这个问题,而且我有一些可以工作的东西,至少如果主程序也是用 D 编写的(在 Linux 上,我认为它也可以在 Windows 上用 C 语言工作。原因是我没有链接到 D 中的 .so 中的 phobos,所以它依赖于这些符号的 exe。我想,我不知道这里到底发生了什么,也许它会更好如果我也使用了共享的 phobos 库)
不管怎样,首先,让我们写一些代码。
这是 testdll.d,它构建了我们的 dll
module testdll;
import std.stdio;
extern(C)
export void lol() {
import core.stdc.stdio;
printf("hello from C\n");
writeln("hello!");
}
version(Windows)
extern(Windows) bool DllMain(void* hInstance, uint ulReason, void*) {
import std.c.windows.windows;
import core.sys.windows.dll;
switch (ulReason)
{
default: assert(0);
case DLL_PROCESS_ATTACH:
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;
}
您会注意到大部分代码是 WinMain,它只调用运行时函数。我认为 main 至少应该可以作为 mixin 使用,或者甚至可以是全自动的,因为它是纯样板文件。
以及客户端代码:
import core.runtime;
alias extern(C) void function() functype;
version(Posix) {
extern(C) void* dlsym(void*, const char*);
extern(C) void* dlopen(const char*, int);
extern(C) char* dlerror();
pragma(lib, "dl");
} else version(Windows) {
extern(Windows) void* LoadLibraryA(const char* filename);
extern(Windows) void* GetProcAddress(void*, const char*);
}
void main() {
version(Posix) {
auto thing = dlopen("./testdll.so", 2);
if(thing is null) {
import std.conv;
import std.stdio;
writeln(to!string(dlerror()));
return;
}
auto proc = cast(functype) dlsym(thing, "lol");
} else version(Windows) {
auto thing = LoadLibraryA("testdll.dll");
assert(thing !is null);
auto proc = cast(functype) GetProcAddress(thing, "lol");
}
assert(proc !is null);
//import std.stdio; writeln("calling proc");
proc();
}
这对于 Windows 和 Linux 有不同的代码,尽管它们非常相似。正如我们在 cmets 中提到的,druntime 的东西应该尽快开始处理这个问题。
编译命令还不错,但有点奇怪。 Linux优先:
dmd -fPIC -shared testdll.d -defaultlib= # builds the dll
PIC 和 shared 告诉它构建 .so。我做了空白的 defaultlib,因为没有它,在运行时加载 dll 失败并出现“符号已定义”错误。
构建客户端很简单:
dmd testdllc.d
请注意,文件中的 pragma(lib) 会自动与 -ldl 选项链接。运行它并打个招呼!顺便说一句,确保两者都在同一个目录中,因为这会将 ./ 加载到加载器中。
现在,让我们在 Windows 上构建。
dmd -oftestdll.dll -shared testdll.d testdll.def
告诉它输出我们的dll,使用-shared让它知道,然后是def文件,就像这里描述的http://dlang.org/dll.html/dllmain
这些是该文件的内容:
LIBRARY testdll
EXETYPE NT
CODE SHARED EXECUTE
DATA WRITE
EXPORTS
lol
如果不使用 .def 文件,则 dll 将成功构建,但由于未导出,因此无法找到该过程。 (我认为 D 中的 export 关键字应该能够自动执行此操作,绕过 hte .def 文件,并且我相信对此进行了讨论,但据我所知目前是必需的。)
客户端也同样简单:
dmd testdllc.d
如果一切顺利,运行它并打个招呼。
现在,我为什么要在客户端做 functype 别名?比进行其他类型转换等更容易,而且它非常适合 extern(C)。
为什么首先是lol函数extern(C)?只是为了在 GetProcAddress/dlsym 中有一个更容易使用的名称。也可能有 pragma(mangle) 或 .mangleof 与导入的东西。那里有各种各样的选项,相当简单,我只是想保持简单,让测试更容易集中。 “lol”是一个比“_D7testdll3lolFZv”更简单的名称,或者其他任何被破坏的名称......(天哪,我用手正确地破坏了它!有时我认为我写了太多的 D 哈哈),是的,它也能工作,只是更难用眼球来做。注意:在 Windows 上,如果您这样做,.def 文件可能必须去掉前导下划线。
无论如何,是的,这为我和一个程序成功加载和使用它提供了一个工作 dll/so。没有它可以/应该的那么漂亮,但它确实有效。至少对我来说。