I'm writing a wrapper in C# for an unmanaged C DLL. In the DLL I have the following method which returns pointer struct (struct code near end of post):
struct zint_symbol *ZBarcode_Create()
{
struct zint_symbol *symbol = (struct zint_symbol*)calloc(1, sizeof(struct zint_symbol));
if (!symbol) return NULL;
symbol->symbology = BARCODE_CODE128;
strcpy(symbol->fgcolour, "000000");
strcpy(symbol->bgcolour, "ffffff");
strcpy(symbol->outfile, "out.png");
symbol->scale = 1.0;
symbol->option_1 = -1;
symbol->option_3 = 928; // PDF_MAX
symbol->show_hrt = 1; // Show human readable text
return symbol;
}
The extern methods I am using are:
extern struct zint_symbol* ZBarcode_Create(void);
extern int ZBarcode_Encode_and_Buffer(struct zint_symbol *symbol, unsigned char *input, int length, int rotate_angle);
extern int ZBarcode_Encode_and_Print(struct zint_symbol *symbol, unsigned char *input, int length, int rotate_angle);
ZBarcode_Encode_and_Buffer renders an image and saves the bitmap in a variable in my struct called bitmap.
ZBarcode_Encode_and_Print renders an image and saves it to the file system. They both return 0 when they are successful, and a number between 1-8 when they fail. Each time for me, they return 0.
My C# looks like the following:
[DllImport("zint.dll", EntryPoint = "ZBarcode_Create", CallingConvention = CallingConvention.Cdecl)]
public extern static IntPtr Create();
[DllImport("zint.dll", EntryPoint = "ZBarcode_Encode_and_Buffer", CallingConvention = CallingConvention.Cdecl)]
public extern static int EncodeAndBuffer(
ref zint_symbol symbol,
string input,
int length,
int rotate_angle);
[DllImport("zint.dll", EntryPoint = "ZBarcode_Encode_and_Print", CallingConvention = CallingConvention.Cdecl)]
public extern static int EncodeAndPrint(
ref zint_symbol symbol,
string input,
int length,
int rotate_angle);
public static void Render()
{
// call DLL function to generate pointer to initialized struct
zint_symbol s = (zint_symbol)
// generate managed counterpart of struct
Marshal.PtrToStructure(ZintLib.Create(), typeof(zint_symbol));
// change some settings
s.symbology = 71;
s.outfile = "baro.png";
s.text = "12345";
String str = "12345";
// DLL function call to generate output file using changed settings -- DOES NOT WORK --
System.Console.WriteLine(ZintLib.EncodeAndBuffer(ref s, str, str.Length, 0));
// DLL function call to generate output file using changed settings -- WORKS --
System.Console.WriteLine(ZintLib.EncodeAndPrint(ref s, str, str.Length, 0));
// notice that these variables are set in ZBarcode_Create()?
Console.WriteLine("bgcolor=" + s.bgcolour + ", fgcolor=" + s.fgcolour + ", outfile=" + s.outfile);
// these variables maintain the same values as when they were written to in ZBarcode_Create().
if (s.errtxt != null)
Console.WriteLine("Error: " + s.errtxt);
else
Console.WriteLine("Image size rendered: " + s.bitmap_width + " x " + s.bitmap_height);
}
All of the variables in s remain unchanged, even though the DLL is supposed to change some of them such as bitmap, bitmap_width, bitmap_height, etc.
I suspect that there are two copies of zint_symbol in memory; one that my C# code has (created by ZintLib.Create()) and
another one that the DLL is writing to. I am certain that the library works correctly, however.
The C struct:
struct zint_symbol {
int symbology;
int height;
int whitespace_width;
int border_width;
int output_options;
#define ZINT_COLOUR_SIZE 10
char fgcolour[ZINT_COLOUR_SIZE];
char bgcolour[ZINT_COLOUR_SIZE];
char outfile[FILENAME_MAX];
float scale;
int option_1;
int option_2;
int option_3;
int show_hrt;
int input_mode;
#define ZINT_TEXT_SIZE 128
unsigned char text[ZINT_TEXT_SIZE];
int rows;
int width;
#define ZINT_PRIMARY_SIZE 128
char primary[ZINT_PRIMARY_SIZE];
#define ZINT_ROWS_MAX 178
#define ZINT_COLS_MAX 178
unsigned char encoded_data[ZINT_ROWS_MAX][ZINT_COLS_MAX];
int row_height[ZINT_ROWS_MAX]; /* Largest symbol is 177x177 QR Code */
#define ZINT_ERR_SIZE 100
char errtxt[ZINT_ERR_SIZE];
char *bitmap;
int bitmap_width;
int bitmap_height;
struct zint_render *rendered;
};
The C# struct:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct zint_symbol
{
public int symbology;
public int height;
public int whitespace_width;
public int border_width;
public int output_options;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
public String fgcolour;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
public String bgcolour;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string errtxt;
public float scale;
public int option_1;
public int option_2;
public int option_3;
public int show_hrt;
public int input_mode;
public int rows;
public int width;
public int bitmap_width;
public int bitmap_height;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string text;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string primary;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 31684)]
public byte[] encoded_data;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I4, SizeConst = 178)]
public int[] row_height;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string outfile;
public IntPtr bitmap;
public IntPtr rendered;
}
I have written a small Windows forms example (the DLLs are in there and also available here. The library documentation (page 18/61 explains the struct variables) is available here and the entire C source code is available here (zint-2.4.3.tar.gz). Files of particular interest are zint.h, library.c and datamatrix.c. The C source code is the same version of the DLLs.
Edit
The struct layouts appear to be mismatched. sizeof(zint_symbol) in C != sizeof(zint_symbol) in C#.
You have an odd mistake here:
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 25454)]
public byte[] encoded_data;
Which in the .h file is:
#define ZINT_ROWS_MAX 178
#define ZINT_COLS_MAX 178
uint8_t encoded_data[ZINT_ROWS_MAX][ZINT_COLS_MAX];
Just declare it the same way:
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 178 * 178)]
public byte[] encoded_data;
One more mistake, FILENAME_MAX is 260:
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string outfile;
Now you should get a proper match:
static void Main(string[] args) {
var len = Marshal.SizeOf(typeof(zint_symbol)); // 33100
var offs = Marshal.OffsetOf(typeof(zint_symbol), "errtxt"); // 32984
}
and in the C test program:
#include <stddef.h>
//...
int main()
{
size_t len = sizeof(zint_symbol); // 33100
size_t offs = offsetof(zint_symbol, errtxt); // 32984
return 0;
}
Looking at the differences in your P/Invoke declarations, we can see that the successful interface uses the [In, Out] attributes. you should add those to the other function, too.
see here: Are P/Invoke [In, Out] attributes optional for marshaling arrays?
The zint_symbol structure is defined differently in the PDF you linked to. It looks as though you have an incompatibility between your structure and the one the dll version is using.
For instance, in the PDF, scale appears after option_3, whereas you have it before option_1. And the last element in the PDF is bitmap_height, whereas yours continues on after that. There may be other differences, those are just what I noticed.
Although you don't have access to the dll source, in this kind of situation you can test your api by creating a simple dll yourself. Create a simple WinForms solution that has just a form with one button on it, and add a CPP Win32 project to the solution (project type: Win32 Project; application type: DLL). For simplicity, you can just add the struct and a stub function in the existing dllmain.cpp:
struct zint_symbol {
/*
.
.
.
*/
};
extern "C"
{
__declspec(dllexport) int ZBarcode_Encode_and_Print(struct zint_symbol *symbol, unsigned char *input, int length, int rotate_angle)
{
symbol->bitmap_width = 456;
symbol->errtxt[0] = 'e';
symbol->errtxt[1] = 'r';
symbol->errtxt[2] = 'r';
symbol->errtxt[3] = 0;
return 123;
}
}
And in the WinForms button click event handler etc:
public static void doStuff()
{
zint_symbol symbol = new zint_symbol();
int retval = EncodeAndPrint(ref symbol, "input string", 123, 456);
Console.WriteLine(symbol.bitmap_width);
Console.WriteLine(symbol.errtxt);
}
Set a breakpoint in the dll to set/inspect other values as desired. To do this, you'll need to set "Enable unmanaged code debugging" on your C# project properties debug tab.
Using the above code with the C and C# structures you posted, I found no problems; values I set in the dll were apparent to the C# in the struct.
You can experiment further using this approach, but the bottom line is, you need the right struct definition for the dll version you're using, and currently your struct doesn't match the PDF you linked to. Hopefully you can find the right version of the dll, or the right struct definition for your existing dll.
Related
From the beginning, sorry for the weird title, but i really don't know how to describe this problem in short phrase.
I'm trying to wrapp a c++ DLL using pinvoke method. I have this function:
C++ header:
int32_t __cdecl ShowAllCharacters(Uint32Array *Image);
C#:
[DllImport(#"x86\OCR.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int ShowAllCharacters(ref IntPtr image);
Where IntPtr image leads to following struct:
C++ header:
typedef struct {
int32_t dimSizes[2];
uint32_t elt[1];
} Uint32ArrayBase;
typedef Uint32ArrayBase **Uint32Array;
C#:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct Uint32Array
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public int[] dimSizes;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public uint[] elt;
}
Basically function return Uint32Array struct, which represents array of uint elements. In Uint32Array, dimSizes is array length (dimSizes elements need to be multiplied to receive size) and elt is the first element of the array. That means, this uint array can have dynamic length.
Now my usage:
[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
public static extern void CopyMemory(IntPtr Destination, IntPtr Source, uint Length);
unsafe public static void ShowAllCharacters()
{
IntPtr ptr = IntPtr.Zero;
OCRPinvoke.ShowAllCharacters(ref ptr);
IntPtr imgPP = (IntPtr)Marshal.PtrToStructure(ptr, typeof(IntPtr));
Uint32Array img = (Uint32Array)Marshal.PtrToStructure(imgPP, typeof(Uint32Array));
uint[] dest = new uint[img.dimSizes[1] * img.dimSizes[0]];
fixed (uint* arrPtr = img.elt)
{
fixed (uint* destPtr = dest)
{
CopyMemory((IntPtr)destPtr, (IntPtr)arrPtr, (uint)dest.Length * sizeof(uint)); // Access violation reading location
}
}
}
My assumption is that this error is due to memory override before i'm able to copy uint array to managed array. Why? I know by fact that in some conditions, size of 'elt' array should be 5038848. If i'm setting SizeConst of the elt variable to 5038848, CopyMemory pass without exception
internal struct Uint32Array
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public int[] dimSizes;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5038848)]
public uint[] elt;
}
I have also try to copy array by iterating through loop. Every time, accessing elt elements by index crashes on different index.
My question is if there is any way to lock some range of memory until i copy my array and then release it to being override by other processes?
I was able to solve this problem, inspired by #David Heffernan. He ask why marshaling manually?
Knowing that Uint32Array is sequential, we can read every value directly from pointers and then use Marshal.Copy to receive final managed array:
IntPtr ptr = IntPtr.Zero;
OCRPinvoke.ShowAllCharacters(ref ptr);
imagePtr = Marshal.ReadIntPtr(ptr);
int height = Marshal.ReadInt32(ptr);
int width = Marshal.ReadInt32(ptr + sizeof(int));
int[] img = new int[width * height];
Marshal.Copy(ptr + sizeof(int) * 2, img, 0, img.Length);
I work on c++ dll and have any problem!
my header file is like this
struct ST_DevInfo
{
EN_DevType de_type;
int screen_width;
int screen_height;
char dev_name[256];
char id[14];
char sboox_version[16];
char fpga_version[16];
};
extern "C" __declspec(dllexport) int CB_GetDeviceList(ST_DevInfo* buff,int length);
and c++ code
int CB_GetDeviceList(ST_DevInfo* buff,int length)
{
buff = (ST_DevInfo *)malloc(sizeof(ST_DevInfo) * length);
return GetDeviceList(buff, length);
}
now i use this function in c# like this
[StructLayout(LayoutKind.Sequential)]
struct ST_DevInfo
{
[MarshalAs(UnmanagedType.I4)]
public EN_DevType de_type;
[MarshalAs(UnmanagedType.I4)]
public int screen_width;
[MarshalAs(UnmanagedType.I4)]
public int screen_height;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = 256)]
public char[] dev_name;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = 14)]
public char[] id;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = 16)]
public char[] sboox_version;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = 16)]
public char[] fpga_version;
};
[DllImport(dllName, EntryPoint = "CB_GetDeviceList", SetLastError = true, ExactSpelling = true,
CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
public static extern
int CB_GetDeviceList([MarshalAs(UnmanagedType.LPArray)] ref ST_DevInfo[] buff,
int length);
and finally i use this function in my program like this
ST_DevInfo[] buff = new ST_DevInfo[dev_length];
int ret = BBIA.CB_GetDeviceList( ref buff, dev_length);
but after retrieve from CB_GetDeviceList my buff variable assigned but not has any value(contain 0x00). i test it in c++ and it work fine!!
i think a have problem on this line
buff = (ST_DevInfo *)malloc(sizeof(ST_DevInfo) * length);
In your C# code you're doing this:
ST_DevInfo[] buff = new ST_DevInfo[dev_length];
int ret = BBIA.CB_GetDeviceList( ref buff, dev_length);
Which is allocating an array, and passing that (by a double pointer since you have ref), to the C++ code.
In your C++ code you are doing:
int CB_GetDeviceList(ST_DevInfo* buff,int length)
{
buff = (ST_DevInfo *)malloc(sizeof(ST_DevInfo) * length);
return GetDeviceList(buff, length);
}
Which is taking an array (and not as a double pointer) and changing that (local) pointer to point to some new memory. So your original array from C# will never be touched by the C++ code.
First, remove the malloc call completely. Then change your pinvoke to something like:
[DllImport( ... )]
public static extern int CB_GetDeviceList( [In, Out] ST_DevInfo[] buff, int length );
And call as before but without the ref. In, Out is needed to tell the marshaller that you expect the pinvoke call to modify the data. They are not necessary in every case, but in your case I'm not a 100% sure so I'd keep them just in case.
I'm using a DLL written in C++ in my C# project by using DllImport and one of the functions I'm using looks like this:
[DllImport("dds.dll", CharSet = CharSet.Auto)]
private static extern int Par(
ddTableResults2 tableResult,
ref parResults ParResult,
int vul
);
The parResults struct is defined in C++ like this:
struct parResults {
/* index = 0 is NS view and index = 1
is EW view. By 'view' is here meant
which side that starts the bidding. */
char parScore[2][16];
char parContractsString[2][128];
};
The start of the C++ function
int STDCALL Par(struct ddTableResults * tablep, struct parResults *presp,
int vulnerable)
How should I define the above struct in C# to able to send that struct as en reference into the DLL function?
This is what I have tried but don't work at all and I just get a Access Violation Error
[StructLayout(LayoutKind.Sequential)]
public struct parResults
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public char[,] parScore;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
public char[,] parContractsString;
public parResults(int x)
{
parScore = new char[2,16];
parContractsString = new char[2,128];
}
}
This is quite a tricky struct to marshal in C#. There are various ways to attempt it, but I think that it will be cleanest to represent the character arrays as byte arrays and marshal to and from strings by hand. Here is a demonstration of what I mean:
C++
#include <cstring>
struct parResults {
char parScore[2][16];
char parContractsString[2][128];
};
extern "C"
{
__declspec(dllexport) void foo(struct parResults *res)
{
strcpy(res->parScore[0], res->parContractsString[0]);
strcpy(res->parScore[1], res->parContractsString[1]);
}
}
C#
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
class Program
{
[StructLayout(LayoutKind.Sequential)]
class parResults
{
private const int parScoreCount = 2;
private const int parScoreLen = 16;
private const int parContractsStringCount = 2;
private const int parContractsStringLen = 128;
[MarshalAs(UnmanagedType.ByValArray,
SizeConst = parScoreCount * parScoreLen)]
private byte[] parScoreBuff;
[MarshalAs(UnmanagedType.ByValArray,
SizeConst = parContractsStringCount * parContractsStringLen)]
private byte[] parContractsStringBuff;
public string getParScore(int index)
{
string str = Encoding.Default.GetString(parScoreBuff,
index * parScoreLen, parScoreLen);
int len = str.IndexOf('\0');
if (len != -1)
str = str.Substring(0, len);
return str;
}
public void setParScore(int index, string value)
{
byte[] bytes = Encoding.Default.GetBytes(value);
int len = Math.Min(bytes.Length, parScoreLen);
Array.Copy(bytes, 0, parScoreBuff, index * parScoreLen, len);
Array.Clear(parScoreBuff, index * parScoreLen + len,
parScoreLen - len);
}
public string parContractsString(int index)
{
string str = Encoding.Default.GetString(parContractsStringBuff,
index * parContractsStringLen, parContractsStringLen);
int len = str.IndexOf('\0');
if (len != -1)
str = str.Substring(0, len);
return str;
}
public void setParContractsString(int index, string value)
{
byte[] bytes = Encoding.Default.GetBytes(value);
int len = Math.Min(bytes.Length, parContractsStringLen);
Array.Copy(bytes, 0, parContractsStringBuff,
index * parContractsStringLen, len);
Array.Clear(parContractsStringBuff,
index * parContractsStringLen + len,
parContractsStringLen - len);
}
public parResults()
{
parScoreBuff = new byte[parScoreCount * parScoreLen];
parContractsStringBuff =
new byte[parContractsStringCount * parContractsStringLen];
}
};
[DllImport(#"...", CallingConvention = CallingConvention.Cdecl)]
static extern void foo([In,Out] parResults res);
static void Main(string[] args)
{
parResults res = new parResults();
res.setParContractsString(0, "foo");
res.setParContractsString(1, "bar");
foo(res);
Console.WriteLine(res.getParScore(0));
Console.WriteLine(res.getParScore(1));
Console.ReadLine();
}
}
}
Here I've used a class to represent the struct. Since a class in C# is a reference, we don't declare the parameters of that type with ref. I've also used __cdecl for convenience to avoid having to work out what the decorated name of the function would be. But your library used __stdcall and so you need to stick to that.
The class demonstrates sending data in both directions. You could probably simplify the code if the data flow was more restricted.
EDIT: I oversimplified my example... In the real code I was assigning values to strMyStringx without correctly using wcscpy_s, so instead of assigning the values I was just passing the pointer, which was out of scope by the time the values were being marshaled into managed code...
I'm trying to marshal a struct with three string properties from C to C#, but I can't get the definition of the struct right in C#. All of the properties print as garbage. Am I marshaling wrong or do my properties have the wrong type?
My custom structure:
typedef struct _MY_STRUCT_STRING {
LPWSTR strMyString1;
LPWSTR strMyString2;
LPWSTR strMyString3;
}MY_STRUCT_STRING, *PMY_STRUCT_STRING;
My C function returns an array of pointers to this struct:
bool bEnumerateString(OUT LONG &i_arr_size, OUT PMY_STRUCT_STRING* &pArrStringStruct)
{
// [...] function simplified to demonstrate building a pointer to an array of struct*
long i_arr_size = 3
PMY_STRUCT_STRING *ptr_arr_string = (PMY_STRUCT_STRING *)malloc(sizeof(PMY_STRUCT_STRING)* i_arr_size);
for (int i = 0; i < i_arr_size; i++) {
ptr_arr_string[i] = (PMY_STRUCT_STRING)malloc(sizeof(MY_STRUCT_STRING));
ptr_arr_string[i]->strMyString1 = L"String 1"; // This would work. In the real code I was assigning values from another array and mistakenly passed the pointer rather than doing wcscpy_s
ptr_arr_string[i]->strMyString2 = L"String 2";
ptr_arr_string[i]->strMyString3 = L"String 3";
}
pArrStringStruct = ptr_arr_string;
return true;
}
C#:
//Import the DLL with my function
[DllImport("My.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "bEnumerateString")]
internal static extern bool bEnumerateString(out long count, out IntPtr pArrStringStruct);
// Define the C# equivalent of the C struct
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct MY_STRUCT_STRING
{
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 8)]
public string strMyString1;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 8)]
public string strMyString1;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 8)]
public string strMyString1;
}
[...]
// Code to marshal (try... catch etc removed for succinctness)
IntPtr pArrStruct = IntPtr.Zero;
long lCount = 0;
bool bResult = false;
bResult = bEnumerateString(out lCount, out pArrStruct);
if (!bResult)
{
// Marshal to deref pointer
IntPtr[] pArrStructList = new IntPtr[lCount];
for (ulong i = 0; i < (ulong)lCount; i++)
{
pArrStructList[i] = Marshal.ReadIntPtr(pArrStruct, Marshal.SizeOf(typeof(IntPtr)) * (int)i);
}
// Marshal pointers to struct
var lstMyStringStrct = new List<MY_STRUCT_STRING>(pArrStructList.Length);
foreach (IntPtr ptr in pArrStructList)
{
lstMyStringStrct.Add((MY_STRUCT_STRING)Marshal.PtrToStructure(ptr, typeof(MY_STRUCT_STRING)));
}
// Enumerate struct
foreach (MY_STRUCT_STRING myStr in lstMyStringStrct)
{
// All of these outputs are garbage
Console.WriteLine("strMyString1: " + myStr.strMyString1);
Console.WriteLine("strMyString2: " + myStr.strMyString2);
Console.WriteLine("strMyString3: " + myStr.strMyString3);
}
}
I see one problem. You're C++ structure uses LPWSTR (pointers) whereas you're C# code is expecting fixed size char arrays.
Change your strings from:
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 8)]
public string strMyString1;
which would be used when the C++ structure was defined like:
char strMyString1[8];
to:
[MarshalAsAttribute(UnmanagedType.LPWStr)]
public string strMyString1;
I’ve just joined the site, and this is my first posting here.
I could really do with some help calling an unmanaged DLL function from my C# application. I've been searching around, and have found various similar questions, but nothing which quite fits what I need.
The function I need to call is:
int DevConfig(struct A *a, int b, unsigned int c, BOOL *d);
Where A is defined as:
struct A
{
double f[4];
unsigned int df[4];
unsigned int dfn[4];
double *dc[4];
float dg[4];
float g;
UCHAR gs;
};
I’m getting a bit stuck and confused as to how to handle this in the C#. I think I need to do something like this, but I know it’s not quite right:
[StructLayout(LayoutKind.Sequential)]
public struct A
{
public double[] f = new double[4];
public uint[] df = new uint[4];
public uint[] dfn = new uint[4];
public double[,] dc = new uint[4,256];
public float[] dg = new float[4];
public float g;
public byte gs;
}
[DllImport("DevControl.dll")]
static extern int DevConfig (ref A a, int b, uint c, bool[] d);
I think this is the correct approach, but I’m confused about the array of pointers to doubles (double *dc[4]), and also the pointer to an array of Boolean (BOOL *d).
Please could someone help me get this right?
Edit with more info:
In the case of this function, data flows from the C# to the DLL. That is, the C# allocates the memory.
I was thinking of specifing dc as follows, unless it's easier to do it another way:
double[,] dc = new double[4, 256];
The size of this array is always 4 x 256. Somehow I need to marshal this into the IntPtr[] field of the struct?
And for the bool array:
bool[] d = new bool[8];
The length of this array may vary, it is not necessarily always 8 (but the DLL knows how long it is via one of the other parameters passed in).
The struct needs to look like this:
public struct A
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
public double[];
[MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
public uint[] df;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
public uint[] dfn;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
public IntPtr[] dc;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
public float[] dg;
public float g;
public byte gs;
}
The key point is that these arrays are stored inline in the struct and therefore must be marshalled using UnmanagedType.ByValArray.
The function declaration should be:
[DllImport("DevControl.dll", CallingConvention=CallingConvention.Cdecl)]
static extern int DevConfig (ref A a, int b, uint c, bool[] d);
This does assume that the final parameter is an array rather than a pointer to a single boolean. Only you know that. Note also the calling convention. As written, your unmanaged function is cdecl. And you do need to specify that in the p/invoke.
Now, the problem that you will face is the 2D array in the struct, dc. The only way to handle that is to manually allocate and marshal unmanaged memory. You cannot use a double[,] because that simply does not map to double*[]. Hence you need IntPtr[] and then some use of the Marshal class to finish the job. Exactly how all that is to be done cannot be discerned from the question. It does not specify who allocates the memory.
You state in an update that you want to allocate the double arrays in the C# to pass to the unmanaged code. Some example code for that:
IntPtr[] CreateUnmanagedArrays(double[][] arr)
{
IntPtr[] result = new IntPtr[arr.Length];
for (int i=0; i<arr.Length; i++)
{
result[i] = Marshal.AllocCoTaskMem(arr[i].Length*sizeof(double));
Marshal.Copy(arr[i], 0, result[i], arr[i].Length);
}
return result;
}
void DestroyUnmanagedArrays(IntPtr[] arr)
{
for (int i=0; i<arr.Length; i++)
{
Marshal.FreeCoTaskMem(arr[i]);
arr[i] = IntPtr.Zero;
}
}
[StructLayoutAttribute(LayoutKind.Sequential)]
public struct A
{
[MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.R8)]
public double[] f;
[MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.U4)]
public uint[] df;
[MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.U4)]
public uint[] dfn;
[MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.SysUInt)]
public System.IntPtr[] dc;
[MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.R4)]
public float[] dg;
public float g;
public byte gs;
}
[DllImportAttribute("DevControl.dll", EntryPoint = "DevConfig")]
public static extern int DevConfig(ref A a, int b, uint c, ref int d);