I'm trying to invoke the I2CTransfer function below, and immediately getting a System.NotSupportedException. I suspect my marshalling is wrong, but cannot work out the problem.
Here are the C structures:
BOOL I2CTransfer(HANDLE hDev, PI2C_TRANSFER_BLOCK pI2CTransferBlock);
typedef struct {
I2C_PACKET *pI2CPackets;
INT32 iNumPackets;
} I2C_TRANSFER_BLOCK, *PI2C_TRANSFER_BLOCK;
typedef struct {
BYTE byAddr;
BYTE byRW;
PBYTE pbyBuf;
WORD wLen;
LPINT lpiResult;
} I2C_PACKET, *PI2C_PACKET;
And here are the c# structures I'm attempting:
[DllImport("i2csdk.dll", EntryPoint = "I2CTransfer")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool I2CTransfer(IntPtr hI2C,ref I2C_TRANSFER_BLOCK pI2CTransferBlock);
[StructLayout(LayoutKind.Sequential)]
public struct I2C_TRANSFER_BLOCK
{
public I2C_PACKET[] pI2CPackets;
public int iNumPackets;
}
[StructLayout(LayoutKind.Sequential)]
public struct I2C_PACKET
{
public byte byAddr;
public byte byRW;
public byte[] pbyBuf;
public UInt16 wLen;
public IntPtr lpiResult;
}
Calling code:
I2C_TRANSFER_BLOCK i2CTransferBlock = new I2C_TRANSFER_BLOCK();
I2C_PACKET packet = new I2C_PACKET();
int result;
IntPtr resultPtr = IntPtr.Zero;
//Populating data...
byte[] pBuf = new byte[1 + pbData.Length];
pBuf[0] = (byte) ((regStart & 0x7F) << 1);
Array.Copy(pbData, 0, pBuf, 1, pbData.Length);
// Fill packet for register write
packet.pbyBuf = pBuf;
packet.wLen = (ushort) pBuf.Length;
packet.byRW = NativeConstants.I2C_RW_WRITE;
packet.byAddr = address;
packet.lpiResult = resultPtr;
// Fill transfer block
i2CTransferBlock.pI2CPackets = new I2C_PACKET[] {packet};
i2CTransferBlock.iNumPackets = 1;
// NotSupportedException here
bool brc = I2CTransfer(port, ref i2CTransferBlock);
The arrays are initialized in C# before calling the method.
I've tried adding
[MarshalAs(UnmanagedType.LPArray)]
to the arrays (pI2cPackets, and pbyBuf) to no avail.
This is on Windows CE - compact framework, .NET 3.5.
Is there something obviously wrong with the above translation?
Many thanks in advance.
By no means I'm an expert on Marshaling but i think i'll throw in few ideas just in case.
1) try to manually marshal arrays (as IntPtr) by allocating memory for them in your code.
2) This line IntPtr resultPtr = IntPtr.Zero; looks suspicious. Normally when you pass a pointer to unmanaged code from managed code it is your job to allocate (and free) memory for this pointer. Check this out for details http://msdn.microsoft.com/en-us/library/0szztey7%28v=VS.90%29.aspx
Your problems are in the pointers contained in the structs:
[StructLayout(LayoutKind.Sequential)]
public struct I2C_TRANSFER_BLOCK
{
public I2C_PACKET[] pI2CPackets; // here ....
public int iNumPackets;
}
[StructLayout(LayoutKind.Sequential)]
public struct I2C_PACKET
{
public byte byAddr;
public byte byRW;
public byte[] pbyBuf; // .... and here
public UInt16 wLen;
public IntPtr lpiResult;
}
You cannot persuade the p/invoke marshaller to marshal pointers to arrays that are embedded inside structs. That form of marshalling is only available for function parameters.
You'll need to declare both of those fields as IntPtr and do the marshalling by hand.
Related
I'm trying to return a struct from a C++ callback from C# and I get the error as described in the title.
I appreciate there is a lot of information, but I wanted to include too much rather than not enough.
I've tried returning the function as a structure, but then read it may be easier to return the function as an IntPtr and use Marshal.PtrtoStructure.
The function that I want to call is from a .dll that comes from a different source and the input is:
C++:
rVDACQ_Connect(int, tVDACQ_CallBackProc, void*, short*, int)
the function in the sample (C++) code returns a struct (rACQ_CallBackRec) which is as follows:
rACQ_CallBackRec = theApp.m_Intf.rVDACQ_Connect(cVDACQ_FBright, CALLBACK_Acquisition, this, m_FrmBuffer, 0);
//rACQ_CallBackRec is the struct type tVDACQ_CallBackRec (as described below)
With CALLBACK_Aquisition being:
extern "C" {
__declspec(dllexport) void _stdcall CALLBACK_Acquisition(tVDACQ_CallBackRec* AR)
{
tVDACQ_CallBackProc test;
((CPreviewDlg*)AR->rUserParam)->My_ACQ_CallBack(AR, test);
}
}
The layout of the struct is as follows:
typedef struct {
int rFlags, // combination of cVDACQ_Fxxxx
rType, // cVDACQ_ETxxx
rEvent, // cVDACQ_Exxx
rSocket; // 0:no relation to a 'socket'; otherwise socket's ID>0 (event's source ID)
TCHAR rMsg[256]; // message (trace, wrn, err)
int rFrameWidth, // full frame width
rFrameHeight; // full frame height
short* rFrameBuffer; // user supplied frame buffer "AFrameBuffer"
union {
int rCaptureRows; // # of received rows (for single frame acquisition)
int rCaptureFrames; // # of received full frames (for framegrabber)
};
int rCapturePercent; // received data in percents
void* rUserCallBackProc, // user supplied "ACallBackProc"
* rUserParam; // user supplied "AUserParam"
int rAborted; // 1: VDACQ_Abort -1:internally
void* rPacketData; // pointer to received packet; usually it is nil
int rFGControl; // frame-grabber's control flags
} tVDACQ_CallBackRec;
typedef void(_stdcall* tVDACQ_CallBackProc)(tVDACQ_CallBackRec*); //The Callback
C#
I've created the callback procedure in C#, as well as the struct and PInvoke:
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void tVDACQ_CallBackProc(tVDACQ_CallBackRec AR);
[DllImport("C:\\Users\\jch\\source\\repos\\FlatPanelSensor\\x64\\Debug\\VADAV_AcqS.dll", EntryPoint = "CALLBACK_Acquisition", CallingConvention = CallingConvention.Cdecl)]
public unsafe static extern void CALLBACK_Acquisition(tVDACQ_CallBackRec AR);
[DllImport("C:\\Users\\jch\\source\\repos\\FlatPanelSensor\\FlatPanelSensor\\bin\\Debug\\VADAV_FGM_64.dll", EntryPoint = "VDACQ_Connect", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I4)]
public unsafe static extern IntPtr VDACQ_Connect(int i, [MarshalAs(UnmanagedType.FunctionPtr)] tVDACQ_CallBackProc proc, dynamic n, IntPtr[] buffer, int j);
Struct:
[StructLayout(LayoutKind.Sequential)]
public struct tVDACQ_CallBackRec
{
public int rFlags;
public int rType;
public int rEvent;
public int rSocket;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string rMsg;
public int rFrameWidth;
public int rFrameHeight;
public IntPtr rFrameBuffer;
public int rCaptureRows;
public int rCaptureFrames;
public int rCapturePercent;
public IntPtr rUserCallBackProc;
public IntPtr rUserParam;
public int rAborted;
public IntPtr rPacketData;
public int rFGControl;
}
CallBack:
tVDACQ_CallBackProc callBack =
(AR) =>
{
CALLBACK_Acquisition(AR); //Don't know how necessary this
is
};
Function call:
IntPtr work = VDACQ_Connect(0, callBack, this, m_FrmBuffer, 0);
//I know 'this' isn't defined as a void* in my DLLImport,
//but making it an IntPtr didn't work either so
//the only type I could think of was dynamic.
If I could return this as a IntPtr or, even better, the struct I defined in my C# code that would be great.
If you need anymore information then please let me know, as I think I included everything.
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;
}
}
I'm trying to get back this struct from a native C library through P/Invoke:
struct ndb_mgm_cluster_state
{
int no_of_nodes;
struct ndb_mgm_node_state node_states[1];
};
where ndb_mgm_node_state is:
struct ndb_mgm_node_state {
int node_id;
enum ndb_mgm_node_type node_type;
enum ndb_mgm_node_status node_status;
int start_phase;
int dynamic_id;
int node_group;
int version;
int connect_count;
char connect_address[sizeof("000.000.000.000")+1 ];
int mysql_version;
};
The method's signature is:
ndb_mgm_cluster_state* WINAPI wrap_ndb_mgm_get_status(HANDLE handle);
All of this is provided by a 3rd party library, so nothing can be changed.
In C# i have the follow definitions:
[DllImport("Ndb_CWrapper.dll", CharSet = CharSet.Ansi)]
private static extern IntPtr wrap_ndb_mgm_get_status(IntPtr handle);
the structures are:
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct ndb_mgm_cluster_state {
public int no_of_nodes;
public IntPtr node_states;
};
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct ndb_mgm_node_state
{
public int node_id;
public ndb_mgm_node_type node_type;
public ndb_mgm_node_status node_status;
public int start_phase;
public int dynamic_id;
public int node_group;
public int version;
public int connect_count;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 17)]
public string connect_address;
public int mysql_version;
};
I tried to unmarshal the results without success (i recieve an oddly error (not an Exception) Fatal error execution, can be a CLR bug or a miscall P/invoke.
Obviously the reason is a problem in my P/Invoke call
I tried in this way:
First of all i unmarshalled the ndb_mgm_cluster_state structure:
var res=(ndb_mgm_cluster_state)Marshal.PtrToStructure(
tmpPtr, typeof(ndb_mgm_cluster_state));
where IntPtr is the result of the native call.
Up to this step everything "seems" to be done right, but when i try to unmarshall the node_states i get the error:
ndb_mgm_node_state tmpNode = (ndb_mgm_node_state)Marshal.PtrToStructure(
status.node_states, typeof(ndb_mgm_node_state));
What can be the problem? I supposed is something related to the strange declaration of ndb_mgm_cluster_state because is defined an array of 1 element, but it contain several elements. (the number of elements is in no_of_nodes)
WORKAROUND:
The only way to let everything work i found is to change a bit the signature, in this way:
ndb_mgm_node_state* WINAPI wrap_ndb_mgm_get_status(HANDLE handle,int* length);
in C#:
[DllImport("Ndb_CWrapper.dll", CharSet = CharSet.Ansi)]
private static extern IntPtr wrap_ndb_mgm_get_status(IntPtr handle,out int length);
where length contains no_of_nodes
the unmarshall will be in this way:
IntPtr tmpPtr = wrap_ndb_mgm_get_status(raw,out length);
ndb_mgm_cluster_state tmpRes = new ndb_mgm_cluster_state();
tmpRes.no_of_nodes = length;
tmpRes.node_states = new ndb_mgm_node_state[length];
int step=0;
for (int i = 0; i < tmpRes.no_of_nodes; i++)
{
tmpRes.node_states[i] = (ndb_mgm_node_state)Marshal.PtrToStructure(
tmpPtr+(step*i), typeof(ndb_mgm_node_state));
step = Marshal.SizeOf(tmpRes.node_states[i]);
}
I know the step calculation is odd at the monent, but its not that the point.
there is no way to let the thing work returning directly the ndb_mgm_cluster_state struct instead of do all of this?
I have a common construct in an unmanaged Win32 C++ DLL:
// FirstElemPtrContainer.h
#include "stdafx.h"
typedef unsigned char elem_type; // a byte
typedef struct FirstElemPtrContainer {
unsigned char num_elems;
void *allocd_ary;
} FirstElemPtrContainer;
The void* in the struct is meant to contain a pointer to the first element of an allocated byte array.
The DLL that uses this definition then exports functions to allocate, populate, and deallocate the struct:
// The exported allocator function.
extern "C" _declspec(dllexport)
FirstElemPtrContainer *BuildStruct(int elem_count)
{
FirstElemPtrContainer *fepc_ptr = new FirstElemPtrContainer;
fepc_ptr->num_elems = elem_count;
elem_type *ary = new elem_type[fepc_ptr->num_elems];
for (int i = 0; i < fepc_ptr->num_elems; i++)
{
ary[i] = ((i + 1) * 5); // multiples of 5
}
fepc_ptr->allocd_ary = ary;
return fepc_ptr;
}
// The exported deallocator function.
extern "C" _declspec(dllexport) void
DestroyStruct(FirstElemPtrContainer *fepc_ptr)
{
delete[] fepc_ptr->allocd_ary;
delete fepc_ptr;
}
These work just fine for a native caller.
In C#, I try to describe this same structure via PInvoke:
[StructLayout(LayoutKind.Sequential)]
public struct FirstElemPtrContainer
{
public byte num_elems;
[MarshalAs(UnmanagedType.LPArray,
ArraySubType = UnmanagedType.U1, SizeConst = 4)]
public IntPtr allocd_ary;
}
... and describe the call interface like so:
public static class Imports
{
[DllImport("MyLib", CallingConvention = CallingConvention.Winapi)]
public static extern IntPtr BuildStruct(int elem_count);
[DllImport("MyLib", CallingConvention = CallingConvention.Winapi)]
public static extern void DestroyStruct(IntPtr fepc_ptr);
}
Now I attempt to call my interface:
class Program
{
const int NUM_ELEMS = 4;
static void Main(string[] args)
{
IntPtr fepc_ptr = Imports.BuildStruct(NUM_ELEMS);
if ( fepc_ptr == IntPtr.Zero )
{
Console.WriteLine("Error getting struct from PInvoke.");
return;
}
FirstElemPtrContainer fepc =
(FirstElemPtrContainer)Marshal.PtrToStructure(fepc_ptr,
typeof(FirstElemPtrContainer));
//...
}
}
The PtrToStructure() call gives the error "Cannot marshal field 'allocd_ary' of type 'MyLibInvoke.FirstElemPtrContainer': Invalid managed/unmanaged type combination (Int/UInt must be paired with SysInt or SysUInt)."
You can see that I've hard-coded a particular number of elements, which we'll assume the caller adheres to. I've also added an ArraySubType clause, though it seems not to make a difference. Why the type mismatch complaint?
Your struct should be declared like this:
[StructLayout(LayoutKind.Sequential)]
public struct FirstElemPtrContainer
{
public byte num_elems;
public IntPtr allocd_ary;
}
it has to be done this way because allocd_ary is a pointer to unmanaged memory and cannot be marshalled by the p/invoke marshaller.
In order to read the contents of allocd_ary you can use Marshal.Copy.
FirstElemPtrContainer fepc = (FirstElemPtrContainer)Marshal.
PtrToStructure(fepc_ptr, typeof(FirstElemPtrContainer));
byte[] ary = new byte[fepc.num_elems];
Marshal.Copy(fepc.allocd_ary, ary, 0, ary.Length);
I suspect that CallingConvention.Winapi is wrong and that you should be using CallingConvention.Cdecl.
Greetings all,
The TBBUTTON struct is defined on MSDN as follows:
typedef struct {
int iBitmap;
int idCommand;
BYTE fsState;
BYTE fsStyle;
#ifdef _WIN64
BYTE bReserved[6];
#else
#if defined(_WIN32)
BYTE bReserved[2];
#endif
#endif
DWORD_PTR dwData;
INT_PTR iString;
} TBBUTTON, *PTBBUTTON, *LPTBBUTTON;
I need to do some interop in C# using this struct. How do I replicate this monster so that it's defined correctly when compiled for AnyCPU? Google is apparently full of dangerous misinformation!
Ahah, I knew there had to be a way. And here it is:
[StructLayout(LayoutKind.Sequential)]
public struct TBBUTTON {
public int iBitmap;
public int idCommand;
[StructLayout(LayoutKind.Explicit)]
private struct TBBUTTON_U {
[FieldOffset(0)] public byte fsState;
[FieldOffset(1)] public byte fsStyle;
[FieldOffset(0)] private IntPtr bReserved;
}
private TBBUTTON_U union;
public byte fsState { get { return union.fsState; } set { union.fsState = value; } }
public byte fsStyle { get { return union.fsStyle; } set { union.fsStyle = value; } }
public UIntPtr dwData;
public IntPtr iString;
}
Marshal.SizeOf returns 32 on x64 processes and 20 on x86 processes, and everything ends up where it should when I pass this to SendMessage. I knew you could do it, C#!
Your best bet is to define two versions, one for 32 bit and one for 64 bit.
public struct TBBUTTON32
{
int iBitmap;
int idCommand;
byte fsState;
byte fsStyle;
[MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 2)]
byte[] bReserved;
UIntPtr dwData;
IntPtr iString;
}
The 64 bit version is just the same but with SizeConst = 6 on the reserved bytes array.
Then you need to switch between them at runtime. Your C# code will have to detect whether it's running as a 32 or 64 bit process.