C# struct variables remain null when passed into unmanaged C DLL functions - c#
Problem:
I am writing a C# wrapper for an unmanaged C DLL. The DLL contains a struct zint_symbol, which contains a variable char[] *bitmap. In my C# struct I have public byte[] bitmap;.
This struct is passed in to an external function ZBarcode_Encode_and_Buffer which is supposed to render a PNG image and write the bitmap data to bitmap in zint_symbol.
When I invoke ZBarcode_Encode_and_Buffer and check bitmap, it remains null.
In fact, every variable in my C# struct that is supposed to be written by the DLL remains null.
What I've tried:
Marshalling bitmap as a fixed-size array with a very large constant, i.e.,
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 25454)]
public byte[] bitmap;
...which yields a SystemAccessViolation:
An unhandled exception of type 'System.AccessViolationException' occurred in mscorlib.dll
Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Changing the type of ibtmap to char[] and string. This changed nothing.
Adding the [In,Out] decorator to the struct parameter I am passing into my C# function.
Changing the type of bitmap from byte[] to IntPtr.
What I cannot do:
I cannot seem to compile the library, despite having the source (the project is open source but abandoned years ago). This means I cannot change the C code.
Code/Working example
A small but complete working example can be downloaded here.
C# class containing my struct:
class ZintLib
{
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 = 256)]
public string outfile;
public float scale;
public int option_1;
public int option_2;
public int option_3;
public int show_hrt;
public int input_mode;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string text;
public int rows;
public int width;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string primary;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 25454)]
public byte[] encoded_data;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I4, SizeConst = 178)]
public int[] row_height;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string errtxt;
public byte[] bitmap;
public int bitmap_width;
public int bitmap_height;
public IntPtr rendered;
}
[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(
[In, Out] ref zint_symbol symbol,
String input,
int length,
int rotate_angle);
}
The function calling EncodeAndBuffer:
// call DLL function to generate pointer to initialized struct
ZintLib.zint_symbol s = (ZintLib.zint_symbol)
// generate managed counterpart of struct
Marshal.PtrToStructure(ZintLib.Create(), typeof(ZintLib.zint_symbol));
// change some settings
s.symbology = 71;
s.outfile = "datamatrix.png";
// DLL function call to generate output file using changed settings -- WORKS --
//System.Console.WriteLine(ZintLib.EncodeAndPrint(ref s, "12345", 5, 0));
// DLL function to generate data in s.bitmap, s.bitmapheight, s.bitmapwidth -- DOES NOT WORK managed struct is unaltered --
System.Console.WriteLine(ZintLib.EncodeAndBuffer(ref s, (String)"12345", 5, 0));
if (s.bitmap == null)
Console.WriteLine("bitmap is null.");
else
Console.WriteLine("bitmap is not null.");
The struct and export methods in C:
struct zint_symbol {
int symbology;
int height;
int whitespace_width;
int border_width;
int output_options;
char fgcolour[10];
char bgcolour[10];
char outfile[256];
float scale;
int option_1;
int option_2;
int option_3;
int show_hrt;
int input_mode;
unsigned char text[128];
int rows;
int width;
char primary[128];
unsigned char encoded_data[178][143];
int row_height[178]; /* Largest symbol is 177x177 QR Code */
char errtxt[100];
char *bitmap;
int bitmap_width;
int bitmap_height;
struct zint_render *rendered;
};
#if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) || defined(_MSC_VER)
# if defined (DLL_EXPORT) || defined(PIC) || defined(_USRDLL)
# define ZINT_EXTERN __declspec(dllexport)
# elif defined(ZINT_DLL)
# define ZINT_EXTERN __declspec(dllimport)
# else
# define ZINT_EXTERN extern
# endif
#else
# define ZINT_EXTERN extern
#endif
ZINT_EXTERN struct zint_symbol *ZBarcode_Create(void);
ZINT_EXTERN void ZBarcode_Clear(struct zint_symbol *symbol);
ZINT_EXTERN void ZBarcode_Delete(struct zint_symbol *symbol);
ZINT_EXTERN int ZBarcode_Encode(struct zint_symbol *symbol, unsigned char *input, int length);
ZINT_EXTERN int ZBarcode_Encode_File(struct zint_symbol *symbol, char *filename);
ZINT_EXTERN int ZBarcode_Print(struct zint_symbol *symbol, int rotate_angle);
ZINT_EXTERN int ZBarcode_Encode_and_Print(struct zint_symbol *symbol, unsigned char *input, int length, int rotate_angle);
ZINT_EXTERN int ZBarcode_Encode_File_and_Print(struct zint_symbol *symbol, char *filename, int rotate_angle);
ZINT_EXTERN int ZBarcode_Render(struct zint_symbol *symbol, float width, float height);
ZINT_EXTERN int ZBarcode_Buffer(struct zint_symbol *symbol, int rotate_angle);
ZINT_EXTERN int ZBarcode_Encode_and_Buffer(struct zint_symbol *symbol, unsigned char *input, int length, int rotate_angle);
ZINT_EXTERN int ZBarcode_Encode_File_and_Buffer(struct zint_symbol *symbol, char *filename, int rotate_angle);
ZINT_EXTERN int ZBarcode_ValidID(int symbol_id);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* ZINT_H */
Note: A similar question was asked but never resolved in this thread. I've been scratching my head over this for a week. Any ideas?
You are going to have to declare the bitmap member as IntPtr and marshal it manually.
If the caller allocates the memory do so with Marshal.AllocHGlobal. And then use Marshal.Copy to copy between the unmanaged memory and your managed representation of the bitmap. Or pin the managed byte array that represents the bitmap.
If the callee allocates the memory then you just need Marshal.Copy to copy from unmanaged to managed.
It's quite a complex interface and it seems plausible that there will be other errors. I've just looked at the bitmap field since it's what you asked about.
Related
converting c++ API struct to c#
I am trying to convert c++ api example to c#. but I can not find the way of struct pointer in struct by the way struct has string types. Here is c++ structs and functions struct AuthParam { char server_ip[32]; char username[50]; char password[50]; }; struct CameraInfo { int index; char devicename[100]; char smallrtsp[1000]; char bigrtsp[1000]; }; struct SingleDevice { char deviceid[50]; char devicename[100]; int flag_onuse; int cameralist_size; CameraInfo* cameralist; }; struct DeviceList { int listsize; SingleDevice* singledevicelist; }; typedef int (WINAPI capi_init)(void); typedef int (WINAPI capi_disabled)(void); typedef int (WINAPI capi_GetServerTimeCode)(char* server_ip, unsigned int* timecode); typedef int (WINAPI GetDevicelist)(AuthParam auth_para, DeviceList* devicelist); and this is my c# code but I can not find the way of defining struct pointer in struct I got " Error CS0208 Cannot take the address of, get the size of, or declare a pointer to a managed type ('Form1.CameraInfo')" error. public struct AuthParam { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string server_ip; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)] public string username; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)] public string password; }; [StructLayout(LayoutKind.Sequential)] public struct CameraInfo { public int index; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)] public string devicename; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1000)] public string smallrtsp; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1000)] public string bigrtsp; }; [StructLayout(LayoutKind.Sequential)] public struct SingleDevice { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)] public string deviceid; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)] public string devicename; public int flag_onuse; public int cameralist_size; public CameraInfo *cameralist; }; [StructLayout(LayoutKind.Sequential)] public unsafe struct DeviceList { public int listsize; public SingleDevice *singledevicelist; }; [DllImport("c:\\lib\\api_client.dll")] public static extern int capi_init(); [DllImport("c:\\lib\\api_client.dll")] public static extern int capi_disabled(); [DllImport("c:\\lib\\api_client.dll")] public static extern int capi_GetServerTimeCode(string ip, ref uint timecode); [DllImport("c:\\lib\\api_client.dll")] public static extern int GetDevicelist (AuthParam auth_para,ref DeviceList devicelist); is there any way to achieve this convertion?
i think this error because of the different nature of c++ (un-managed code) and c# (managed code), so maybe using System.IntPtr as a pointer to your camera struct. please consider this question here, and you can find about P/Invoke Interop Assistant which is an open source tool to convert your code from the un-managed code to a managed C# code. with a small blog article about C++/C# interoperability
C struct and use C dll in C#
everyone, I'm newbie in C#, and I have a problem with using "C" dll in C#, below is the C side code which work fine. // myCode.h typedef struct _SubABC { unsigned short WordCount; unsigned char *WordData; unsigned char SpeedUp; } SubABC; typedef struct _ABC { unsigned char SubFontNum; SubABC subABC[5]; } ABC extern __declspec(dllimport) int __stdcall MY_SetSth(unsigned char SerialNum, ABC *pABC); //myCode.c ABC myFun = { '\0' }; unsigned char text_c[] = { 0x00,0x27,0xff,0xA5,0xC3, 0x00,0x27,0xff,0xA6,0x26, 0x00,0x23,0xff,0xA7,0xAE, 0x00,0x27,0xff,0xBA,0x7E, 0x00,0x27,0xff,0xC1,0x52, 0x00,0x27,0xff,0xAC,0xF9, 0x00,0x27,0xff,0x20,0x00,0x27,0xff,0x31, 0x00,0x27,0xff,0x20, 0x00,0x27,0xff,0xA4,0xC0, 0x00,0x27,0xff,0xC4,0xC1, }; myFun.SubFontNum = 1; myFun.subMABC[0].WordCount = sizeof(text_c); myFun.subMABC[0].WordData = text_c; myFun.subMABC[0].SpeedUp = 0; int retvalue = MY_SetSth(1, &myFun); and my C# code is below [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct SubABC { public ushort WordCount; [MarshalAs(UnmanagedType.LPArray)] public byte[] WordData; public byte SpeedUp; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct ABC { public byte SubFontNum; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] public SubABC[] subABC; } [DllImport("myDLL.dll", CharSet = CharSet.Ansi)] public extern static int MY_SetSth(byte SerialNum, ref ABC pABC); ABC myFun = new ABC(); ABC.subABC = new SubABC[5]; unsigned char text_c[] = { 0x00,0x27,0xff,0xA5,0xC3, 0x00,0x27,0xff,0xA6,0x26, 0x00,0x23,0xff,0xA7,0xAE, 0x00,0x27,0xff,0xBA,0x7E, 0x00,0x27,0xff,0xC1,0x52, 0x00,0x27,0xff,0xAC,0xF9, 0x00,0x27,0xff,0x20,0x00,0x27,0xff,0x31, 0x00,0x27,0xff,0x20, 0x00,0x27,0xff,0xA4,0xC0, 0x00,0x27,0xff,0xC4,0xC1, }; myFun.SubFontNum = 1; myFun.subMABC[0].WordCount = (ushort)text_c.Length; myFun.subMABC[0].WordData = text_c; myFun.subMABC[0].SpeedUp = 0; int retvalue = MY_SetSth(1, ref myFun); I always get TypeLoadException: Cannot marshal field 'WordData' of type 'subABC': Invalid managed/unmanaged type combination (Array fields must be paired with ByValArray or SafeArray), because WordData is unknown size, I can't use ByValArray, and SafeArray get AccessViolationException: Attempted to read or write protected memory I try to use IntPtr like below [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct SubABC { public ushort WordCount; public IntPtr WordData; public byte SpeedUp; } ABC.subABC[0].WordData = Marshal.AllocHGlobal(text_c.Length); ; Marshal.Copy(text_c, 0, ABC.subABC[0].WordData, text_c.Length); int retvalue = MY_SetSth(1, ref myFun); I get error message AccessViolationException: Attempted to read or write protected memory Can anyone help me? thank a lot.
C# P/Invoke 2 dimensional array
I am trying to call a C function from C# code: typedef char IPTriggerNameType[256]; typedef unsigned long COMM_HANDLE; typedef BYTE GUID_TYPE[16]; typedef long LONGINT; typedef struct { GUID_TYPE Guid; IPTriggerNameType Name; }IPCAM_GENERIC_EVENT_ID; typedef struct { IPCAM_GENERIC_EVENT_ID EventId; LONGINT RelatedTriggerId; LONGINT ObsoleteEvent; }IPCAM_GENERIC_EVENT_INFO; typedef struct { LONGINT NumOfEvents; IPCAM_GENERIC_EVENT_INFO *GenericEventsList; }VID_CHANNEL_GENERIC_EVENTS_STRUCT; int __stdcall GetGenericEvents( /* Inputs: */ COMM_HANDLE Handle, LONGINT MaxNumOfChannelsInTable, LONGINT MaxNumOfEventsPerChannel, /* Outputs: */ LONGINT *NumOfChannels, VID_CHANNEL_GENERIC_EVENTS_STRUCT *ChannelsEventsTable); and the C# equivalent is as follows: [StructLayout(LayoutKind.Sequential)] public struct IPCAM_GENERIC_EVENT_ID { [MarshalAs(UnmanagedType.LPArray, SizeConst = 16)] public byte[] Guid; [MarshalAs(UnmanagedType.LPArray, SizeConst = 256)] public char[] Name; }; [StructLayout(LayoutKind.Sequential)] public struct IPCAM_GENERIC_EVENT_INFO { public IPCAM_GENERIC_EVENT_ID EventId; public int RelatedTriggerId; public int ObsoleteEvent; } [StructLayout(LayoutKind.Sequential)] public struct VID_CHANNEL_GENERIC_EVENTS_STRUCT { public int NumOfEvents; [MarshalAs(UnmanagedType.LPArray, SizeConst = 100)] public IPCAM_GENERIC_EVENT_INFO[] GenericEventsList; } [DllImport(dllName)] public static extern int GetGenericEvents( /* Inputs: */ int Handle, int MaxNumOfChannelsInTable, int MaxNumOfEventsPerChannel, /* Outputs: */ out int NumOfChannels, out VID_CHANNEL_GENERIC_EVENTS_STRUCT[] ChannelsEventsTable); int numOfChannels = 16, actualNumOfChannels = 0; int maxNumOfEvents = 100; VID_CHANNEL_GENERIC_EVENTS_STRUCT[] genericEventsList = new VID_CHANNEL_GENERIC_EVENTS_STRUCT[numOfChannels]; for (int i = 0; i < numOfChannels; i++) { genericEventsList[i].GenericEventsList = new IPCAM_GENERIC_EVENT_INFO[maxNumOfEvents]; } GetGenericEvents(conn, numOfChannels, maxNumOfEvents, out actualNumOfChannels, out genericEventsList); when calling the C# method I get exception which crashes the application: Managed Debugging Assistant 'FatalExecutionEngineError' has detected a problem in 'My.exe'. Additional information: The runtime has encountered a fatal error. The address of the error was at 0x72a6cf41, on thread 0x5a98. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack. What am I doing wrong ?
The field: VID_CHANNEL_GENERIC_EVENTS_STRUCT.GenericEventsList is a pointer, declared as: IPCAM_GENERIC_EVENT_INFO *GenericEventsList But you are declaring it as an array, included in the struct: [MarshalAs(UnmanagedType.LPArray, SizeConst = 100)] public IPCAM_GENERIC_EVENT_INFO[] GenericEventsList; You may try to use an IntPtr and Marshal.AllocHGlobal. Check this out: How use pinvoke for C struct array pointer to C#
consume c# array of structs using c++ unmanaged Dll
as i am new to the unmanaged world, and after a success test of interacting with c++ DLL on a simple task, i am now trying to consume in c#, data manipulated by c++ via an array of struct i have also tried to wrap the struct[] within another struct to simplify the marshal but i did not succeed in any way. what is the correct (and most efficient performance wise) way to handle the data returned from c++ given the code below. C# [StructLayout(LayoutKind.Sequential)] public struct chars { public int V; //[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 10)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public char[] testStr; } [DllImport("ExportStructArr.dll", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] public static extern void dodata(int requestedMasterArrLength, int requestedStringSize,[MarshalAs(UnmanagedType.LPArray), Out()] chars[] Cars); //tried also this signature public static extern void dodata( int requestedMasterArrLength, int requestedStringSize, out IntPtr chars); c++ #ifdef EXPORTSTRUCTARR_EXPORTS #define EXPORTSTRUCTARR_API extern "C" __declspec(dllexport) typedef struct { int Id; char *StrVal; }Unit; EXPORTSTRUCTARR_API void dodata(int requestedMasterArrLength,int requestedStringSize, Unit **UntArr) { int ccountr,count; count=0; *UntArr = (Unit*)LocalAlloc(0, requestedMasterArrLength * sizeof(Unit)); Unit *CurU = *UntArr; while(count!= requestedMasterArrLength) { char dummyStringDataObject[]= "abcdefgHi"; CurU[count].StrVal = NULL; dummyStringDataObject[0] = count+'0'; CurU[count].Id = count; CurU[count].StrVal= (char*) LocalAlloc(0,(sizeof(char)* requestedStringSize)); ccountr=0; while(ccountr!= requestedStringSize){ CurU[count].StrVal[ccountr] = dummyStringDataObject[ccountr]; ++ccountr; } ++count; } }
DllImport signature error
I have an DLL file named WD_SDK.dll (it go with an SDK.h file). I open the SDK.h and I see: typedef void (CALLBACK * VideoCaptureCB_Ptr)(PVOID pContext, BYTE * apData[3], VideoSampleInfo_T * pVSI); typedef struct _VideoSampleInfo_T { ULONG idFormat; // ULONG lSignalState; int nLen; // not used for raw video data(e.g. YUV420) int nWidth; int nHeight; int anPitchs[3]; // only used for raw video data(e.g. YUV420) ULONG dwMicrosecsPerFrame; // 1000*1000/FPS ULONG field; int iSerial; } VideoSampleInfo_T; WD_RegisterVideoPreviewCB(HANDLE hChannel, PVOID pContext, VideoCaptureCB_Ptr pCB); I want to call the WD_RegisterVideoPreviewCB in C#. So I use the DllImport to declare it in C#. It look like this: [DllImport("WD_SDK.dll", EntryPoint = "_WD_RegisterVideoPreviewCB#12", ExactSpelling = true)] static extern int WD_RegisterVideoPreviewCB(IntPtr hChannel, object pContext, VideoCaptureCB_Ptr pCB); Then I re-declare the C++ struct and CALLBACK in C# like this: public delegate void VideoCaptureCB_Ptr(IntPtr pContext, byte[] apData, VideoSampleInfo_T pVSI); [StructLayout(LayoutKind.Sequential)] public struct VideoSampleInfo_T { public uint idFormat; public uint lSignalState; public int nLen; // not used for raw video data(e.g. YUV420) public int nWidth; public int nHeight; public int[] anPitchs; // only used for raw video data(e.g. YUV420) public uint dwMicrosecsPerFrame; // 1000*1000/FPS public uint field; public int iSerial; } And one delegate implement public static void HandleVideoStatic(IntPtr pContext, byte[] apData, VideoSampleInfo_T pVSI) { } When i run my code : WD_RegisterVideoPreviewCB(m_ahChannels[i], m_aMediaHandler[i], HandleVideoStatic); THe program show me an error does not match the unmanaged target signature. I thinks encountered problem with the PVOID pContext, I changed it to object pContext. But I have to pass the m_aMediaHandler[i] (It's an instance of a class not a pointer) to it, the error raised " .."invalid agruments" P/S: The WD_RegisterVideoPreviewCB call in C++ WD_RegisterVideoPreviewCB(m_ahChannels[i], &m_aMediaHandler[i], HandleVideoStatic);