简体   繁体   English

从C#使用C ++ / CLI结构

[英]Using C++/CLI structs from C#

Let me start another question because although I saw many similar ones, not one really speaks about this aspect... I have a C++ DLL (no source code but .lib and .h) and I wrote the necessary managed wrapper. 让我开始另一个问题,因为尽管我看到了许多类似的问题,但没有人真正谈论过这一方面……我有一个C ++ DLL(没有源代码,只有.lib和.h),并且编写了必要的托管包装器。 There is no problem with that, the question is about the structs and enums defined in the original C++ code, and there are lots of them, all needed to be exposed to the C# code. 这样做没有问题,问题在于原始C ++代码中定义的结构和枚举,其中有很多都需要向C#代码公开。 Tutorials and samples usually use simple data types like floats and strings, not real world scenarios of complex data structures. 教程和示例通常使用简单的数据类型(例如浮点数和字符串),而不是复杂数据结构的真实场景。

My managed C++/CLI wrapper consumes the unmanaged structs from the DLL's .h header files. 我的托管C ++ / CLI包装器使用DLL的.h头文件中的非托管结构。 The class member functions that I wrap use them all the time. 我包装的类成员函数一直都在使用它们。 Consequently, I need to use the same structs in my C# code, passing them and receiving from the C++ code. 因此,我需要在我的C#代码中使用相同的结构,将它们传递并从C ++代码接收。 It seems clear that I can't avoid redefining all of them in C# but even then, using them is problematic. 显然,我无法避免在C#中重新定义所有它们,但是即使那样,使用它们还是有问题的。 Let's have an example: a simple struct that is used by a function in the unmanaged code: 让我们举个例子:一个简单的结构,供非托管代码中的函数使用:

typedef struct INFO {
  ...
} INFO;

int GetInfo(INFO& out_Info);

I have the same structure declared in my C++/CLI wrapper code: 我在C ++ / CLI包装器代码中声明了相同的结构:

public ref struct INFO_WRAP {
  ...
};

int GetInfo(INFO_WRAP out_Info);

The implementation in the wrapper code tries to convert this new structure to the original one for the consumption of the old, unmanaged code: 包装器代码中的实现会尝试将此新结构转换为原始结构,以使用旧的非托管代码:

int Namespace::Wrapper::GetInfo(INFO_WRAP out_Info) {
  pin_ptr<INFO> pin_out_Info = out_Info;
  return self->GetInfo(*(::INFO*)pin_out_Info);
}

But this won't compile (cannot convert between the structs and no suitable conversion is found). 但这不会编译(无法在结构之间转换并且找不到合适的转换)。

Is there a solution that doesn't involve creating new data structures and manually copying all struct members all the time, back and forth? 有没有一种解决方案不涉及创建新的数据结构以及始终无休止地手动复制所有struct成员? Not only because of the extra work and time, but there are really many structs. 不仅因为额外的工作和时间,而且确实有很多结构。

  public ref struct INFO_WRAP

You did not declare a struct, this is a class in C# terms. 您没有声明结构,这是C#术语中的类。 Quirky C++ implementation detail, a C++ struct is simply a class with all of its members public . 一个古怪的C ++实现细节,一个C ++结构只是一个所有成员都是public的类 You need to use value struct in C++/CLI to declare the equivalent of a C# struct. 您需要在C ++ / CLI中使用value struct来声明与C#结构等效的结构。

  int Namespace::Wrapper::GetInfo(INFO_WRAP out_Info)

That's wrong as well, since INFO_WRAP is actually a reference type you must always declare it with the ^ hat. 这也是错误的,因为INFO_WRAP实际上是引用类型,所以必须始终使用^帽子声明它。 Or with % to pass it by reference, surely the intention here. 或使用%作为参考传递它,当然是这里的意图。


Basic obstacles out of the way, what you are asking for is not directly supported. 基本障碍阻碍了您的要求,但不直接支持。 A managed compiler is not allowed to make any assumptions about the layout of a managed struct. 托管编译器不允许对托管结构的布局进行任何假设。 And will bark when you try it anyway. 并且无论如何尝试都会吠叫。 For a very good reason, it is just not that predictable . 出于很好的理由,它只是不可预测的 Layout is a strong runtime implementation detail and can change if the code runs on a different runtime. 布局是一个重要的运行时实现细节,如果代码在其他运行时上运行,则布局可以更改。 Like 32-bit vs 64-bit, might work in one but not the other. 像32位和64位一样,可能在一个中起作用,但在另一个中不起作用。 As Jon found out. 正如乔恩所发现的。

Copying the fields one-by-one always works and is performant enough. 一对一地复制字段始终有效,并且性能足够。 Just not code that programmers ever like to maintain. 只是不是程序员曾经喜欢维护的代码。 Your can ask the framework to do it for you, call Marshal::PtrToStructure() or StructureToPtr(). 您可以要求框架为您执行此操作,请调用Marshal :: PtrToStructure()或StructureToPtr()。


Cheating is possible and certainly something you'd contemplate when you write C++/CLI code. 作弊可能的,并且在编写C ++ / CLI代码时肯定会考虑到这种情况。 After all, the point of the language is to make interop fast. 毕竟,该语言的重点是使互操作快速。 You just void the warranty, you must thoroughly test the code on any of the platforms you intend to support. 您只是使保修无效, 必须在要支持的任何平台上彻底测试代码。 A simple example: 一个简单的例子:

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));
}

With works just fine and executes predictably on any platform, the struct is simple. 由于工作正常且可以在任何平台上执行,因此结构很简单。 When the struct is not simple or you, say, modify Native and forget to modify Managed then there's hell to pay, stack and GC heap corruption are very unpleasant mishaps and very hard to debug. 如果结构不简单,或者说您修改了本机而忘了修改托管,那么您将付出巨大的代价,堆栈和GC堆损坏是非常令人不愉快的事故,并且很难调试。

Here is the whole solution, in a full description, for other people coming after me. 这是为跟随我的其他人提供的完整解决方案的完整描述。 :-) Let's suppose we have a DLL with enums, structs, classes, functions in the .h header file: :-)假设我们在.h头文件中有一个包含枚举,结构,类和函数的DLL:

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;

Convert the struct the usual way, and add two static conversion functions that handle the marshaling. 按常规方式转换结构,并添加两个处理封送处理的静态转换函数。 As Hans has noted, as tedious as it seems, piecewise copying is the only really reliable solution across platforms and architectures. 正如Hans所指出的那样,看起来很乏味,分段复制是跨平台和体系结构的唯一真正可靠的解决方案。

#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;
  }
};

Next, we have usual a class in the .h header file: 接下来,我们通常在.h头文件中有一个类:

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);
};

We need to create a wrapper class. 我们需要创建一个包装器类。 Its header: 它的标题:

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;
};

And its implementation: 及其实现:

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;
}

Basically, you need to use the usual and your own struct marshalling functions to convert both incoming and outgoing data manually. 基本上,您需要使用通常的和您自己的struct marshalling函数来手动转换传入和传出数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM