I want to call a function of a library from Net6 C#. The function expects a pointer to a structure. Inside the structure is a variable-length array. I donĀ“t know how to marshal this array correctly.
The following code is test code to demonstrate the problem.
This is the header of the C-library:
typedef struct SubTest
{
char *name;
int num1;
} SubTest;
typedef struct Test
{
char *name;
int num1;
float num2;
SubTest *testarray;
int testarraylen;
} Test;
void PrintTestStruct(Test *teststruct);
This is the implementation of PrintTestStruct:
void PrintTestStruct(Test *teststruct)
{
printf("Name: %s \n", teststruct->name);
printf("Num1: %d \n", teststruct->num1);
printf("Num2: %f \n", teststruct->num2);
printf("Array content: \n");
for(int i=0; i < teststruct->testarraylen; i++)
{
printf("Array Name %d: %s \n", i,teststruct->testarray[i].name);
printf("Array Number %d: %d \n", i,teststruct->testarray[i].num1);
}
}
This is the definition in C#:
[StructLayout(LayoutKind.Sequential)]
public struct Test
{
public string name;
public int num1;
public float num2;
public IntPtr testarray;
public int testarraylen;
}
[StructLayout(LayoutKind.Sequential)]
public struct SubTest
{
public string name;
public int num1;
}
[DllImport("cshared")]
private static extern void PrintTestStruct(ref Test teststruct);
This is what I have tried:
public static void Main(string[] args)
{
var data = new Test();
data.name = "Hello from C#";
data.num1 = 5;
data.num2 = 3.2f;
data.testarraylen = 2;
var field1 = new SubTest();
field1.name = "Testarray 1";
field1.num1 = 1;
var field2 = new SubTest();
field2.name = "Testarray 2";
field1.num1 = 2;
SubTest[] subarray = {field1, field2};
IntPtr mem = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(SubTest)) * data.testarraylen);
for (int ix = 0; ix < 2; ix++)
{
Marshal.StructureToPtr<SubTest>(subarray[ix], mem, false);
mem += Marshal.SizeOf(subarray[ix]);
}
data.testarray = mem;
PrintTestStruct(ref data);
}
Unfortunately the result is garbage, the data of the array is not printed correctly. I followed all suggestions I found on stackoverflow, but could not get any better results.
Question:
Is there a way to fix this ?
As I have access to the source code of the C library, is there a better way to transmit these kind of variable-length arrays between C# and C ? Can I change the C library in some way to make this easier ?
The problem is you've changed the address of mem, but you didn't revert it.
mem += Marshal.SizeOf(subarray[ix]);
So a solution is adding the offset address to men instead of changing in the loop.
var sizeOfSubTest = Marshal.SizeOf(typeof(SubTest));
IntPtr mem = Marshal.AllocCoTaskMem(sizeOfSubTest * data.testarraylen);
for (int ix = 0; ix < 2; ix++)
{
Marshal.StructureToPtr(subarray[ix], mem + sizeOfSubTest * ix, false);
}
Can I change the C library in some way to make this easier ?
IMO this is easiest.
Related
I have this code (snipet) and it runs through and works, but after leaving the whole procedure procedureInAClass() and going on with next code it crashes: "Bad_module_error". I don't see the error.
public void procedureInAClass(){ //this code is in a Class, it works but after leaving whole procedure it crashes
char** comment=(char**)Marshal.AllocHGlobal(sizeof(char*)); //comment is in Code a class Member
string aval="some chars in a string";
SToCP(val, comment) ; //value of String to *comment
CPToS(comment); //**comment to string
}
//this part is in a static class
public static void SToCP(string s, char** c)//writes string s in *c
{
*c = SToCP(s);
}
public static char* SToCP(string s)
{
char* ret= (char*)Marshal.AllocHGlobal( sizeof(char) * (s.Length +1));
int i;
byte se = sizeof(char);
for (i = 0; i < s.Length; i++)
*(ret + se * i) = s[i];
*(ret + s.Length * se) = '\0';
return ret;
}
public static String CPToS(char** c)
{
return CPToS(*c); //passing the pointer char* which is holded by char** c
}
public static String CPToS(char* c)
{
string ret = "";
byte s = sizeof(char);//char is two bytes long
int i = 0;
while (*(c + s * i) != '\0')//zero terminated string
ret += *(c + s * i++);
return ret;
}
Your problem is that you are multiplying your index i by the sizeof(char*) but treating that as a char*. Adding 1 to a char* adds the size of a char (2 bytes) to the pointer. You were writing every other character, then continuing to overwrite memory after your string allocation because of going too far.
Try this instead:
public static char* SToCP(string s) {
char* ret = (char*)Marshal.AllocHGlobal(sizeof(char) * (s.Length + 1));
char* p = ret;
for (int i = 0; i < s.Length; i++)
*(p++) = s[i];
*(p++) = '\0';
return ret;
}
Of course your CPToS method is also wrong, so if you fix SToCP to correctly not write over other character, you will need to fix CPToS as well otherwise it will return the wrong string back.
Here's a fixed version of CPToS to match:
public static String CPToS(char* c) {
string ret = "";
int i = 0;
while (*(c + i) != '\0')//zero terminated string
ret += *(c + i++);
return ret;
}
SToCP was causing the stack to be screwed up and causing the crash, though.
The issue is passing a 2D string array (non-blitable) from managed C# to unmanaged C++.
I not sure if the DllImport and MarshalAs conventions are fully correct for this type of string array. Maybe, the pointer / memory allocation definition has a missing attribute. Many thanks for your comments.
public struct TestStruct
{
public string[,] stringArray;
}
[DllImport("C:\\Users\\Win32Project2.dll",
EntryPoint = "DDentry",
CallingConvention = CallingConvention.StdCall)]
public static extern void DDentry
(
[In][MarshalAs(UnmanagedType.LPArray,
ArraySubType = UnmanagedType.LPStr)] string[,] arrayReadDat, int iDim1, int iDim2
);
private void button6_Click_1(object sender, EventArgs e)
{
TestStruct arrayReadDat = new TestStruct();
arrayReadDat.stringArray = new string[lastRow+1, lastCol+1];
for (int i = 2; i <= lastRow; i++)
{
for (int j = 1; j <= lastCol; j++)
{
arrayReadDat.stringArray[i, j] = i;
}
}
int size = Marshal.SizeOf(typeof(TestStruct));
IntPtr strPointer = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(arrayReadDat, strPointer, false);
DDentry(arrayReadDat.stringArray, lastRow+1, lastCol+1);
Marshal.FreeHGlobal(strPointer);
}
Here the unmanaged C++ code, which don not show the data from the C# code:
_declspec(dllexport) void DDentry(string *p2DIntArray, int iDim1, int iDim2)
{
int iIndex = 0;
for (int i = 2; i <= iDim1; i++)
{
for (int j = 1; j <= iDim2; j++)
{
arrayREAD[i][j] = p2DIntArray[iIndex++];
}
}
}
It looks like, that instead of importing the DLL in C++ code and exporting it in C# code, You are doing it exactly vice versa.
An example how to call a managed DLL from native Visual C++ code can be found here:
https://support.microsoft.com/en-us/kb/828736
It is written for VS2005, but the overall logic should be same in newer VS versions also.
this is a very clean and nice solution to marsahall a struct array from unmanaged C++ code.
it is allmost perfect solution when it comes to simplicity, it took me a while to get to that level of understanding the concept, so that in few lines of code, as you can see C# Main(), i have a populated array of struct ready to be 'harvested'..
typedef struct {
int Id;
BSTR StrVal;
}Package;
extern "C" __declspec(dllexport) void dodata(int requestedLength,int StringSize, Package **Packs){
int count;
count=0;
*Packs = (Package*)LocalAlloc(0, requestedLength * sizeof(Package));
Package *Cur = *Packs;
while(count!= requestedLength)
{
Cur[count].StrVal = NULL;
Cur[count].Id = count;
Cur[count].StrVal=SysAllocString(L"abcdefghij");
Cur[count].StrVal[StringSize-1]=count+'0';
++count;
}
}
C#
[DllImport(#"ExportStructArr.dll", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
public static extern void dodata(int requestedLength, int StringSize, out IntPtr csPkPtr);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct csPk
{
public int V;
[MarshalAsAttribute(UnmanagedType.BStr, SizeConst = 10)]
public string testStr;
}
static void Main(string[] args){
int ArrL = 16000;
csPk[] Cpk = new csPk[ArrL];
IntPtr CpkPtr = IntPtr.Zero;
int szPk = Marshal.SizeOf(typeof(csPk));
dodata(ArrL, 10, out CpkPtr);
}
now all i have to do is :
for (int i = 0; i < Cpk.Length; i++)
{
Cpk[i] = (csPk)Marshal.PtrToStructure(new IntPtr(CpkPtr.ToInt32() + (szPk * i)), typeof(csPk));
}
the solution is quite easy as you can see
the question is using unsafe or any kind of transformation to the data, even going down to bytes...
how could i optimize it to perform better returning the data ?
Edit:
links i have tried to learn from other answers here in SO:
answer from Hans Passant
answer from AbdElRaheim
answer from supercat
also tried google : wikipedia , a github post by stephentoub
this is a complete blazing fast solution to populate a list of objects, i did my best
and i will be happy to have comments and suggestions.
c++
typedef struct _DataPacket
{
BSTR buffer;
UINT size;
} DataPacket;
extern "C" __declspec(dllexport) void GetPacksUnsafe( int size, DataPacket** DpArray )
{
int szr = size;int count=0;
*DpArray = (DataPacket*)CoTaskMemAlloc( szr * sizeof( DataPacket ));
if ( DpArray != NULL )
{
DataPacket* CurPack = *DpArray;
for ( int i = 0; i < szr; i++, CurPack++ )
{
CurPack->size = i;
CurPack->buffer = SysAllocString(L"SomeText00");
CurPack->buffer[9]=i+'0';
}
}
}
C#
[DllImport(#"ExportStructArr.dll", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
public static extern void GetPacksUnsafe(int size, PackU** outPackUArr);
[StructLayout(LayoutKind.Sequential)]
public unsafe struct PackU
{
public char* StrVal;
public int IntVal;
}
public static unsafe List<PackU> PopulateLstPackU(int ArrL)
{
PackU* PackUArrOut;
List<PackU> RtLstPackU = new List<PackU>(ArrL);
GetPacksUnsafe(ArrL, &PackUArrOut);
PackU* CurrentPack = PackUArrOut;
for (int i = 0; i < ArrL; i++, CurrentPack++)
{
RtLstPackU.Add(new PackU(){ StrVal = CurrentPack->StrVal, IntVal=CurrentPack->IntVal});
}
Marshal.FreeCoTaskMem((IntPtr)PackUArrOut);
for (int i = 0; i < 10; i++)
{
Console.WriteLine("{0}", new string(RtLstPackU[i].StrVal));
}
return RtLstPackU;
}
using the code is as simple as it could possibly be
static unsafe void Main(string[] args)
{
int ArrL = 100000;
List<PackU> LstPackU;
LstPackU = PopulateLstPackU(ArrL);
}
there you have a list of custom data as fast as a bullet..
EDIT
using pointers instead of strings :
typedef struct _DataPackCharPnt
{
char* buffer;
UINT IntVal;
} DataPackCharPnt;
extern "C" __declspec(dllexport) void GetPacksPnt( int size, DataPackCharPnt** DpArrPnt )
{
int count = 0;
int TmpStrSize = 10;
*DpArrPnt = (DataPackCharPnt*)CoTaskMemAlloc( size * sizeof( DataPackCharPnt ));
DataPackCharPnt* CurPackPnt = *DpArrPnt;
char dummyStringDataObject[]= "abcdefgHi";
for ( int i = 0; i < size; i++,CurPackPnt++ )
{
dummyStringDataObject[9] = i+'0';
CurPackPnt->IntVal=i;
CurPackPnt->buffer = (char*)malloc(sizeof(char)*TmpStrSize);
strcpy(CurPackPnt->buffer, dummyStringDataObject);
}
}
reduced the time taken from 11 to 7 ms populating 100k elements
is there any part of creating the buffer i could omit ?
the duty of dummyStringDataObject is to simulate work, say getting a file name then set the buffer with its value, so except for this extra time which is the whole purpose of this function, to return some unknown values and lengths of the strings...
could you optimize it even further ?
How to convert the IntPtr to an array. Actually I called the function from unmanaged dll. It returns IntPtr. Now I need to convert it to an array. Please any one give an idea.Code snippet is below.
Unmanaged function declared
[DllImport("NLib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern unsafe IntPtr N_AllocPt1dArray(NL_INDEX n, ref stacks S);
Calling the function
void Function1()
{
IntPtr PPtr=N_AllocPt1dArray(n, ref S);
}
Now I need to convert PPtr to an array(array is demo[]).where demo is defined by
public unsafe struct demo
{
public int x ;
public int y ;
public int z ;
}demo DEMO;
Try this:
array[0] = (demo)System.Runtime.InteropServices.Marshal.PtrToStructure(PPtr , typeof(demo));
UPDATE :
Solution 2 of this page is what you need.
It depends of what type of data you are pointing to, the next code get an array of strings from an IntPtr:
nstring is the number of elements you expect to get.
You can modify the code to satisfy your needs, but this can give you an idea of how to retrive data from an IntPtr in an unmaganed block code.
private string[] ConvertIntPtrToStringArray(IntPtr p, int nstring)
{
//Marshal.ptr
string[] s = new string[nstring];
char[] word;
int i, j, size;
unsafe
{
byte** str = (byte**)p.ToPointer();
i = 0;
while (i < nstring)
{
j = 0;
while (str[i][j] != 0)
j++;
size = j;
word = new char[size];
j = 0;
while (str[i][j] != 0)
{
word[j] = (char)str[i][j];
j++;
}
s[i] = new string(word);
i++;
}
}
return s;
}
cheers,
Kevin
I have C code which will be build as a dynamic library (DLL) , which i would like
to call C function from C# using dLL created from the C code
C code :
struct data
{
char data_val1[100];
float data_val2;
float data_val3[50];
};
typedef struct data data;
#ifdef __cplusplus
extern "C" __declspec(dllexport) void cfun_call(data *pdata,long count);
#endif
#ifdef __cplusplus
extern "C"
{
#endif
__declspec(dllexport) void cfun_call(data *pdata,long count)
{
int x = 0;
for(x=0;x<count;x++)
{
data[x].data_val2 = (pdata->data_val3[49] + pdata->data_val3[48]) / 2.0;
}
}
#ifdef __cplusplus
}
#endif
Here i wanted to import the function "cfun_call" in C# code, and pass values to the fucntion call
and manipulate the passed values in C function from the dll and wanted to display the updated values
back to the C# code and display it, Since my expertise in C# is limited i need some help to solve this issue
C# code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
class Program
{
public class data
{
public char[] data_val1 = new char[100];
public float data_val2;
public float[] data_val3 = new float[50];
};
[DllImport("mycdll.dll", EntryPoint = "cfun_call", CallingConvention = CallingConvention.Cdecl, ExactSpelling = false)]
// void cfun_call(data *pdata,long count); //C function for reference
public static extern void cfun_call([In, Out] data[] ouputdata, long count);
static void Main(string[] args)
{
data[] objData = new data[10];
for (int i = 0; i < 10; i++)
{
//Fill the data in objitemData
objData[i] = new objData();
for (int j = 0; j < 100; j++)
{
objData[i].data_val1[j] = '\0';
}
for (int k = 0; k < 50; k++)
{
objData[i].data_val3[k] = 20.00;
}
objData[i].data_val2 = 0.00;
}
cfun_call(objData,10); //Making call to C dll function
for (int i = 0; i < 10; i++)
Console.WriteLine("{0} ", objData[i].data_val2);
Console.WriteLine("");//new line
Console.ReadLine();
}
}
Here the values (objData) passed from C# function is not updated by using the C dll fucntion , I am not sure why.
Can anyone point me to right direction ?
Edit 1:
I have updated code as suggested ,
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct data
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
public char[] data_val1;
public float data_val2;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 50)]
public float[] data_val3;
};
Initialized struct elements like below ,
data[] objData = new data[10];
for (int i = 0; i < 10; i++)
{
//Fill the data in objitemData
objData[i] = new objData();
for (int j = 0; j < 100; j++)
{
objData[i].data_val1[j] = '\0'; //I am getting exception here
}
for (int k = 0; k < 50; k++)
{
objData[i].data_val3[k] = 20.00;
}
objData[i].data_val2 = 0.00;
}
Runtime i am getting null ptr exception , like
An unhandled exception of type 'System.NullReferenceException' occurred in mybinary.exe
Additional information: Object reference not set to an instance of an object.
How to initialize the struct array elements properly in manged code ?
Edit 2:
Hi one more question , when i add , objData[i].data_val3[k] = randomData; //randomvalues, It is not updated when making cfun_call while using contnt value it is updated why ?
Your translation of the struct is incorrect. You need it to be like so:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct data
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
public char[] data_val1;
public float data_val2;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 50)]
public float[] data_val3;
};
You have to make this a struct since you need to pass an array of values. Your declaration using class leads to you passing an array of references.
You will need to initialize the arrays explicitly now. That might look like so:
data[] objData = new data[10];
for (int i = 0; i < 10; i++)
{
objData[i].data_val1 = new char[100];
objData[i].data_val2 = 0.00;
objData[i].data_val3 = new float[50];
for (int k = 0; k < 50; k++)
{
objData[i].data_val3[k] = 20.0f;
}
}
Further, C++ long is 32 bits wide, but C# long is 64 bits wide. You therefore have a mismatch. Your p/invoke should be:
[DllImport("mycdll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void cfun_call(
[In, Out] data[] ouputItem_data,
int count
);