使用C#中的C ++ / CLI结构

时间:2016-03-10 09:44:32

标签: c# c++-cli

让我开始另一个问题,因为虽然我看到了许多相似的问题,但没有人真正谈到这个方面......我有一个C ++ DLL(没有源代码但是.lib和.h)我编写了必要的托管包装器。这没有问题,问题是关于原始C ++代码中定义的结构和枚举,并且有很多这些结构和枚举,都需要暴露给C#代码。教程和示例通常使用简单的数据类型,如浮点数和字符串,而不是复杂数据结构的真实场景。

我的托管C ++ / CLI包装器使用DLL的.h头文件中的非托管结构。我包装的类成员函数一直使用它们。因此,我需要在我的C#代码中使用相同的结构,传递它们并从C ++代码接收。很明显,我无法避免在C#中重新定义所有这些内容,但即便如此,使用它们也存在问题。让我们举个例子:非托管代码中函数使用的简单结构:

typedef struct INFO {
  ...
} INFO;

int GetInfo(INFO& out_Info);

我在C ++ / CLI包装器代码中声明了相同的结构:

public ref struct INFO_WRAP {
  ...
};

int GetInfo(INFO_WRAP out_Info);

包装器代码中的实现尝试将此新结构转换为原始结构,以便使用旧的非托管代码:

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

但这不会编译(无法在结构之间进行转换,也找不到合适的转换)。

是否有一个解决方案不涉及创建新的数据结构并手动复制所有结构成员,来回?不仅因为额外的工作和时间,而且还有很多结构。

2 个答案:

答案 0 :(得分:2)

  public ref struct INFO_WRAP

您没有声明结构,这是C#术语中的一个类。 Quirky C ++实现细节,C ++结构只是一个包含所有成员 public 的类。您需要在C ++ / CLI中使用value struct来声明C#struct的等价物。

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

这也是错误的,因为INFO_WRAP实际上是一个引用类型,你必须始终用^ hat声明它。或者通过引用传递%,肯定是这里的意图。

除了直接支持的基本障碍之外,你要求的是什么。托管编译器不允许对托管结构的布局做出任何假设。无论如何,当你尝试它时会吠叫。有一个很好的理由,它是just not that predictable。布局是一个强大的运行时实现细节,如果代码在不同的运行时运行,则可以更改。像32位和64位一样,可能在一个但不在另一个中工作。正如乔恩发现的那样。

逐个复制字段始终有效且性能足够高。只是不编程程序员喜欢维护。您可以要求框架为您执行此操作,调用Marshal::PtrToStructure()或StructureToPtr()。

作弊 是可能的,当然你在编写C ++ / CLI代码时会考虑的事情。毕竟,语言的重点是快速互操作。您只是取消了保修,必须在您打算支持的任何平台上彻底测试代码。一个简单的例子:

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

随着工作正常并在任何平台上可预测地执行,结构很简单。当结构不简单或你说,修改Native并忘记修改Managed然后付出代价,堆栈和GC堆损坏是非常不愉快的事故并且很难调试。

答案 1 :(得分:0)

这是整个解决方案,在完整的描述中,对于追随我的其他人。 :-)假设我们在.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;

以通常的方式转换结构,并添加两个处理编组的静态转换函数。正如汉斯所指出的那样,片断复制是整个平台和架构中唯一真正可靠的解决方案。

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

接下来,我们通常在.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);
};

我们需要创建一个包装类。它的标题:

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

及其实施:

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

基本上,您需要使用常规和自己的struct marshalling函数手动转换传入和传出数据。