与D接口的正确交互:返回一个结构体数组

8

这个问题是关于“新”的D语言编译器的,具体为:DMD32 D Compiler v2.068.2

如果您不需要细节,请跳过下面的内容直接看问题。

在visual studio(我使用的是v2010)中创建一个新项目,选择D,然后选择动态库(Dynamic Library)

当项目创建完成后,在解决方案资源管理器中会出现两个文件:

  • dllmain.d
  • dll.def

保留.def文件不变,通过向dllmain.d添加一些新函数并以“:”为前缀,我已经理解了这一点。

extern (Windows) export  

将导出该函数,可以从 c# 调用它,但尚未尝试与 CC++ 一起使用。

顺便说一下,除非您知道自己在做什么,否则不要触碰任何现有代码。

因此,下面的代码按预期工作。

extern (Windows) export uint D_mathPower(uint p)
{     
    return p * p; 
}

通过以下C#签名调用它:

    [DllImport(@"pathTo...\DynamicLib1.dll", CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurity]
    public static extern uint D_mathPower(uint p);

我可以轻松地按以下方式使用它:
uint powD = D_mathPower(5);

我的问题是

如何返回一个结构体的数组(最好是成本最低的方式)?

struct dpack{ char* Name; uint Id; }

我尝试使用char[]char*,但都没有成功。
这是我目前的代码。
extern (Windows) export
dpack[] D_getPacks(uint size)
{
    dpack[] rtDpArr = new dpack[size];
    char[] str = "someText".dup;

    for(uint i=0; i<size; i++)
    {

        str[$ - 1] = cast(char)('0' + i % (126 - '0'));
        rtDpArr[i].Id = i;
        rtDpArr[i].Name= str.dup;
    }
   return rtDpArr;
}


void getPacksPtr(uint size, dpack** DpArr)
{
 // this is the signature i have successfully implemented via c++
}

将D数组返回给另一种语言可能会起作用,但通常不会,因为ABI细节不一定匹配。尝试使用指针和长度创建自己的结构类型以进行互操作,或者像这样做:getArray(size_t* lengthPtr, dpack** ptrPtr) { *lengthPtr = array.length; *ptrPtr = array.ptr; } - Adam D. Ruppe
在尝试使用D DLL之前,您应该练习创建一个C DLL并在C#中使用它。找出如何从C#中使用返回“数组”的C函数,然后在D中使用相同的技术。C#不知道D如何存储切片,因此您无法直接使用它们。 - Colonel Thirty Two
2个回答

1
由于D数组具有特殊的布局,您应该返回指向第一个项的指针。然后在C#中,您可以通过每8个字节读取一次(这与dpack.sizeof匹配)从基本指针转换每个项,因为您已经知道计数:
struct dpack{ immutable(char)* Name; uint Id; }

extern (Windows) export
void* D_getPacks(uint count)
{
    dpack[] rtDpArr = new dpack[count];
    char[] str = "someText".dup;

    import std.string;
    for(uint i=0; i<count; i++)
    {
        rtDpArr[i].Id = i;
        // add a trailing '\0'
        rtDpArr[i].Name = toStringz(str);
    }
    // pointer to the first item
    return rtDpArr.ptr;
}

另外,要转换成.Name成员,必须添加一个终止符,否则无法知道字符串的长度。这是通过 std.string.toStringz 完成的,该函数将在字符串末尾添加空字符。然后可以将 char* Name 成员转换为由具有C接口的dll函数提供的通常字符串。

嘿,感谢@Nested type提供的代码。我已经尝试了很多变化,但没有包括std.string。在找到答案后,我尝试了你的代码,并在与c ++实现进行基准测试后,使用std.string进行了测试,性能下降了0.75%,与char *字段相比。至于D语言中最快的代码(到目前为止),就是我发布的那个。它仍然比C ++中最快的方法慢,也只有c ++代码的0.75%,因此C ++是45毫秒,D(char *)是57毫秒,而D(std.string)是79毫秒。 - Raj Felix
奇怪的是,每次调用函数时,内容都不同。元素[0]处的尾巴一次可能是“G”,再次执行它时是“;”,然后又变成了“G”。不稳定,这与malloc没有free有关吗?或者还有其他原因吗? - Raj Felix

0

这是我能够实现的最有效的方式。

extern (Windows) export
void D_getPacksPtr(uint size, dpack** DpArr)
{
    *DpArr = cast(dpack*) malloc(size * dpack.sizeof);
    dpack* curP = *DpArr;
    char[] str = "abcdefghij".dup;
    uint i=0;
    while(i!=size){
        str[$ - 1] = cast(char)('0' + i % (126 - '0'));
        curP.Name = cast(char*)str.dup; curP.Id = i;
        ++i;++curP;
    }
}

    [DllImport(@"PathTo...\DynamicLib1.dll", CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurity]
    public static extern void D_getPacksPtr(uint size, dpack** DPArr);

使用它:

    dpack* outDpack;
    D_getPacksPtr(500000, &outDpack);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接