【问题标题】:Passing StringBuilder to DLL function expecting char pointer将 StringBuilder 传递给需要 char 指针的 DLL 函数
【发布时间】:2012-07-15 12:48:54
【问题描述】:

我正在尝试与在 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 的内存设置为 0。我试图在 C# 中找到一个等价物,但也许这是 StringBuilder 的问题?如果我的问题不是很清楚,我只想能够将一个字符串传递给我的函数,让函数填充它,然后打印出来。字符串在 C# 中是不可变的,并且不存在好的指针选项,这就是我尝试 StringBuilder 的原因。

【问题讨论】:

  • 你试过将字符串声明为 ref string z 吗?
  • 你应该尝试使用string
  • 我尝试使用ref string z,然后将字符串“”作为引用传递,它打印出空行。

标签: c# c++ pointers dllimport


【解决方案1】:

在 .NET 中,字符串(和 StringBuilders)是 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] 属性是多余的(这是默认值),但是将它放在那里可以明确表示您知道您需要 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);
}

【讨论】:

  • 除了将 jibberish 更改为不同的 jibberish 之外,这并没有做太多事情。我尝试了数十种不同 CharSet 的组合,将 [out] 换成 ref 等,但没有任何效果。出于某种原因,我认为这与内存问题有关。我记得当我用 C++ 编写解决方案时,我一直在胡言乱语,直到我在分配的内存块上使用 ZeroMemory()。 C#中有相关的方法吗?
  • @Atlos 因此,在您的情况下,您想要同时编组 In 和 Out。您是否尝试删除 Out 属性(参考不正确)。可能是编组器对编组 StringBuilder 的“聪明”(不复制所有零字节),尝试使用 byte[] 并在结果数组上使用 Encoding.ASCII.GetString 以获取 String。
  • 谢谢!最后一次更新通过一次修改解决了我的问题。 ChannelName 实际上返回了一个错误代码,所以我现在只是将length 替换为一个固定的数字。对不起我可怕的变量命名,当我匆忙写很多东西然后在我有工作解决方案后重构时,我会这样做。无论如何,我相信你所做的事情是有道理的。
  • @Atlos 不客气。使用仅接受数组的 Encoding.ASCII.GetString 的重载。如果您将偏移量和长度传递给它,则返回的字符串将具有精确的长度,末尾有一堆零(嵌入的零在 C# 中是合法的)。仅数组重载将在找到第一个零时截断字符串。
  • @Atlos 实际上,这不是真的。如果您不指定 offset,length,Encoding.GetString 将返回数组长度的字符串。您可以使用.TrimEnd((char)0) 截断尾随零。
猜你喜欢
  • 2021-09-07
  • 2011-11-18
  • 2016-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-06
相关资源
最近更新 更多