Marshalling non-Blittable Structs from C# to C++
- by Greggo
I'm in the process of rewriting an overengineered and unmaintainable chunk of my company's library code that interfaces between C# and C++. I've started looking into P/Invoke, but it seems like there's not much in the way of accessible help.
We're passing a struct that contains various parameters and settings down to unmanaged codes, so we're defining identical structs. We don't need to change any of those parameters on the C++ side, but we do need to access them after the P/Invoked function has returned.
My questions are:
What is the best way to pass strings? Some are short (device id's which can be set by us), and some are file paths (which may contain Asian characters)
Should I pass an IntPtr to the C# struct or should I just let the Marshaller take care of it by putting the struct type in the function signature?
Should I be worried about any non-pointer datatypes like bools or enums (in other, related structs)? We have the treat warnings as errors flag set in C++ so we can't use the Microsoft extension for enums to force a datatype.
Is P/Invoke actually the way to go? There was some Microsoft documentation about Implicit P/Invoke that said it was more type-safe and performant.
For reference, here is one of the pairs of structs I've written so far:
C++
/**
Struct used for marshalling Scan parameters from managed to unmanaged code.
*/
struct ScanParameters
{
LPSTR deviceID;
LPSTR spdClock;
LPSTR spdStartTrigger;
double spinRpm;
double startRadius;
double endRadius;
double trackSpacing;
UINT64 numTracks;
UINT32 nominalSampleCount;
double gainLimit;
double sampleRate;
double scanHeight;
LPWSTR qmoPath; //includes filename
LPWSTR qzpPath; //includes filename
};
C#
/// <summary>
/// Struct used for marshalling scan parameters between managed and unmanaged code.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct ScanParameters
{
[MarshalAs(UnmanagedType.LPStr)]
public string deviceID;
[MarshalAs(UnmanagedType.LPStr)]
public string spdClock;
[MarshalAs(UnmanagedType.LPStr)]
public string spdStartTrigger;
public Double spinRpm;
public Double startRadius;
public Double endRadius;
public Double trackSpacing;
public UInt64 numTracks;
public UInt32 nominalSampleCount;
public Double gainLimit;
public Double sampleRate;
public Double scanHeight;
[MarshalAs(UnmanagedType.LPWStr)]
public string qmoPath;
[MarshalAs(UnmanagedType.LPWStr)]
public string qzpPath;
}