c# - 如何调用分配输出缓冲区以在c#中返回数据的非托管c++函数?
Posted
技术标签:
【中文标题】c# - 如何调用分配输出缓冲区以在c#中返回数据的非托管c++函数?【英文标题】:How to call unmanaged c++ function that allocates output buffer to return data in c#? 【发布时间】:2010-02-05 07:16:56 【问题描述】:我在编组将数据数组返回到 c# 的 c++ 函数的输出参数时遇到问题。
这里是 C++ 声明:
#define DLL_API __declspec(dllexport)
typedef TPARAMETER_DATA
char *parameter;
int size;
PARAMETER_DATA;
int DLL_API GetParameters(PARAMETER_DATA *outputData);
该函数为 char 数组分配内存,将数据放在那里,并在“size”字段中返回分配的字节数。 这是我的 c# 声明:
[StructLayout(LayoutKind.Sequential)]
public struct PARAMETER_DATA
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = 50000)]
public byte[] data; // tried also SizeParamIndex = 1 instead of SizeConst
[MarshalAs(UnmanagedType.I4)]
public int size;
[DllImport("thedll.dll", SetLastError = true, ExactSpelling = true)]
public extern static uint GetParameters(ref PARAMETER_DATA outputData); // tried also 'out' parameter
在 c# 中调用函数时,我得到空结构(大小=0,空数组)。我尝试将 outputData 参数与初始化为新字节 [50000] 的数据字段一起传递,但无论如何都没有返回数据。
这个 dll 中的所有其他函数(一些具有复杂的输入结构)都可以正常工作,但这是唯一分配内存以返回数据的函数。 我尝试了许多其他 C# 编组声明(使用 LPArray、LPString)但没有成功 - 总是返回空数据结构或抛出内存访问异常。 我在这里遗漏了一些简单的东西吗?
编辑:
我无法更改 c++ 代码 - 它是外部库。
【问题讨论】:
如果将data
更改为IntPtr
类型,是否会得到非空结果?
如何查看 IntPtr 变量的内容?
你可以和IntPtr.Zero
比较
char *parameter;
和指向 ANSI 字符串的指针,还是指向字节数组的指针?如果是字符串,@Koert 的答案应该是解决方案。如果是字节数组,请使用 Marshal.Copy 而不是 Marshal.PtrToStringAnsi。
char* 参数是一个指向字节数组的指针(我认为,该库的文档不清楚)。 char* 声明在这里有点令人困惑。我会测试这两种方式。
【参考方案1】:
您面临的问题是返回了一个指针——不是真正的字符串或数组。编组器无法将指针转换为数组或字符串,因为长度未知。
解决方案可能是在 c# 中进行指针处理。您还应该弄清楚您是否负责释放指针,或者库会为您执行此操作。
[StructLayout(LayoutKind.Sequential)]
public struct PARAMETER_DATA
public IntPtr data; // tried also SizeParamIndex = 1 instead of SizeConst
[MarshalAs(UnmanagedType.I4)]
public int size;
[DllImport("thedll.dll", SetLastError = true, ExactSpelling = true)]
private extern static uint GetParameters(ref PARAMETER_DATA outputData);
public static uint GetParameters(out String result)
PARAMETER_DATA outputData = new PARAMETER_DATA();
result= Marshal.PtrToStringAnsi(outputData.data, outputData.size );
Marshal.FreeHGlobal(outputData.data); // not sure about this
【讨论】:
真的是指针返回吗? c++ 函数参数是 *outputData 但它是传递给函数的指针,它应该指向一个已经分配的结构(char* 有 4 个字节,int 大小有 4 个字节)。那么函数的调用在 C# 中应该是什么样子的呢?【参考方案2】:您对 C# 结构的封送声明与您的 C++ 结构不匹配,它们对应于此 c++ 结构
typedef TPARAMETER_DATA
char parameter[50000];
int size;
PARAMETER_DATA;
您也许可以使用一些特殊的编组代码,但我认为更简单的方法是更改您在 C++ 端进行分配的方式。
我确信还有其他方法可以做到这一点,但我开始工作的一种方法是使用 SAFEARRAY。 SAFEARRAY 是标准 COM API 的一部分,最初是为与 VB 互操作而创建的(我认为)。 http://msdn.microsoft.com/en-us/library/ms221145.aspx
SAFEARRAY 知道它的大小和数据类型,因此编组器很容易处理。我必须先锁定,然后才能用 C++ 写入它,然后在尝试编组之前解锁。
所以你的新参数结构是这样的,(我认为不需要大小,SAFEARRAY 已经知道了)
typedef TPARAMETER_DATA
SAFEARRAY * parameter;
int size; // I think this is redundant.
在您的 C++ 代码中,您使用分配数组
SAFEARRAY * psa = SafeArrayCreateVector(VT_UI1, 0, 50000);
if ( ! psa)
return E_OUTOFMEMORY;
HRESULT hr = SafeArrayLock(psa);
if (FAILED(hr))
SafeArrayDestroy(psa);
return hr;
CopyMemory(psa->pvData, mydataptr, 50000);
SafeArrayUnlock(psa);
PARAMETER_DATA pda = psa, 50000;
那么结构的C#声明就是
[StructLayout(LayoutKind.Sequential)]
public struct PARAMETER_DATA
[MarshalAs(UnmanagedType.SafeArray)]
public byte[] data; // I used System.Array here, but I think byte[] is OK
[MarshalAs(UnmanagedType.I4)]
public int size;
【讨论】:
问题是我无法更改 c++ 代码 - 它是外部库。【参考方案3】:这是您尝试过的事情之一吗? LPArray 和 SizeParamIndex = 1
[StructLayout (LayoutKind.Sequential)]
public struct PARAMETER_DATA
[MarshalAs (UnmanagedType.LPArray, ArraySubType = UnmanagedType.I1, SizeParamIndex = 1)]
public byte [] data;
[MarshalAs (UnmanagedType.I4)]
public int size;
【讨论】:
是的。没有成功。调用后 byte[] 数据完好无损。 那么没有办法向 marshaler 描述你的结构。您必须进行自定义编组,或编写一些 C++ 代码以在编组之前更改结构。 Koehrt 的解决方案非常接近您的需求。你应该从那里开始。 SizeParamIndex 只能用于参数,不能用于字段。【参考方案4】: 供您参考,我使用 LayoutKind.Explicit 和 FieldOffset,并在我的项目中忽略 ArraySubType。 [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi, Size=50004)] 公共结构 PARAMETER_DATA [FieldOffset(0)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50000)] 公共字节[]数据; [FieldOffset(50000)] [MarshalAs(UnmanagedType.I4)] 公共整数大小;【讨论】:
以上是关于c# - 如何调用分配输出缓冲区以在c#中返回数据的非托管c++函数?的主要内容,如果未能解决你的问题,请参考以下文章
创建 Restful Web 服务以在 C# 中调用存储过程
调用方法并将返回值分配给数组时,为啥C#在调用方法时使用数组引用?
将 UI Thread 方法传递给另一个线程以在 C# 中调用