How to marshal an array of structure In C#? - c#

I have to call a C++ dll in C#. And the header of the dll is as following(simplified):
//Header of C++
struct vector
{
float x;
float y;
vector()
{}
vector(float x0, float y0)
{
x = x0;
y = y0;
}
};
struct unmanaged_struct
{
int int_var;
float float_var;
char* chars_var;
vector vector_var;
unmanaged_struct(int i, float f, char* ch, float vec_x, float vec_y)
{
int_var = i;
float_var = f;
chars_var = ch;
vector_var = vector(vec_x, vec_y);
}
};
// this function is used to output all the variable values of the struct instance
extern "C" __declspec( dllexport ) void unmanagedstruct_summary(unmanaged_struct* us_list, int length);
And I defined following class in C#
//CSharp
[StructLayout(LayoutKind.Sequential)]
public class Vector
{
public float x;
public float y;
public Vector(float f1, float f2)
{
x = f1;
y = f2;
}
}
[StructLayout(LayoutKind.Sequential)]
public class UnmanagedStruct
{
public int int_var;
public float float_var;
public string char_var;
public Vector vector_var;
public UnmanagedStruct(int i, float f, string s, Vector vec)
{
this.int_var = i;
this.float_var = f;
this.char_var = s;
this.vector_var = vec;
}
}
class UnmanagedDllCallTest
{
[DllImport("unmanageddll.dll", EntryPoint = "unmanagedstruct_summary")]
public static extern void unmanagedstruct_summary([Out]UnmanagedStruct[] usList, int length);
static void Main(string[] args)
{
UnmanagedStruct[] usList = new UnmanagedStruct[1];
usList[0] = new UnmanagedStruct(1, 1.0f, "aa", new Vector(10, 1));
usList[1] = new UnmanagedStruct(2, 2.0f, "ba", new Vector(20, 2));
UnmanagedDllCallTest.unmanagedstruct_summary(usList, 2);
}
And the output is as following:
unmanaged_struct summary:
0
1.12104e-044
Unhandled Exception:
System.AccessViolationException:
Attempted to read or write protected
memory. This is often an indication
that other memory is corrupt. at
callunmanageddll.UnmanagedDllCallTest.unmanagedstruct_summary(UnmanagedStr
uct[] usList, Int32 length) at
callunmanageddll.Program.Main(String[]
args) in c:\users\dynaturtle\docume
nts\visual studio
2010\Projects\callunmanageddll\callunmanageddll\Program.cs:lin
e 68
The C++ dll is OK as I have written test in C++ and the function works well. I have read this thread but it seems the solution didn't work in my case. Any suggestions? Thanks in advance!

Use Marshal.PtrToStructure. There is a sample here.
So you would have to change the signature of the method from out structure array to out IntPtr. However, you need to know the size of the buffer being passed out.
public struct Vector
{
public float x;
public float y;
}
public struct UnmanagedStruct
{
public int int_var;
public float float_var;
public string char_var;
public Vector vector_var;
}
class UnmanagedDllCallTest
{
[DllImport("unmanageddll.dll", EntryPoint = "unmanagedstruct_summary")]
public static extern void unmanagedstruct_summary([Out] IntPtr ptr, int length);
static void Main(string[] args)
{
for(int i=0; i<length; i++)
{
UnmanagedStruc st;
Marshal.PtrToStructure(ptr, st);
// increment ptr and move forward
}
}

First: Vector and UnmanagedStruct should be structs, not classes.

JIC I'd share my approach. Perhaps this is not an expected answer by at one time I spend a time to resolve my issue.
I have the following structure to expose some data from DLL.
//C++ code
struct State
{
const wchar_t * name;
unsigned int state;
};
APIENTRY bool get_states(H_PRCSR, MacroState *, const int sz); //pay attention, function accepts already allocated array and size for it
To accept this data From C++ I can do this way
std::vector<State> states(desired_size);
get_states(hparser, &states[0], states.size());
To do the same on C# I used the following way
//C#
[StructLayout(LayoutKind.Sequential)]
public struct Status
{
public IntPtr name;
public uint state;
public string getName()
{
if (name == IntPtr.Zero) return "<no-value>";
return Marshal.PtrToStringUni(name);
}
}
//And import function...
[DllImport(dll, CallingConvention = CallingConvention.Winapi)]
private static extern bool get_states(IntPtr p, [Out]MacroFlag[] flags, int flags_size);
//And simple decoder
public static Status[] getAll(IntPtr p, int size)
{
var results = new Status[size];
get_states(p, results, size);
return results;
}
As, I saw the are different approaches to do this. This is one of them. And it works for me. Perhaps, this post will not resolve the issue but will be a good point to start

Related

Importing C struct to C# program

The Sctruct
I am not much of a C# or C programmer and have not found much guidance on specifying pointers and structs within a struct. I am attempting to import the following from a C dll into a C# program:
#define MAXFILENAME 259
struct IDentry {
char* IDname;
int length;
};
typedef struct IDentry idEntry;
struct SMOutputAPI {
char name[MAXFILENAME + 1];
FILE* file;
struct IDentry *elementNames;
long Nperiods;
int FlowUnits;
int Nsubcatch;
int Nnodes;
int Nlinks;
int Npolluts;
int SubcatchVars;
int NodeVars;
int LinkVars;
int SysVars;
double StartDate;
int ReportStep;
__int64 IDPos;
__int64 ObjPropPos;
__int64 ResultsPos;
__int64 BytesPerPeriod;
};
I am not sure how to handle the *elementsNames, file or name properties. What I have so far in C# is:
int MAXFILENAME = 259
[StructLayout(LayoutKind.Sequential)]
public struct SMOutputAPI
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = MAXFILENAME+1)]
public string name;
IntPtr file;
IntPtr elementNames;
public long Nperiods;
public int FlowUnits;
public int Nsubcatch;
public int Nnodes;
public int Nlinks;
public int Npolluts;
public int SubcatchVars;
public int NodeVars;
public int LinkVars;
public int SysVars;
public double StartDate;
public int ReportStep;
public int IDPos;
public int ObjPropPos;
public int ResultsPos;
public int BytesPerPeriod;
};
The C# application builds fine, but when I call the C initialization function that should return a new SMOutputAPI struct I get an error:
System.Runtime.InteropServices.MarshalDirectiveException
Method's type signature is not PInvoke compatible.
Any thoughts on how to properly specify this struct in C# would be much appreciated. Thanks!
Initializing the Struct
The struct is initialized in the c-code with:
SMOutputAPI* DLLEXPORT SMO_init(void)
//
// Purpose: Returns an initialized pointer for the opaque SMOutputAPI
// structure.
//
{
SMOutputAPI *smoapi = malloc(sizeof(struct SMOutputAPI));
smoapi->elementNames = NULL;
return smoapi;
}
The corresponding c# code is:
[DllImport("swmm-output.dll")]
static extern SMOutputAPI SMO_init();
static void Main(string[] args)
{
Console.Write("Hello World!");
SMOutputAPI SMO = SMO_init();
}

c# Calling c++ dll transfer struct array occurs exception

c++ header definition:
#pragma once
#ifndef __AFXWIN_H__
#error "include 'stdafx.h' before including this file for PCH"
#endif
c++ function:
int Detect(Pos* pfps);
c++ struct definition:
struct FaceAngle
{
int yaw;
int pitch;
int roll;
float confidence;
};
typedef struct tagPOINT
{
LONG x;
LONG y;
}
POINT, * PPOINT, NEAR * NPPOINT, FAR * LPPOINT;
struct Pos
{
RECT rcFace;
POINT ptNose;
FaceAngle fAngle;
int nQuality;
BYTE pFacialData[512];
Pos()
{
memset(&rcFace, 0, sizeof(RECT));
memset(&ptNose, 0, sizeof(POINT));
memset(&fAngle, 0, sizeof(FaceAngle));
nQuality = 0;
memset(pFacialData, 0, 512);
}
};
c++ Calling code:
void detect()
{
Pos ptfp[10];
Detect(ptfp);
}
===========================================================================
c# function definition:
[DllImport(THFaceDLLName, EntryPoint = "Detect")]
public static extern int Detect([In, Out] Pos[] posArray);
c# struct definition:
[System.Runtime.InteropServices.StructLayoutAttribute (System.Runtime.InteropServices.LayoutKind.Sequential,
CharSet = System.Runtime.InteropServices.CharSet.Ansi, Pack = 1)]
public struct Pos
{
public RECT rcFace;
public POINT ptNose;
public FaceAngle fAngle;
int nQuality;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)]
byte[] pFacialData;
};
[System.Runtime.InteropServices.StructLayoutAttribute (System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Ansi, Pack = 1)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[System.Runtime.InteropServices.StructLayoutAttribute (System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Ansi, Pack = 1)]
public struct POINT
{
public int x;
public int y;
}
[System.Runtime.InteropServices.StructLayoutAttribute (System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Ansi, Pack = 1)]
public struct FaceAngle
{
int yaw;
int pitch;
int roll;
float confidence;
};
c# calling code:
void detect()
{
Pos[] facePosArray = new Pos[10];
int faceNum = Detect(facePosArray);
}
An “System.AccessViolationException” exception occurs after execution Detect function,
should i convert Pos[] to Intptr? can anyone has good solution
I found a tool that can directly convert c++ struct
such as:
Software Interface
this is the download url:
--download.microsoft.com/download/f/2/7/f279e71e-efb0-4155-873d-5554a0608523/CLRInsideOut2008_01.exe--

Passing C# struct to C++ unmanaged DLL returning incorrect result

I have a simple C++ win32 DLL developed in visual studio 2017 and compiled in 64 bit environment having the following code:
typedef struct sum {
struct {
int num1;
int num2;
} nums;
} sum1;
extern "C" {
__declspec(dllexport) int initialize(sum1 *summing)
{
int res;
res = summing->nums.num1 + summing->nums.num2;
return res;
}
}
The above code contains a method which returns the sum of two integers by taking a typedef struct as an argument.
I have a C# client application which consumes this Win32 C++ DLL using PInvoke. Following is the code of my C# client application:
[StructLayout(LayoutKind.Sequential)]
public struct nums
{
public int a;
public int b;
}
[StructLayout(LayoutKind.Sequential)]
public struct mydef
{
public IntPtr sum;
}
public class LibWrap
{
[DllImport("C++.dll", EntryPoint = "initialize")]
public static extern int Initialize(ref mydef mydef);
}
class Program
{
static void Main(string[] args)
{
mydef mydef = new mydef();
nums nums;
nums.a = 6;
nums.b = 6;
IntPtr buffer1 = Marshal.AllocCoTaskMem(Marshal.SizeOf(nums));
Marshal.StructureToPtr(nums, buffer1, false);
mydef.sum = buffer1;
int res = LibWrap.Initialize(ref mydef);
Console.WriteLine(res);
}
}
With the above code, I am expecting '12' as output, but instead I am getting '-1504178328' as output.
I am a C# developer with no experience in C++ at all. Please help me to solve this problem.
Use a simpler P/Invoke wrapper:
public static class LibWrap
{
[DllImport("C++.dll", EntryPoint = "initialize")]
public static extern int Initialize(ref Nums nums);
[StructLayout(LayoutKind.Sequential)]
public struct Nums
{
public int a;
public int b;
}
}
and use it like this:
void CSharpExample()
{
LibWrap.Nums nums;
nums.a = 6;
nums.b = 7;
int res = LibWrap.Initialize(ref nums);
Console.WriteLine(res);
}
In your example, you don't need any memory allocation and marshaling, because:
LibWrap.Nums is a struct, thus local variable nums in CSharpExample() is allocated completely on stack.
passing managed struct LibWrap.Nums by ref to LibWrap.Initialize will pass the pointer to local variable nums on stack.
LibWrap.Initialize is called synchronously, so that the pointer you pass to it isn't used anywhere after LibWrap.Initialize function exits. This is important because the pointer becomes invalid as soon as CSharpExample() exits.
On the C# side, you are not handling the nested struct correctly. Try this instead:
[StructLayout(LayoutKind.Sequential)]
public struct mynums {
public int num1;
public int num2;
}
[StructLayout(LayoutKind.Sequential)]
public struct sum1 {
public mynums nums;
}
public class LibWrap {
[DllImport("C++.dll", EntryPoint = "initialize")]
public static extern int Initialize(ref sum1 summing);
}
class Program {
static void Main(string[] args) {
sum1 mysum;
mysum.nums.num1 = 6;
mysum.nums.num2 = 6;
int res = LibWrap.Initialize(ref mysum);
Console.WriteLine(res);
}
}
That being said, having a struct whose sole data member is another struct is redundant and unnecessary. You should remove the outer struct altogether, eg:
struct nums {
int num1;
int num2;
};
extern "C" {
__declspec(dllexport) int initialize(nums *summing) {
return summing->num1 + summing->num2;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct nums {
public int num1;
public int num2;
}
public class LibWrap {
[DllImport("C++.dll", EntryPoint = "initialize")]
public static extern int Initialize(ref nums summing);
}
class Program {
static void Main(string[] args) {
nums mynums;
mynums.num1 = 6;
mynums.num2 = 6;
int res = LibWrap.Initialize(ref mynums);
Console.WriteLine(res);
}
}

Understanding C++ DLL mapping to C# with unions

I have the following scenario. I have a struct with a union in a DLL written in C++ and I need to map this structure to C# code. Please, have in mind that I cannot change the code in the DLL. Here is a sample code to illustrate what is happening:
typedef struct {
int type;
union {
int val;
double val2;
char *name;
};
} BIZARRE;
__declspec(dllexport) void changeBizarre(BIZARRE *bizarre, int type, const char *v)
{
bizarre->type = type;
if(type == 0) {
bizarre->val = 5;
} else if(type == 1) {
bizarre->val2 = 2.0;
} else if(type == 2) {
strncpy(bizarre->name, "test", strlen("test"));
} else if(type == 3 && strcmp(v, "test") == 0) {
bizarre->val = 10;
}
}
And in the C# code, I've done the following:
[StructLayout(LayoutKind.Explicit)]
public unsafe struct BIZARRE
{
[FieldOffset(0)]
public int type;
[FieldOffset(8)]
public int val;
[FieldOffset(8)]
public double val2;
[FieldOffset(8)]
public char *name;
}
[DllImport("proj.dll", CallingConvention = CallingConvention.Cdecl)]
public unsafe static extern void changeBizarre(ref BIZARRE bizarre, int type, char *name);
unsafe static void Main()
{
char[] v = "test".ToCharArray();
bizarre.type = 0;
bizarre.val = 0;
bizarre.val2 = 0.0;
fixed (char* address = v)
{
bizarre.name = address;
changeBizarre(ref bizarre, 3, &bizarre.name);
Console.WriteLine("{0}", bizarre.val);
}
Console.ReadKey();
}
Now, if you run this code by passing type = 2, and try to print the bizarre.name, it will appear junk characters and if you pass type = 3, apparently the DLL is not being able to get the content pointed by bizarre.name, I do believe these two behaviors have the same cause, but I don't know what is it.
The char array is not a real char array in C, in fact C# will use wchar_t type (16 bit char).
Change your C code using wchar_t stuff:
typedef struct {
int type;
union {
int val;
double val2;
wchar_t *name;
};
} BIZARRE;
__declspec(dllexport) void changeBizarre(BIZARRE *bizarre, int type, const wchar_t *v)
{
bizarre->type = type;
if (type == 0) {
bizarre->val = 5;
}
else if (type == 1) {
bizarre->val2 = 2.0;
}
else if (type == 2) {
wcsncpy(bizarre->name, L"test", wcslen("test"));
}
else if (type == 3 && wcscmp(v, L"test") == 0) {
bizarre->val = 10;
}
}
Or
Change your C# code using the byte type instead of char (as mentioned #Hans Passant) :
[StructLayout(LayoutKind.Explicit)]
public unsafe struct BIZARRE
{
[FieldOffset(0)]
public int type;
[FieldOffset(8)]
public int val;
[FieldOffset(8)]
public double val2;
[FieldOffset(8)]
public byte* name;
}
class Program
{
[DllImport("UnionMapping_dll.dll", CallingConvention = CallingConvention.Cdecl)]
public unsafe static extern void changeBizarre(ref BIZARRE bizarre, int type, byte* name);
static unsafe void Main(string[] args)
{
BIZARRE bizarre;
byte[] v = Encoding.ASCII.GetBytes("test");
bizarre.type = 0;
bizarre.val = 0;
bizarre.val2 = 0.0;
fixed (byte* address = v)
{
bizarre.name = address;
changeBizarre(ref bizarre, 3, bizarre.name);
Console.WriteLine("{0}", bizarre.val);
}
Console.ReadKey();
}
}
The C++ code has a union within a struct - your C# equivalent for interop purposes should reflect that:
using System.Runtime.InteropServices;
public struct BIZARRE
{
public int type;
[StructLayout(LayoutKind.Explicit)]
public struct AnonymousStruct
{
[FieldOffset(0)]
public int val;
[FieldOffset(0)]
public double val2;
[FieldOffset(0)]
public string name;
}
}

Passing a struct pointer as a parameter in C#

I have a function in C++ that I exported to a DLL. I contains a struct pointer as one of the parameters. I need to use this function in C#, so I used DLLImport for the function and recreated the struct in C# using StructLayout. I've tried passing in the parameter using ref as well as tried Marshaling it in using MarshalAs(UnmangedType.Struct) and Marshal.PtrToStructure. The parameter still isn't passing correctly.
Example:
[DllImport("testdll.dll")]
public static extern int getProduct(int num1, int num2, [MarshalAs(UnmanagedType.Struct)] ref test_packet tester);
One more tidbit of info, the struct contains a byte* var, which I think may be causing the problem in terms of passing the param as ref. Any ideas? Am I on the right track? Thanks for the help.
Thanks nobugz for the response. Here's a sample of the struct def:
//C# DEFINITION
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct test_packet
{
public UInt32 var_alloc_size;
public byte* var;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_TAG_LENGTH)]
public byte[] tag;
}
//ORIGINAL UNMANAGED STRUCT
typedef struct test_packet_tag
{
unsigned int var_alloc_size;
unsigned char *var;
unsigned char tag[MAX_TAG_LENGTH];
} test_packet;
Using "ref" is the correct way, get rid of the [MarshalAs] attribute. The real problem is almost certainly the structure declaration. You didn't post anything that would help us help you with that.
The DllImportAttribute.CharSet property is wrong, make it CharSet.Ansi. The "var" member is declared wrong, make it byte[]. Be sure to initialize it before the call:
var product = new test_packet();
product.var_alloc_size = 666; // Adjust as needed
product.var = new byte[product.var_alloc_size];
int retval = getProduct(42, 43, ref product);
Best guess, hope it works.
Here's an example from my personal stuff. It can be really complicated. Note that its not easy to move arrays over as pointers, so you should look at how one does that in the c# side. This should hit a lot of the major data types. You must make sure your elements line up EXACTLY. Otherwise it will look like its working, but you'll get bad ptrs (at best). I had a great deal of trouble when moving this struct, and this was the only approach that worked. Good luck
Function sig -
[DllImport("stochfitdll.dll", EntryPoint = "Init", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern void Init([MarshalAs(UnmanagedType.LPStruct)] ModelSettings settings);
C++ side
#pragma pack(push, 8)
struct ReflSettings
{
LPCWSTR Directory;
double* Q;
double* Refl;
double* ReflError;
double* QError;
int QPoints;
double SubSLD;
double FilmSLD;
double SupSLD;
int Boxes;
double FilmAbs;
double SubAbs;
double SupAbs;
double Wavelength;
BOOL UseSurfAbs;
double Leftoffset;
double QErr;
BOOL Forcenorm;
double Forcesig;
BOOL Debug;
BOOL XRonly;
int Resolution;
double Totallength;
double FilmLength;
BOOL Impnorm;
int Objectivefunction;
double Paramtemp;
LPCWSTR Title;
};
#pragma pack(pop)
C# side -
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 8)]
public class ModelSettings:IDisposable
{
#region Variables
public string Directory;
public IntPtr Q;
public IntPtr Refl;
public IntPtr ReflError;
public IntPtr QError;
public int QPoints;
public double SubSLD;
public double SurflayerSLD;
public double SupSLD;
public int Boxes;
public double SurflayerAbs;
public double SubAbs;
public double SupAbs;
public double Wavelength;
public bool UseAbs;
public double SupOffset;
public double Percerror;
public bool Forcenorm;
public double Forcesig;
public bool Debug;
public bool ForceXR;
public int Resolution;
public double Totallength;
public double Surflayerlength;
public bool ImpNorm;
public int FitFunc;
public double ParamTemp;
public string version = "0.0.0";
[XmlIgnoreAttribute] private bool disposed = false;
#endregion
public ModelSettings()
{ }
~ModelSettings()
{
Dispose(false);
}
#region Public Methods
public void SetArrays(double[] iQ, double[] iR, double[] iRerr, double[] iQerr)
{
//Blank our arrays if they hold data
if (Q == IntPtr.Zero)
ReleaseMemory();
int size = Marshal.SizeOf(iQ[0]) * iQ.Length;
try
{
QPoints = iQ.Length;
Q = Marshal.AllocHGlobal(size);
Refl = Marshal.AllocHGlobal(size);
ReflError = Marshal.AllocHGlobal(size);
if (iQerr != null)
QError = Marshal.AllocHGlobal(size);
else
QError = IntPtr.Zero;
Marshal.Copy(iQ, 0, Q, iQ.Length);
Marshal.Copy(iR, 0, Refl, iR.Length);
Marshal.Copy(iRerr, 0, ReflError, iRerr.Length);
if (iQerr != null)
Marshal.Copy(iQerr, 0, QError, iQerr.Length);
}
catch (Exception ex)
{
//error handling
}
}
#endregion
#region IDisposable Members
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.disposed)
{
// Call the appropriate methods to clean up
// unmanaged resources here.
// If disposing is false,
// only the following code is executed.
ReleaseMemory();
// Note disposing has been done.
disposed = true;
}
}
private void ReleaseMemory()
{
if (Q != IntPtr.Zero)
{
Marshal.FreeHGlobal(Q);
Marshal.FreeHGlobal(Refl);
Marshal.FreeHGlobal(ReflError);
if (QError != IntPtr.Zero)
Marshal.FreeHGlobal(QError);
}
}
#endregion
}
Your original P/Invoke declaration should be okay, though you shouldn't need UnmanagedType.Struct there at all. The problem seems to be with your C# struct declaration. In particular, why is the order of the field declarations different from the C++ version?

Categories

Resources