我想在 D 中创建一个动态库(跨平台),所以我做了一些谷歌搜索。过了一段时间我找到了this页。我对编写、编译甚至链接到 DLL 的复杂程度感到非常震惊。难道没有像在 C 中那样创建共享库的统一方法吗? (只需省略 main 函数并将一些标志传递给链接器)
最佳答案
好吧,我决定今天花一些时间来解决这个问题,我有点东西可以工作,至少如果主程序也是用 D 编写的(在 Linux 上,我认为它也可以在 Windows 上使用 C 语言。原因是我没有在 D 中的 .so 中链接到 phobos,所以它依赖于这些符号的 exe。我想,tbh 我不知道这里到底发生了什么,也许如果我使用它会更好共享的 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 有不同的代码,尽管它非常相似。正如我们在评论中提到的,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 lol)是的,它也有效,只是更难用眼球来做。注意:在 Windows 上,如果您这样做,.def 文件可能必须去掉前导下划线。
无论如何,是的,这为我制作了一个有效的 dll/so 和一个可以成功加载和使用它的程序。不像它可能/应该的那样漂亮,但它有效。至少对我来说。
关于dll - 如何在 D 中创建动态库?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20598427/