繁体   English   中英

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

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

让我开始另一个问题,因为尽管我看到了许多类似的问题,但没有人真正谈论过这一方面……我有一个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);
}

但这不会编译(无法在结构之间转换并且找不到合适的转换)。

有没有一种解决方案不涉及创建新的数据结构以及始终无休止地手动复制所有struct成员? 不仅因为额外的工作和时间,而且确实有很多结构。

  public ref struct INFO_WRAP

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

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

这也是错误的,因为INFO_WRAP实际上是引用类型,所以必须始终使用^帽子声明它。 或使用%作为参考传递它,当然是这里的意图。


基本障碍阻碍了您的要求,但不直接支持。 托管编译器不允许对托管结构的布局进行任何假设。 并且无论如何尝试都会吠叫。 出于很好的理由,它只是不可预测的 布局是一个重要的运行时实现细节,如果代码在其他运行时上运行,则布局可以更改。 像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));
}

由于工作正常且可以在任何平台上执行,因此结构很简单。 如果结构不简单,或者说您修改了本机而忘了修改托管,那么您将付出巨大的代价,堆栈和GC堆损坏是非常令人不愉快的事故,并且很难调试。

这是为跟随我的其他人提供的完整解决方案的完整描述。 :-)假设我们在.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;

按常规方式转换结构,并添加两个处理封送处理的静态转换函数。 正如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;
  }
};

接下来,我们通常在.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函数来手动转换传入和传出数据。

暂无
暂无

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

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