从C++ DLL返回字节数组到C#

3

我正在实现一个需要读写串口数据的C++ DLL。该DLL被用于一个C#应用程序中。目前,当我使用C++的读取代码时,无法从C#应用程序中读取数据(没有C#包装器时,读取函数可以正常工作)。

C++代码:

extern "C" __declspec(dllexport) int Read(void *Buffer, unsigned int MaxNbBytes, unsigned int TimeOut_ms)
{
    return uart.Read(Buffer, MaxNbBytes, TimeOut_ms);
}

C# 代码

[DllImport("RS232LIB.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
public static extern int Read(out byte[] bytesRead, int maxNbBytes, int timeOutMs);

var bytes = new byte[4];
Read(out bytes, 4, 10);

在运行这些代码后,我一直收到“System.AccessViolationException”异常。该如何解决这个问题?
备注:我不能使用C# Serial类。我的C++串行函数工作正常。
“uart.Read(void *Buffer, unsigned int MaxNbBytes, unsigned int TimeOut_ms)”参考:
\Buffer:从串行设备读取的字节数组 \MaxNbBytes:允许读取的最大字节数 \TimeOut_ms:在放弃读取之前的超时延迟

我不认为那是你的问题,但在C++部分中你使用了“unsigned int” - 那么C#对应部分不应该使用“uint”吗? - Christoph Fink
这不是问题所在。即使我将硬编码的值设置为长度和超时,我仍然会遇到此异常。 - gr1d3r
我认为您会发现这篇帖子的答案很有帮助:https://dev59.com/u3DYa4cB1Zd3GeqPCaG- - nabuchodonossor
2
那个 C++ 函数编写正确,它实际上并不返回一个数组。它使用你传递的数组,并用字节填充它。你必须删除 out。将 bytes.Length 作为第二个参数传递,这样你就永远不会出错。而且不要忽略返回值,它可能告诉你它复制了多少字节到你的数组中。 - Hans Passant
阅读了您的评论后,我将uint签名更改为int并删除了out。没有异常,但是我从该行获取到垃圾数据。 - gr1d3r
不要忽略返回值。而且你一定要专注于正确配置串口,例如选择错误的波特率会产生垃圾数据。 - Hans Passant
1个回答

7
错误在于您使用了 out 关键字。如果您需要被调用者分配一个新数组并将其返回给您,则可以使用该关键字。这是多余的间接层级。
因此,您可以使用以下的 p/invoke:
[DllImport("RS232LIB.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int Read(byte[] bytesRead, uint maxNbBytes, uint timeOutMs);

使用以下方式调用:

var bytes = new byte[4];
Read(bytes, (uint)bytes.Length, timeOutMs);

请注意,byte是可平坦化的,因此byte[]也是可平坦化的。这意味着框架将简单地固定您的数组。因此,它会作为[In,Out]进行编组。如果您想更明确地表达意图,可以编写:
[DllImport("RS232LIB.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int Read([Out] byte[] bytesRead, uint maxNbBytes, uint timeOutMs);

但是行为不会有任何区别。数组仍然会被固定,语义上的参数将是[In,Out]。
我还删除了不必要的CharSet规范,并将另外两个参数更改为uint以匹配unsigned int。当然,使用uint可能会引入额外的转换,这可能会让您感到恼火。出于方便起见,在p/invoke声明中坚持使用int可能会得到原谅。

大卫,我尝试了你的解决方案。目前我仍然得到相同的4个垃圾数据字节。在读取超过4个字节时,其余部分都为0x00。 - gr1d3r
1
很可能这就是DLL发送给你的内容。这个问题涉及到封送,我已经准确地回答了。您可以通过编写一个具有相同函数签名的简单DLL来验证,该DLL使用已知值填充数组。当您验证这些值出现在C#程序中时,您将知道封送是正确的,而问题则在其他地方。这真的是调试101。缩小问题范围,直到问题变得明显。 - David Heffernan
你的解决方案有效。我的问题在串行设备上。 - gr1d3r

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