我想在D中创建一个动态库(跨平台),所以我做了一些谷歌搜索。过了一段时间,我找到了this页面。我对编写,编译甚至链接DLL的复杂程度感到震惊。难道没有像在C中那样创建共享库的统一方法吗? (只是省略主函数并将一些标志传递给链接器)
答案 0 :(得分:6)
好吧,我今天决定花一些时间来搞乱这个问题,而且我有点可行,至少如果主程序也是用D语言写的(在Linux上,我认为它也可以在Windows上运行。原因是我没有链接到D中的.so中的phobos,所以它依赖于那些符号的exe。我想,我不知道到底发生了什么,也许它会更好用如果我也使用共享的phobos lib)
无论如何,首先,让我们抛出一些代码。
这是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,它只调用druntime函数。我认为主要应该至少可以作为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有不同的代码,尽管它非常相似。如我们在评论中提到的那样,应该尽快开始解决这个问题。
编译命令不是太糟糕但有点奇怪。 Linux第一:
dmd -fPIC -shared testdll.d -defaultlib= # builds the dll
PIC和共享告诉它构建.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
如果一切顺利的话,运行它并得到一些hellos。
现在,为什么我在客户端中使用functype别名?比进行其他铸造等更容易,并且它很好地外部(C)。
为什么lol函数extern(C)首先?这样它在GetProcAddress / dlsym中使用的名称更容易。可能还有pragma(mangle)或带有导入内容的.mangleof。那里有各种各样的选项,相当简单,我只想保持简单,让测试更容易关注。 “lol”是一个比“_D7testdll3lolFZv”更简单的名字,或者任何被破坏的名字......(OMG我用手正确地破坏了它!有时候我觉得我写了太多D lol)并且是的,它也有效,它只是更难通过眼球来做。注意:在Windows上,如果以这种方式执行,.def文件可能必须不使用前导下划线。
无论如何,是的,这为我和一个程序加载并成功使用它做了一个工作的DLL。不像它应该/应该那么漂亮,但它的工作原理。对我来说至少。