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# 中调用

如何使用 pinvoke 将二进制数据缓冲区从 C 传递到 C#

在 C# 中为富文本框分配内存

我应该如何编写 .i 文件以在 Java 或 C# 中包装回调