📜 ⬆️ ⬇️

Calling native code from C #. Marshaling structures

Increasingly, developers began to get up the task of calling native methods from managed code. In most cases, the challenge is simple enough, but sometimes there are unpleasant incidents, such as a method that contains a structure with a dynamic array of structures containing a dynamic array of structures.

A little bit about the task

There is a dll written in C ++, with the source code which contains the following declarations:
#pragma pack (push, 4) struct vpsMSR { unsigned __int64 data; unsigned int address; }; #pragma pack (pop) struct vpsConfCounter { int address; int number; vpsMSR *config; unsigned int configCount; }; struct vpsConfig { int processorsCount; vpsConfCounter *counters; unsigned int countersCount; bool printToScreen; std::wstring activityName; }; extern "C" VPS::ErrorCode InitConfig(vpsConfig conf); extern "C" VPS::ErrorCode ClearConfig(vpsConfig conf); 

The task is to implement a method call from managed code.

Possible approaches

Nahrapom solve the problem failed. When searching for solutions, there was one HowTo . It briefly describes the possibility of marshaling a dynamic array of structures, but with it, each pointer has a lot of "serving" code. If you choose the marshalling path described in this article, you must declare arrays with the fixed keyword, otherwise there may be problems.
The essence of the problem is that the GC can ignore the nesting of the second level and move your structures, after this action the pointers will contain incorrect values.


Since the method referenced above requires quite accurate work and generates a lot of excess code that makes reading difficult. It was decided to use temporary structures:
 namespace ManagedTemp { #pragma pack (push, 4) struct vpsMSR { unsigned __int64 data; unsigned int address; }; #pragma pack (pop) struct vpsConfCounter { int address; int number; vpsMSR config[10]; unsigned int configCount; }; struct vpsConfig { int processorsCount; vpsConfCounter counters[10]; unsigned int countersCount; bool printToScreen; wchar_t* activityName; }; } 

Arrays of constant length are used in these structures. Restrictions - dictated by the subject area and in practice are not achieved.
D dll were added two methods that take structures with a constant array length and forward them to the original methods.
 extern "C" VPS::ErrorCode InitConfig2(ManagedTemp::vpsConfig* conf); extern "C" VPS::ErrorCode ClearConfig2(ManagedTemp::vpsConfig* conf); 


We start to marshal the structures described above:
 [StructLayout(LayoutKind.Sequential, Pack = 4)] public struct MSR { [MarshalAs(UnmanagedType.U8)] public System.UInt64 data; public int adress; } [StructLayout(LayoutKind.Sequential)] public struct ConfCounter { public int adress; public int number; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] public MSR[] config; [MarshalAs(UnmanagedType.U4)] public uint configCount; } [StructLayout(LayoutKind.Sequential), Serializable] public struct Config { public int processorCount; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] public ConfCounter[] counters; [MarshalAs(UnmanagedType.U4)] public uint countersCount; [MarshalAs(UnmanagedType.Bool)] public bool printToScreen; [MarshalAs(UnmanagedType.LPWStr)] public string activityName; } 

Marshaling methods:
  [DllImport(@"..\..\IConfigure.dll", EntryPoint = "InitConfig2")] public static extern int InitConfig(ref Config conf); [DllImport(@"..\..\IConfigure.dll", EntryPoint = "ClearConfig2")] public static extern int ClearConfig(ref Config conf); 

So, as in the dll on C ++, stdcall is set by default, there is no need to explicitly indicate the type of call through the CallingConvention attribute.

Problems encountered and how to solve them

When implementing marshaling, I get an error / warning of StackUnbalanced. What to do?

I had to see this error most often - it means that the signatures of the imported function and the source function do not match.
The reasons:
1. Incorrect type mashing.
2. Different call convention.
Methods of diagnosis: First, check the compliance of the calling conventions, then try to transfer the structure field by field and look at which field the error flies.

Impossibility of casting int to __int64

PInvoke does not lead int to int64. Therefore it was necessary to use the System.Int64 type.

This material is based on the experience in the ITLab laboratory at UNN. The process used HowTo article and information about the marshaling on msdn.

Source: https://habr.com/ru/post/105232/

All Articles