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.