将 StringBuilder 传递给期望 char 指针的 DLL 函数

5

我想与一个由Delphi创建的DLL库进行交互。在C++中,我可以很好地调用它:

for(int y = 1; y <= 12; y++)
{
    char * chanName = (char *) malloc(21); 
    memset(chanName,0,21);
    channelName(y,20,chanName);
    ...
}

channelName是类型定义,定义如下:typedef int (CALLBACK* ChannelName)(int,int,char*);

现在我正在尝试用C#完成相同的操作。我搜索并发现StringBuilder通常被用作DLL函数的char指针。这里是我声明函数的方式:

[DllImport("myDLL.dll")]
public static extern int ChannelName(int x, int y, StringBuilder z);

这是我尝试调用它的方式:

for (int x = 0; x < 12; x++)
{
    StringBuilder b = new StringBuilder(100);
    DLLInterface.ChannelName(x+1, b.Length, b);
    Console.WriteLine(b.ToString());
}

这只是让控制台打印出无意义的字符,例如:

☺ §
☺ î☺8f9
☺ î☺8f9
☺ î☺8f9
☺ î☺8f9
☺ î☺8f9
☺ î☺8f9
☺ î☺8f9
☺ î☺8f9
☺ î☺8f9
☺ î☺8f9
☺ î☺8f9

我记得在C++中遇到了类似的问题,所以我用memset将我malloc的内存清零。我试图在C#中找到一个相应的方法,但也许这是与StringBuilder有关的问题?如果我的问题不是很清楚,我只是想能够将字符串传递给我的函数,让函数填充它,然后打印出来。在C#中,字符串是不可变的,没有好的指针选项,这就是为什么我正在尝试使用StringBuilder的原因。


你尝试过将字符串声明为“ref string z”吗? - AlexDev
1
你应该只尝试使用 string - Ajay
我尝试使用 ref string z,然后将字符串 "" 作为 ref 传递进去,结果打印出了空行。 - telkins
1个回答

7
在.NET中,字符串(和StringBuilder)是16位Unicode字符。我猜你的本地函数处理8位ASCII字符。当进行封送时,您需要告诉Marshaller如何转换字符。将您的DllImport属性更改为以下内容:
[DllImport("myDLL.dll", CharSet=CharSet.Ansi)]
public static extern int ChannelName(int x, int y, [Out] StringBuilder z);

更新

同时,您应该在StringBuilder上指定[Out]属性,以便Marshaller仅在传递内容时进行编组,因为您在进入时未传递任何内容。

再次更新

[In,Out]属性是冗余的(这是默认值),但将其放置在那里可以明确表示您知道您需要进行输入和输出复制。

[DllImport("myDLL.dll")]
private static extern int ChannelName(int x, int y, [In,Out] byte[] z);
public static int ChannelName(int x, int y, out string result)
{
    byte[] z = new byte[100];
    int ret = ChannelName(x, y, z);
    result = Encoding.ASCII.GetString(z);
    return ret;
}

再次更新

看起来(名称不太合适的)'y'参数是传递给char *缓冲区的长度,我猜它返回写入缓冲区中的字符数。如果是这样的话,我会用更自然的C#方式来包装这个调用:

[DllImport("myDLL.dll")]
private static extern int ChannelName(int x, int y, [In, Out] byte[] z);

public static string ChannelName(int x)
{
    byte[] z = new byte[100];
    int length = ChannelName(x, z.Length, z);
    return Encoding.ASCII.GetString(z, 0, length);
}

这并没有做太多事情,除了将无意义的东西变成不同的无意义的东西。我尝试了许多不同CharSet的组合,用ref替换[out]等等,但都没有起作用。出于某种原因,我认为这与内存问题有关。我记得在C++中编写解决方案时也会出现无意义的东西,直到我在分配的内存块上使用ZeroMemory()。在C#中是否有相关方法? - telkins
@Atlos 所以在你的情况下,你想要编组In和Out。你尝试过删除Out属性(ref不正确)。可能的情况是编组器在编组StringBuilder时是“聪明的”(不复制所有零字节),尝试使用byte[]代替,并在生成的数组上使用Encoding.ASCII.GetString来获取字符串。 - Tergiver
谢谢!最后一次更新通过一个修改解决了我的问题。ChannelName 实际上返回了一个错误代码,所以我现在只是用一个固定的数字来替换 length。对不起,我的变量命名很糟糕,因为我在匆忙写很多东西后再重构。无论如何,我相信你所做的事情是有意义的。 - telkins
@Atlos 不客气。使用只接受数组作为参数的 Encoding.ASCII.GetString 重载方法。如果你给它传递偏移量和长度,返回的字符串将具有确切的长度,在末尾会有许多零(嵌入式零在 C# 中是合法的)。只接受数组作为参数的重载方法将在找到第一个零时截断字符串。 - Tergiver
@Atlos 实际上,这不是真的。如果您没有指定 offset 和 length,Encoding.GetString 返回数组长度的字符串。您可以使用 .TrimEnd((char)0) 截断末尾的零。 - Tergiver
我使用了 DllInterface.ChannelName(x + 1).Trim(),它运行良好。再次感谢。 - telkins

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