I am trying to learn enough C# so that I can pass a strcture by reference to a C DLL; but it never gets to the "cFunction". As you can see in the cFunction, I am explicitly setting the streamSubset value to 44; but back in the c# portion it does not return "44".
Here is the C code:
typedef struct s_mPlot
{
double price[100];
int streamSubset;
} mPlot;
extern "C" __declspec( dllexport )
void cFunction(mPlot *Mplot){
Mplot->streamSubset = 44;}
// and here is the c# code
using System;
using Vibe.Function;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public class MPLOT
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
public double [] price;
public int streamSubset;
}
namespace Vibe.Indicator{
public class myIdx : IndicatorObject {
[DllImport("C:\\Users\\joe\\mcDll.dll", CharSet = CharSet.Auto)]
public static extern void cFunction(
[In, MarshalAs(UnmanagedType.LPStruct)] MPLOT mPlot );
public myIdx(object _ctx):base(_ctx){}
private IPlotObject plot1;
protected override void Create()
{
MPLOT mPlot = new MPLOT();
mPlot.streamSubset = 2;
cFunction(mPlot);
if (mPlot.streamSubset == 44)
go();
}
}
}
I can see the following:
You almost certainly need to specify the cdecl calling convention in your DllImport attribute. Add CallingConvention=CallingConvention.Cdecl.
I believe that UnmanagedType.LPStruct adds an extra level of indirection. But you are passing a C# class which is a reference type. That means you are passing a pointer to a pointer. That's one level of indirection too many. First of all remove [In, MarshalAs(UnmanagedType.LPStruct)] altogether. Then your code should work. If you switched to a struct rather than a class for MPLOT then you'd need to pass by ref to get the indirection.
I think I would have the code like this:
[StructLayout(LayoutKind.Sequential)]
public struct MPLOT
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
public double [] price;
public int streamSubset;
}
[DllImport("dllname.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern void cFunction(
ref MPLOT mPlot
);
Try specifying the calling convention explicitly:
[DllImport("C:\\Users\\joe\\mcDll.dll", CallingConvention=CallingConvention.Cdecl CharSet = CharSet.Auto)]
VC exports with calling convention cdecl by default, but DllImport uses stdcall by default. So you have to specify at least one of them explicitly, or better, both.
Replace [In, MarshalAs(UnmanagedType.LPStruct)] with ref.
Related
I am working in C# and I need to call a function in a C++ dll library. This function returns a struct but I can´t get anything.
This is the function I need to call and the struct that returns in C++ library:
ATHENA_API _DEVICE_INFO* __stdcall GetDeviceInfoKeepConnection(_DEVICE_INFO* pDeviceInfo);
typedef struct TD_DEVICE_INFO{
TCHAR chDeviceName[256];
int nCommPort;
int nECGPos;
int nNumberOfChannel;
int nESUType;
int nTymestampType;
int nDeviceHandle;
TCHAR chDeviceID[260];
}_DEVICE_INFO;
This is my C# code trying to call the function:
[DllImport(#"\BAlertSDK\ABM_Athena.dll")]
static extern _DEVICE_INFO GetDeviceInfoKeepConnection(_DEVICE_INFO deviceInfo);
[StructLayout(LayoutKind.Sequential)]
struct _DEVICE_INFO
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string chDeviceName;
public int nCommPort;
public int nECGPos;
public int nNumberOfChannel;
public int nESUType;
public int nTymestampType;
public int nDeviceHandle;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string chDeviceID;
}
void Test_Click()
{
_DEVICE_INFO d = new _DEVICE_INFO();
_DEVICE_INFO deviceInfo = GetDeviceInfoKeepConnection(d);
}
The only I can get is an empty _DEVICE_INFO object. I think my problem is that I am not defining correctly the DEVICE INFO struct.
I have never worked with dll´s to this level. Can you help me?
Thanks in advance.
Thanks to all!! The problem has solved with this:
Parameter pass by reference and struct charset Unicode.
It seems that the function GetDeviceInfoKeepConnection returns a pointer to _DEVICE_INFO struct. So, in your C# code, you need to change the definition of the function to:
[DllImport(#"\BAlertSDK\ABM_Athena.dll")]
static extern IntPtr GetDeviceInfoKeepConnection(IntPtr deviceInfo);
And then you can access the struct data like this:
void Test_Click()
{
_DEVICE_INFO d = new _DEVICE_INFO();
IntPtr pDeviceInfo = Marshal.AllocHGlobal(Marshal.SizeOf(d));
Marshal.StructureToPtr(d, pDeviceInfo, false);
IntPtr deviceInfo = GetDeviceInfoKeepConnection(pDeviceInfo);
_DEVICE_INFO result = (_DEVICE_INFO)Marshal.PtrToStructure(deviceInfo, typeof(_DEVICE_INFO));
Marshal.FreeHGlobal(pDeviceInfo);
}
Note that, to ensure that the memory is cleaned up after use, you should use the Marshal.FreeHGlobal method to free the memory that was allocated by Marshal.AllocHGlobal.
I have a C structure used for callback that I need to marshall to C# .NET:
struct CTMDeviceInfo {
enum CTMDeviceType eDeviceType;
char * szDeviceModel;
char * szDeviceSubModel;
int32_t * piDeviceID;
};
This is my C# version:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CTMDeviceInfo
{
public CTMDeviceType deviceType;
[MarshalAs(UnmanagedType.LPStr)]
public string deviceModel;
[MarshalAs(UnmanagedType.LPStr)]
public string deviceSubModel;
public IntPtr deviceId;
};
Which is used inside another structure:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CTMDeviceError
{
public CTMDeviceInfo deviceInfo;
[MarshalAs(UnmanagedType.I4)]
public Int32 resultCode;
[MarshalAs(UnmanagedType.I4)]
public Int32 extendedResultCode;
public IntPtr denomination;
public IntPtr changeDue;
};
My problem is that the "IntPtr deviceId" does not consistently return the correct value every time a callback was made.
I was expecting an integer value of 5, 15 or 16 but it keeps returning random values like 106, 865412, 652272, etc.
I don't know what I did wrong. What I did though is to prevent the callback in my managed code to be garbage collected using GCHandle.
Here is the sequence on how I did it:
From my unmanaged code I have this CDECL callback method:
void ctm_add_device_error_event_handler(CTMDeviceErrorCallback);
typedef void (CTMDeviceErrorCallback) (struct CTMEventInfo, struct CTMDeviceError );
This is my managed code:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void OnDeviceErrorCallBack(CTMEventInfo evtInfo, CTMDeviceError deviceError);
[DllImport("libctmclient-0.dll", EntryPoint = "ctm_add_device_error_event_handler", CallingConvention = CallingConvention.Cdecl)]
public static extern void AddDeviceErrorEventHandler([MarshalAs(UnmanagedType.FunctionPtr)] OnDeviceErrorCallBack deviceErrorCallBack);
OnDeviceErrorCallBack deviceErrorCallback;
GCHandle deviceErrorCallbackGCHandle;
deviceErrorCallback = new OnDeviceErrorCallBack(OnDeviceError);
deviceErrorCallbackGCHandle = GCHandle.Alloc(deviceErrorCallback);
AddDeviceErrorEventHandler(deviceErrorCallback);
And this is where the callback is handled:
public void OnDeviceError(CTMEventInfo evtInfo, CTMDeviceError deviceError)
{
int nDeviceId = Marshal.ReadInt32(deviceError.deviceInfo.deviceId);
}
I tried to use unsafe to use pointers directly but the issue is still the same.
public unsafe int *deviceId; //instead of IntPtr
int nDeviceId = 0;
unsafe
{
nDeviceId = *(deviceError.deviceInfo.deviceId);
}
I'm sure that my unmanaged code returned the correct value because I have logs but when it reached in my managed code, somehow another value was returned.
It's like it is reading on a different reference or something.
Hope somewhat could help me because I am stuck for a while now.
Thanks!
Use following c# structure. You can get the two string from the pointer later in the code. :
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CTMDeviceInfo
{
public CTMDeviceType deviceType;
public IntPtr deviceModel;
public IntPtr deviceSubModel;
public IntPtr deviceId;
};
For a few days now I have been trying to Marshal a complex struct from C++ to C#, basically I have managed to get most of what I am trying to achieve done but now I'm stuck trying to marshal what I believe is a list.
In example I will include what I do get working and where I am stuck.
public: void __thiscall TransactionModule_t::GetTransaction(class Identity_t const &)const
Conformed as follwoing:
// public: void __thiscall TransactionModule_t::GetTransaction(class Identity_t const &)const
[DllImport("Transaction.dll", EntryPoint = "?GetTransaction#TransactionModule_t##Identity_t###Z", CallingConvention = CallingConvention.ThisCall)]
public static extern void GetTransaction(IntPtr iPtr,[Out, MarshalAs(UnmanagedType.LPStruct)] Identity transaction);
[StructLayout(LayoutKind.Sequential)]
[Serializable]
public class Identity
{
public uint Id;
public uint Type;
public Identity(uint id = 0, uint type = 0)
{
this.Id = id;
this.Type = type;
}
}
This is working just fine.
However I want to call a method which gives me the list.
public: void __thiscall TransactionModule_t::GetTransactions(class std::vector<class Identity_t,class std::allocator<class Identity_t> > &)const
And where i am getting stuck:
// public: void __thiscall TransactionModule_t::GetTransactions(class std::vector<class Identity_t,class std::allocator<class Identity_t> > &)const
[DllImport("Transaction.dll", EntryPoint = "long mangled entry point", CallingConvention = CallingConvention.ThisCall)]
public static extern void GetTransactions(IntPtr iPtr,[Out] Transactions transactions);
I tried making a class that fits in between the two.
[StructLayout(LayoutKind.Sequential)]
[Serializable]
public class Transactions
{
public Identity Identity;
public Identity[] List;
}
Is it even possible to call this method, am I missing something here?
Is it even possible to call this method?
No it is not. You cannot supply a std::vector from C# code.
Realistically you are going to need a C++/CLI wrapper.
I have following (shortened) function definition in my c++ code:
EXPORT_API Table* OpenTableExport();
where Table is a struct of the form:
typedef struct Table
{
int fCurrKey;
int fTableNo;
int fRecSize;
char fCreating;
Table* fNextTable;
Table* fPrevTable;
MyFileType fFile;
} Table;
So, to PInvoke this function from managed code I naturally tried following:
[DllImport("Export.dll", EntryPoint = "OpenTableExport", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern Table OpenTable();
With following table class:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class Table
{
public KeyDescription fCurrKey;
public int fTableNo;
public uint fRecSize;
public byte fCreating;
public Table fNextTable;
public Table fPrevTable;
public FileDescription fFile;
}
Now when using the method, I get following exception (translated from german):
The field "fNextTable" of type "Table" can not be marshalled, no marshalling support exists for this type.
Why can't .NET marshall the fNextTable and fPrevTable members of the same type it is marshalling in any case?
I can also just replace the references in the managed class definition with IntPtr... but is this really necessary? (The marshalling then will work more or less).
Define the structure by the following way:
public class Table
{
public KeyDescription fCurrKey;
public int fTableNo;
public uint fRecSize;
public byte fCreating;
public IntPtr fNextTable;
public IntPtr fPrevTable;
public FileDescription fFile;
}
Test IntPtr members for NULL (IntPtr.Zero) and handle them. Probably unmanaged API contains another functions expecting Table*. See also Marshal.PtrToStructure method and another low-level Marshal methods for managed-unmanaged memory exchange.
I'm stuck by passing struct with string data from C# code to C++ dll.
c++ code
typedef struct
{
LPCSTR lpLibFileName;
LPCSTR lpProcName;
LPVOID pPointer1;
LPVOID pPointer2;
} ENTITY, *PENTITY, *LPENTITY;
extern "C" __declspec(dllexport) int Test(LPENTITY entryList, int size);
int Test(LPENTITY entryList, int size)
{
for (int i = 0; i < size; i++)
{
ENTITY e = entryList[i];
// the char* value doesn't get passed correctly.
cout << e.lpLibFileName << e.lpProcName << endl;
}
return 0;
}
c# code
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
private class Entity
{
public string lpLibFileName;
public string lpProcName;
public IntPtr pPointer1;
public IntPtr pPointer2;
}
[DllImport("cpp.dll")]
private static extern int Test(
[In, Out, MarshalAs(UnmanagedType.LPArray)]Entity[] entities,
int size);
static void Main(string[] args)
{
var entries = new[]
{
new Entity
{
lpLibFileName = "comdlg32",
lpProcName = "PrintDlgExW",
pPointer1 = Marshal.GetFunctionPointerForDelegate(new PrintDlgEx(PrintDlgExCallback)),
pPointer2 = IntPtr.Zero,
},
new Entity
{
lpLibFileName = "shell32",
lpProcName = "ShellAboutW",
pPointer1 = Marshal.GetFunctionPointerForDelegate(new ShellAbout(ShellAboutCallback)),
pPointer2 = IntPtr.Zero,
},
};
var ret = Test(entries, entries.Length);
}
The PINVOKE was triggered, but the char* data like lpLibFileName and lpProcName cannot be passed correctly. Did I miss something? How to correct it?
Thanks.
Your code maps a C# class onto a native struct. Because a C# class is a reference type then it will be marshalled as a reference. So your code passes an array of references that gets marshalled to an array of pointers on the native side.
But the native code expects a pointer to an array of structs which are value types. So the simplest solution is to change the declaration of Entity to be a struct rather than a class.
The other issues that I can see:
The native code appears to be using the cdecl calling convention. You'll need to change the C# code to match.
You are decorating the array parameter with Out. You cannot marshal modifications to the string fields back to the managed code.
You will need to make sure that you keep alive the delegates that you pass to GetFunctionPointerForDelegate to stop them being collected.
When passing parameter like array of custom structures, use 'struct' instead of 'class' when defining your own data structure. After I changing it back to struct, everything worked fine.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
private struct Entity
{
public string lpLibFileName;
public string lpProcName;
public IntPtr pPointer1;
public IntPtr pPointer2;
}