【问题标题】:Correct way to wrap a c++ structure that contains a char[] in c++/cli?在 c++/cli 中包装包含 char[] 的 c++ 结构的正确方法?
【发布时间】:2026-01-29 18:05:01
【问题描述】:

我正在尝试对我继承的一些 c++ 代码进行正确的 c++/cli 包装,以控制一些实验室设备,以便我可以从其他 .net 语言调用代码。我有几个问题一直找不到答案。 c++ 代码定义了以下内容:

typedef struct{
    bool val;
    /* Some more c++ fundamental types ...*/

    char char_array[32];
    unsigned char unsigned_char_array[128];
    RECT rect;
} UnmangedStruct, *LpUnmanagedStruct;

我的包装结构如下:

public ref structure ManagedStruct{
    bool val;
    /* Some more c++ fundamental types ...*/

    System::String^ char_array;
    System::String^ unsigned_char_array;
    System::Drawing::Rectangle disp_rect;
}  

我知道尝试编写一些辅助函数来在两个结构之间进行转换。

问题:

1) *LpUnmanagedStruct 有什么作用?它是否初始化了这个结构的一个新实例并将 *LpUnmanaged 指向它?

2) String^ 是包装 char[] 和 unsigned char[] 的正确托管类型吗?矩形 矩形?

3) 我想知道的主要事情是如何转换 String^ 或任何用于包装 char[] / unsigned char[] 的正确类型,反之亦然,当只有 char* 可以传入和返回时一个函数。我在许多帖子中看到您应该将所有内容都转换为使用 char*,但如果我能提供帮助,我真的不想修改原始 c++。

【问题讨论】:

  • 你没有包装任何东西。 char_array 成员应该是一个属性,而不是使用非托管成员。并有一个更好的名字:) 当字符串不适合时,在 setter 中抛出异常。请改用ref class,这样您就可以使用private 隐藏本机结构。如果你包装一个指向结构的指针而不是结构本身,那么你需要一个析构函数和终结器。
  • 在您甚至不了解普通 C 和 C++ 的情况下使用 C++/CLI 只会以痛苦告终。但是this C++/CLI smart pointer I wrote can help,这样您就不必手动编写提到的处理程序和终结程序 Hans。 (.NET 类型没有析构函数,只有DisposeFinalize
  • @HansPassant 再次感谢汉斯。我会将您的建议和下面答案中的建议结合起来,看看我是否可以得到一些工作。并将在我的答案下方添加工作代码作为编辑。

标签: .net visual-c++ c++-cli wrapper


【解决方案1】:

正如 cmets 中所说,如果您还不了解 C++,那么 C++/CLI 不会有好的结局。 C++/CLI 具有 C++ 的所有复杂性,C# 的所有复杂性,以及一些它自己的复杂性。

也就是说,这是我对这个问题的看法。

如果您正在编写 C++/CLI 包装器来手动在两个结构之间进行转换,那么您要做的就是使用最自然的类型化成员声明托管类型。例如,使用String^ 声明托管类型,并手动处理到char[] 的转换。

(如果您要进行编组和 P/Invoking,您需要更关心如何声明托管结构。在这种情况下,您需要声明所有内容,以便自动编组产生输出中正确的字节。但既然你说的是“包装器”,我认为这意味着你将手动进行托管到非托管的转换,我们不需要担心。)

那么,让我们来看看结构体的各个字段:

  • char char_array[]:正如你所说,这可能是一个字符串,所以String^ 类型是合适的。
  • unsigned char unsigned_char_array[128]:看看这个会员是怎么用的,真名叫什么。这可能是一个字符串,也可能是二进制数据,即List<System::Byte>array<System::Byte>
  • RECT: System::Drawing::Rectangle 看起来不错,但在编写转换方法时要小心:RECT 使用左/右/上/下,而System::Drawing::Rectangle 使用 x/y/width/height。只需在System::Drawing::Rectangle 上使用正确的访问器和转换方法。您可能还需要考虑 .Net 库中的其他矩形类之一:例如,如果您要在 WPF GUI 中使用该矩形,您可能希望使用 System.Windows.Rectangle 代替。

其他说明:

  • 该 typedef 声明了两种类型:UnmangedStructLpUnmanagedStruct,它是一个指向 UnmangedStruct 的指针。我从来没有真正理解为什么我们需要一个单独的声明类型来引用指向结构的指针(UnmangedStruct* 对我来说总是足够的),但这是一种标准模式。
  • 强烈建议您将此声明为ref class,原因有两个:
    • 您需要的是托管类,而不是托管结构。您应该只将托管结构用于最简单的事情,即使这样,也建议它是不可变的。与 C# 交互时,您很少需要托管的可变结构。
    • 您已经有一个托管类,而不是托管结构:托管结构是value classvalue struct,而托管类是ref classref struct。我建议只使用value structref class,因为其他的很容易出错。
  • 要处理实际编码,我在 .Net 库中找不到可以直接从 String^ 转到现有的 char[] 的方法。 (它们要么将其他东西作为参数,要么返回托管字节数组。)我要做的是使用std::string 作为中介:做marshal_as<std::string>(managedString),然后对char[] 做一个普通的strcpy。对于另一个方向,将 char 数组直接传递给采用 char* 的方法:marshal_as<String^>(char_array)
  • 必须在托管类上编写 Dispose 和 Finalize 方法,以释放使用 newmalloc 分配的所有非托管内存。

    ~ManagedStruct() { delete foo; free(bar); delete managed_idisposable_baz; } // Dispose method
    !ManagedStruct() { delete foo; free(bar); } // Finalize method
    

    或者使用 Ben Voigt 在 cmets 中提到的辅助类。

【讨论】: