简体   繁体   中英

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. 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. 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. 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. It seems clear that I can't avoid redefining all of them in C# but even then, using them is problematic. 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:

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? 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. Quirky C++ implementation detail, a C++ struct is simply a class with all of its members public . You need to use value struct in C++/CLI to declare the equivalent of a C# struct.

  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. 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. 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().


Cheating is possible and certainly something you'd contemplate when you write C++/CLI code. 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.

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:

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.

#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:

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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