将字符串数组作为缓冲区传递给 C++ 到 C#

Posted

技术标签:

【中文标题】将字符串数组作为缓冲区传递给 C++ 到 C#【英文标题】:Pass array of string as a buffer to C++ to C# 【发布时间】:2014-07-18 11:27:47 【问题描述】:

我有一些非托管 C++ 动态库和 C# GUI 应用程序,正在使用它。我需要将已知大小的字符串数组传递给填充它的 C++ 库。还有最大字符串长度值:

// C++ part
#define MAX_SOME_STRING_LEN 250;

MYDLL_API uint8_t __stdcall  getSomeStrings(wchar_t** strings, uint64_t count) 
    /// populate strings, not more characters then MAX_SOME_STRING_LEN for each string
    return 0;


// C# part
[DllImport("biosec_lib.dll", CallingConvention = CallingConvention.StdCall)]
    static extern Byte getSomeStrings(string[] providers, UInt64 size);

我想避免在 C++ 库端进行数组内存管理。我通过其他库 API 调用获得了理想的字符串数组大小。然后在C#端分配合适的数组,调用这个API方法,传递合适的数组。

    这是一种好方法吗?有没有更好的方法?在将数组中的每个字符串传递给 API 方法之前,我可以为其保留 MAX_SOME_STRING_LEN 大小吗?

【问题讨论】:

问题不在于数组,C#代码已经分配了它。问题是数组elements。你是如何创造它们的?他们需要被释放吗?如果要使用 C# 程序分配的内存,则需要使用 StringBuilder[],并使用具有足够容量的 StringBuilder 对象进行初始化。这是有风险的,当您的 wcscpy() 调用溢出缓冲区时,容量太低会导致堆损坏。 @HansPassant 是的,我想分配它们并稍后在 C# 端发布。你提到过,我知道溢出的问题。我应该小心它,并为此提供了缓冲区大小。 没有。 count 参数表示 strings 数组的长度。调用者无法指定每个字符串缓冲区的长度。编写此代码的唯一真正安全的方法是让 C++ 代码分配字符串。这确实需要您处理内存管理,调用者必须再次释放字符串。 pinvoke marshaller 会假设你使用了 CoTaskMemAlloc(),如果你没有使用 kaboom。 【参考方案1】:

这是一种不同的方法。您应该分配一种“非托管”缓冲区,传递给 DLL,然后转换结果并释放缓冲区。这与 C 中的方式完全相同,但从托管环境中调用。

您的 DLL 的签名将类似于:

[DllImport("biosec_lib.dll", CallingConvention = CallingConvention.StdCall)]
static extern Byte getSomeStrings(IntPtr buffer, UInt64 size);

要从 C# 调用它,您应该这样做:

        IntPtr unmanagedBuffer = Marshal.AllocHGlobal(100);
        // Your Unmanaged Call
        getSomeStrings(unmanagedBbuffer, 100);
        string yourString = Marshal.PtrToStringUni(unmanagedBuffer);
        Marshal.FreeHGlobal(unmanagedBuffer);

如果您不想在应用中出现内存泄漏,请不要忘记调用 FreeHGlobal。在“try/finally”子句中保护这一点很有趣。

【讨论】:

在这种情况下是否可以使用一些 RAII 类包装器进行内存管理?这种方法是否适合为每个字符串传递缓冲区数组,而不是单个缓冲区? 在 C/C++ 中,以空字符结尾的字符串是字符数组。这就是字符串的处理方式(有不同的方法,但这很流行)。由于在使用 DLL 时,您需要确定 DLL 对内存的期望,因此最安全的方法是发送缓冲区。 这就像使用非托管 DLL 时的“规则”。甚至 Windows API 也是这样工作的。如果 DLL 需要返回一些东西,你需要预先分配缓冲区来接收这个东西。甚至结构也是这样工作的。【参考方案2】:

如果你想在托管端使用 IntPtr,你可以这样使用:

[DllImport("biosec_lib.dll", CallingConvention = CallingConvention.StdCall)]
static extern Byte getSomeStrings([In][Out] [MarshalAs(UnmanagedType.LPArray, SizeConst = 1)] String[] providers, UInt64 count);

【讨论】:

SizeConst = 1 是否意味着我知道编译时的数组大小?正如我在问题中提到的,我在运行时调用 API 方法中得到了这个大小。编译时的已知大小 - 最大字符串长度。不是字符串大小的数组。 是的,你是对的,如果你使用 SizeConst 参数,你必须知道编译时的数组大小。我猜最好的方法是使用 IntPtr。 不幸的是,我只知道运行时的数组大小。库调用告诉我它需要多大的数组。 我从事编组工作,我们也许可以进行现场会议。 cataktunahan@gmail.com,我们可以修复它。 谢谢,希望我们不需要。似乎 StringBuilder 数组使用适合我的问题。如果没有其他人这样做,我会更新问题或写下我的答案,完成后。

以上是关于将字符串数组作为缓冲区传递给 C++ 到 C#的主要内容,如果未能解决你的问题,请参考以下文章

将可写 StringBuilder 数组从 C# 传递给 C++

将 C# 字符串数组传递给 C++

将 c++ dll 的 char 指针数组传递给 c# 字符串

使用协议缓冲区/Protobuf 的 PInvoke 通用字符串格式

通过互操作将字符串数组从 C# 传递到 C++

C++ 通过引用传递向量字符指针