如何在D中创建动态库?

时间:2013-12-15 19:00:22

标签: dll d dynamic-library

我想在D中创建一个动态库(跨平台),所以我做了一些谷歌搜索。过了一段时间,我找到了this页面。我对编写,编译甚至链接DLL的复杂程度感到震惊。难道没有像在C中那样创建共享库的统一方法吗? (只是省略主函数并将一些标志传递给链接器)

1 个答案:

答案 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。不像它应该/应该那么漂亮,但它的工作原理。对我来说至少。