Dll 如何在D中创建动态库?

Dll 如何在D中创建动态库?,dll,d,dynamic-library,Dll,D,Dynamic Library,我想用D创建一个动态库(跨平台),所以我在谷歌上搜索了一下。过了一段时间,我找到了佩奇。我对编写、编译甚至链接到DLL的过程中有多么复杂感到震惊。难道没有一种统一的方法可以像在C中那样创建共享库吗?(只需省略主函数并将一些标志传递给链接器)好吧,我决定今天花一些时间来处理这个问题,我有点能用,至少如果主程序也是用D编写的话(在Linux上,我认为它在Windows上也可以从C运行。原因是我没有在中链接到phobos。因此在D中,它依赖于exe来表示这些符号。我想,tbh我不知道这里到底发生了什么

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

好吧,我决定今天花一些时间来处理这个问题,我有点能用,至少如果主程序也是用D编写的话(在Linux上,我认为它在Windows上也可以从C运行。原因是我没有在中链接到phobos。因此在D中,它依赖于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,它只调用druntime函数。我认为main应该至少可以作为一个混入,或者甚至是全自动的,因为它是纯样板

和客户端代码:

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和Stand告诉它构建.SO。我做了空白DeFultLIB,因为没有它,在运行时加载DLL失败了“符号已经定义”的错误。 构建客户机非常简单:

dmd testdllc.d
请注意,文件中有pragma(lib)自动链接到-ldl选项。运行它并获取一些hello!顺便说一句,确保两者都在同一目录中,因为这会在加载程序中加载/

现在,让我们在Windows上构建

dmd -oftestdll.dll -shared testdll.d testdll.def
告诉它输出我们的dll,使用-shared让它知道,然后另一件事就是def文件,如这里所述

以下是该文件的内容:

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)或did.mangleof,还有一个导入的东西。那里有各种各样的选项,非常简单,我只是想保持它的简单,使测试更容易关注。“lol”是一个比“_D7testdll3lolFZv”或任何损坏的名称…(天哪,我用手正确地损坏了它!有时我认为我写的D lol太多了)是的,这也很难用肉眼来完成。注意:在Windows上,如果这样做,.def文件可能必须去掉前导下划线


无论如何,是的,这为我和一个程序成功地加载和使用了一个工作的dll/so。虽然没有它可能/应该的那么漂亮,但它工作得很好。至少对我来说。

主要的复杂性来自需要在运行时链接和断开链接的GC。C不需要这样做default@ratchetfreak这不是自动完成的原因吗在druntime中自动完成这一切的实现是不完整的。我开始写一个答案来说明如何使用它,发现它没有链接,因为函数还没有完成……Windows上的模块定义文件也有点痛苦,因为导出关键字在那里没有多大帮助。关于修复的争论正在缓慢进行现在,最好的方法是做大量的手工工作,调用C dynamic lib函数自己加载库…实现实际上是不完整的,但从某种意义上说,它只在Linux上工作。你可以在那里工作(请参阅)。我想事情正在发生变化,我们将在今后的某个时候看到更好的支持。是的,主要问题是“跨平台”部分。为了让动态库“正常工作”,已经做了很多工作"但到目前为止,它主要关注Linux。可能根本不需要.def文件。我只是用损坏的名称做了一些测试,它成功地加载了它。我认为我犯的错误是忘记了GetProcAddress中的前导下划线。我还计划向druntime提交一个pull请求,该请求可以提供一个瓶装dllmain,其中应该真的减少麻烦。