Using C++/CLI structs from C#
让我开始另一个问题,因为尽管我看到了许多相似的问题,但没有一个真正谈到这方面…我有一个C++ DLL(没有源代码,但是.LIB和.H),并且我编写了必要的托管包装器。这是没有问题的,问题是关于在原始C++代码中定义的结构和枚举,并且有很多,它们都需要暴露在C代码中。教程和示例通常使用简单的数据类型,如浮点和字符串,而不是复杂数据结构的真实场景。
我的托管C++包装器从DLL的头文件中消耗非托管文件集。我包装的类成员函数一直使用它们。因此,我需要在C代码中使用相同的结构,传递它们,并从C++代码中接收。很明显,我不能避免在C中重新定义所有这些元素,但即使如此,使用它们也是有问题的。让我们举个例子:非托管代码中的函数使用的简单结构:
1 2 3 4 5 | typedef struct INFO { ... } INFO; int GetInfo(INFO& out_Info); |
我在C++/CLI包装代码中声明了相同的结构:
1 2 3 4 5 | public ref struct INFO_WRAP { ... }; int GetInfo(INFO_WRAP out_Info); |
包装代码中的实现尝试将此新结构转换为原始结构,以便使用旧的非托管代码:
1 2 3 4 | int Namespace::Wrapper::GetInfo(INFO_WRAP out_Info) { pin_ptr<INFO> pin_out_Info = out_Info; return self->GetInfo(*(::INFO*)pin_out_Info); } |
但无法编译(无法在结构之间进行转换,找不到合适的转换)。
有没有一个解决方案不涉及创建新的数据结构和手动复制所有结构成员的所有时间,来回?不仅因为额外的工作和时间,还有很多结构。
1 | public ref struct INFO_WRAP |
您没有声明一个结构,这是一个用C语言表示的类。奇巧C++实现细节,C++结构是一个所有成员都是公共的类。在C++/CLI中,需要使用EDCOX1 0来声明等价于C的结构。
1 | int Namespace::Wrapper::GetInfo(INFO_WRAP out_Info) |
这也是错误的,因为信息包装实际上是一个引用类型,所以必须始终用^ hat声明它。或者用%作为参考,肯定是这里的意图。
基本障碍让开,你所要求的不是直接支持的。托管编译器不允许对托管结构的布局进行任何假设。不管怎样,当你尝试它的时候,它都会吠叫。出于一个很好的理由,这只是不可预测的。布局是一个强大的运行时实现细节,如果代码在不同的运行时上运行,则布局可以更改。像32位和64位一样,可能在一个系统中工作,但在另一个系统中不工作。乔恩发现了。
一个接一个地复制字段总是有效的,并且性能足够好。而不是程序员喜欢维护的代码。您可以要求框架为您执行此操作,调用marshal::ptrtostructure()或structureToptr()。
作弊是可能的,当然是你在编写C++/CLI代码时会考虑的事情。毕竟,该语言的要点是使互操作快速进行。您只是使保修失效,必须在您打算支持的任何平台上彻底测试代码。一个简单的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public value struct Managed { int member1; int member2; }; struct Native { int member1; int member2; }; void GetInfo(Managed% info) { Native n = { 1, 2 }; pin_ptr<Managed> pinfo = &info; memcpy(pinfo, &n, sizeof(n)); } |
由于工作很好,并且可以在任何平台上执行,因此结构很简单。当结构不是简单的或者你,比如说,修改本机并忘记修改托管的时候,就有了地狱般的代价,栈和GC堆的损坏是非常令人不快的不幸,并且很难调试。
这里是完整的解决方案,以完整的描述,为其他人来追求我。:-)假设在
1 2 3 4 5 6 7 8 9 10 11 | typedef int (*DelegateProc)(long inLong, char* inString, STRUCT2* inStruct, long* outLong, char* outString, STRUCT2* outString); typedef struct STRUCT1 { long aLong; short aShort; BOOL aBoolean; char aString[64]; STRUCT2 aStruct; DelegateProc aDelegateProc; char Reserved[32]; } STRUCT1; |
按照通常的方式转换结构,并添加两个处理封送的静态转换函数。正如汉斯所指出的,尽管看起来很乏味,分段复制是跨平台和体系结构的唯一真正可靠的解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #include <msclr\marshal.h> using namespace msclr::interop; public delegate int DelegateProc(long inLong, String^ inString, STRUCT2 inStruct, [Out] long% outLong, [Out] String^ outString, [Out] STRUCT2 outStruct); [StructLayout(LayoutKind::Sequential, Pack = 1)] public value struct WRAP_STRUCT1 { long aLong; short aShort; bool aBoolean; [MarshalAs(UnmanagedType::ByValArray, SizeConst = 64)] array<char>^ aString; WRAP_STRUCT2 aStruct; DelegateProc^ aDelegateProc; [MarshalAs(UnmanagedType::ByValArray, SizeConst = 32)] array<char>^ Reserved; static STRUCT1 convert(WRAP_STRUCT1^ other) { STRUCT1 clone; clone.aLong = other->aLong; clone.aShort = other->aShort; clone.aBoolean = other->aBoolean; sprintf(clone.aString,"%s", other->aString); clone.aStruct = WRAP_STRUCT2::convert(other->aStruct); clone.aDelegateProc = (Delegate1Proc)Marshal::GetFunctionPointerForDelegate(other->aDelegateProc).ToPointer(); return clone; } static WRAP_STRUCT1 convert(STRUCT1 other) { WRAP_STRUCT1 clone; clone.aLong = other.aLong; clone.aShort = other.aShort; clone.aBoolean = (other.aBoolean > 0); clone.aString = marshal_as<String^>(other.aString)->ToCharArray(); clone.aStruct = WRAP_STRUCT2::convert(other.aStruct); clone.aDelegateProc = (DelegateProc)Marshal::GetDelegateForFunctionPointer((IntPtr)other.aDelegateProc, DelegateProc::typeid); return clone; } }; |
接下来,我们在
1 2 3 4 5 6 7 8 9 | class __declspec(dllexport) CLASS1 { public: CLASS1(); virtual ~CLASS1(); virtual int Function1(long inLong, char* inString, STRUCT2* inStruct); virtual int Function2(long* outLong, char* outString, STRUCT2* outStruct); }; |
我们需要创建一个包装类。它的标题:
1 2 3 4 5 6 7 8 9 10 11 12 | public ref class Class1Wrapper { public: Class1Wrapper(); ~Class1Wrapper(); int Function1(long inLong, String^ inString, WRAP_STRUCT2 inStruct); int Function2([Out] long% outLong, [Out] String^ outString, [Out] WRAP_STRUCT2% outStruct); private: CLASS1* self; }; |
及其实施:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | Namespace::Class1Wrapper::Class1Wrapper() { self = new CLASS1(); } Namespace::Class1Wrapper::~Class1Wrapper() { self->~CLASS1(); } int Namespace::Class1Wrapper::Function1(long inLong, String^ inString, WRAP_STRUCT2 inStruct) { char pinString[64]; sprintf(pinString,"%s", inString); STRUCT2 pinStruct = WRAP_STRUCT2::convert(inStruct); return self->Function1(inLong, pinString, pinStruct); } int Namespace::Class1Wrapper::Function2([Out] long% outLong, [Out] String^ outString, [Out] WRAP_STRUCT2% outStruct) { long poutLong; char poutString[64]; STRUCT2 poutStruct; ::ZeroMemory(&poutStruct, sizeof(poutStruct)); int result = self->Function2(poutLong, poutString, poutStruct); outLong = poutLong; outString = marshal_as<String^>(poutString); outStruct = WRAP_STRUCT2::convert(poutStruct); return result; } |
基本上,您需要使用通常的和您自己的结构编组函数来手动转换传入和传出数据。