In C++ i have this function and used it like below. How to i need to code in c sharp?
birdRS232WakeUp(int nGroupID, BOOL bStandAlone, int nNumDevices,
WORD *pwComport, DWORD dwBaudRate, DWORD dwReadTimeout,DWORD dwWriteTimeout,int nGroupMode
= GMS_GROUP_MODE_ALWAYS);
in the manual it state that "pwComport" points to an array of words, each of which is the number of the comport attached to one of the birds(e.g., COM1 = 1, COM2 = 2, etc.)
WORD COM_port[5] = {0,15,0,0,0}
if ((!birdRS232WakeUp(GROUP_ID,
FALSE, // Not stand-alone
DEVCOUNT, // Number of Devices
COM_port, // COM Port
BAUD_RATE, // BAUD
READ_TIMEOUT,WRITE_TIMEOUT, // Reponses timeouts
GMS_GROUP_MODE_ALWAYS)))
{
printf("Can't Wake Up Flock!\n");
Sleep(3000);
exit(-1);
}
This is how i do it in c sharp .
[DllImport(#"Bird.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern bool birdRS232WakeUp(int nGroupID, Boolean bStandAlone, int
nNumDevices,ref ushort pwComport, uint dwBaudRate, uint dwReadTimeout, uint
dwWriteTimeout);
ushort[] COM_port = new ushort[5]{0,13,0,0,0};
if ((!birdRS232WakeUp(GROUP_ID, false, DEVCOUNT,ref COM_port, BAUD_RATE, READ_TIMEOUT, WRITE_TIMEOUT)))
{
LWakeUpStatus.Text = "Failde to wake up FOB";
}
And finalyy i got this error message "Error 2 Argument '4': cannot convert from 'ref ushort[]' to 'ref ushort'"
Someone have any clue about it?
The immediate issue is that you're passing an array of type ushort when only a single ushort value is expected by your current managed extern definition:
That is:
ushort pwComport
Should be:
ushort[] pwComport
Related
Okay one more function that it's not yet working. I am basically calling some C++ functions from C# by using P/Invoke. The problematic function does query a show laser device for some device related information, such as minimal and maximal scan rates and maximal points per second.
The problematic function is:
int GetDeviceInfo(DWORD deviceIndex, DeviceInfo* pDeviceInfo);
Here's the C++ header file that I was given. That's a link to the very brief C++ SDK description. I don't have the sources to rebuild the DLL file and I also don't have the *.pdb file (the manufacturer can not supply it):
#pragma once
#ifdef STCL_DEVICES_DLL
#define STCL_DEVICES_EXPORT extern "C" _declspec(dllexport)
#else
#define STCL_DEVICES_EXPORT extern "C" _declspec(dllimport)
#endif
enum SD_ERR
{
SD_ERR_OK = 0,
SD_ERR_FAIL,
SD_ERR_DLL_NOT_OPEN,
SD_ERR_INVALID_DEVICE, //device with such index doesn't exist
SD_ERR_FRAME_NOT_SENT,
};
#pragma pack (1)
struct LaserPoint
{
WORD x;
WORD y;
byte colors[6];
};
struct DeviceInfo
{
DWORD maxScanrate;
DWORD minScanrate;
DWORD maxNumOfPoints;
char type[32];
};
//////////////////////////////////////////////////////////////////////////
///Must be called when starting to use
//////////////////////////////////////////////////////////////////////////
STCL_DEVICES_EXPORT int OpenDll();
//////////////////////////////////////////////////////////////////////////
///All devices will be closed and all resources deleted
//////////////////////////////////////////////////////////////////////////
STCL_DEVICES_EXPORT void CloseDll();
//////////////////////////////////////////////////////////////////////////
///Search for .NET devices (Moncha.NET now)
///Must be called after OpenDll, but before CreateDeviceList!
///In pNumOfFoundDevs can return number of found devices (optional)
//////////////////////////////////////////////////////////////////////////
STCL_DEVICES_EXPORT int SearchForNETDevices(DWORD* pNumOfFoundDevs);
//////////////////////////////////////////////////////////////////////////
///Creates new list of devices - previous devices will be closed
///pDeviceCount returns device count
//////////////////////////////////////////////////////////////////////////
STCL_DEVICES_EXPORT int CreateDeviceList(DWORD* pDeviceCount);
//////////////////////////////////////////////////////////////////////////
///Returns unique device name
///deviceIndex is zero based device index
//////////////////////////////////////////////////////////////////////////
STCL_DEVICES_EXPORT int GetDeviceIdentifier(DWORD deviceIndex, WCHAR** ppDeviceName);
//////////////////////////////////////////////////////////////////////////
///Send frame to device, frame is in following format:
///WORD x
///WORD y
///byte colors[6]
///so it's 10B point (=> dataSize must be numOfPoints * 10)
///scanrate is in Points Per Second (pps)
//////////////////////////////////////////////////////////////////////////
STCL_DEVICES_EXPORT int SendFrame(DWORD deviceIndex, byte* pData, DWORD numOfPoints, DWORD scanrate);
//////////////////////////////////////////////////////////////////////////
///Returns true in pCanSend if device is ready to send next frame
//////////////////////////////////////////////////////////////////////////
STCL_DEVICES_EXPORT int CanSendNextFrame(DWORD deviceIndex, bool* pCanSend);
//////////////////////////////////////////////////////////////////////////
///Send DMX if device supports it - pDMX must be (!!!) 512B long
//////////////////////////////////////////////////////////////////////////
STCL_DEVICES_EXPORT int SendDMX(DWORD deviceIndex, byte* pDMX);
//////////////////////////////////////////////////////////////////////////
///Send blank point to position x, y
//////////////////////////////////////////////////////////////////////////
STCL_DEVICES_EXPORT int SendBlank(DWORD deviceIndex, WORD x, WORD y);
//////////////////////////////////////////////////////////////////////////
///Get device info
//////////////////////////////////////////////////////////////////////////
STCL_DEVICES_EXPORT int GetDeviceInfo(DWORD deviceIndex, DeviceInfo* pDeviceInfo);
This is the complete C# test code I am currently using. All the functions work fine, except for GetDeviceInfo(...):
using System;
using System.Threading;
using System.Runtime.InteropServices;
namespace MonchaTestSDK {
public class Program {
[DllImport("..\\..\\dll\\StclDevices.dll", CallingConvention = CallingConvention.Cdecl)] // OK
public static extern int OpenDll();
[DllImport("..\\..\\dll\\StclDevices.dll", CallingConvention = CallingConvention.Cdecl)] // OK
public static extern void CloseDll();
[DllImport("..\\..\\dll\\StclDevices.dll", CallingConvention = CallingConvention.Cdecl)] // OK
public static extern int SearchForNETDevices(ref UInt32 pNumOfFoundDevs);
[DllImport("..\\..\\dll\\StclDevices.dll", CallingConvention = CallingConvention.Cdecl)] // OK
public static extern int CreateDeviceList(ref UInt32 pDeviceCount);
[DllImport("..\\..\\dll\\StclDevices.dll", CallingConvention = CallingConvention.Cdecl)] // OK
public static extern int GetDeviceIdentifier(UInt32 deviceIndex, out IntPtr ppDeviceName);
[DllImport("..\\..\\dll\\StclDevices.dll", CallingConvention = CallingConvention.Cdecl)] // OK
public static extern int SendFrame(UInt32 deviceIndex, LaserPoint[] pData, UInt32 numOfPoints, UInt32 scanrate);
[DllImport("..\\..\\dll\\StclDevices.dll", CallingConvention = CallingConvention.Cdecl)] // OK
public static extern int CanSendNextFrame(UInt32 deviceIndex, ref bool pCanSend);
[DllImport("..\\..\\dll\\StclDevices.dll", CallingConvention = CallingConvention.Cdecl)] // OK
public static extern int SendBlank(UInt32 deviceIndex, UInt16 x, UInt16 y);
[DllImport("..\\..\\dll\\StclDevices.dll", CallingConvention = CallingConvention.Cdecl)] // FAILS
public static extern int GetDeviceInfo(UInt32 deviceIndex, ref DeviceInfo pDeviceInfo);
[StructLayout(LayoutKind.Sequential, Pack=1)]
public struct LaserPoint {
public UInt16 x;
public UInt16 y;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] colors;
}
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct DeviceInfo {
public UInt32 maxScanrate;
public UInt32 minScanrate;
public UInt32 maxNumOfPoints;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string deviceType;
}
public static void Main(string[] args) {
Console.WriteLine("Moncha SDK\n");
OpenDll();
Console.WriteLine("StclDevices.dll is open.");
UInt32 deviceCount1 = 0;
int r1 = SearchForNETDevices(ref deviceCount1);
Console.WriteLine("SearchForNETDevices() [" + r1+"]: "+deviceCount1);
UInt32 deviceCount2 = 0;
int r2 = CreateDeviceList(ref deviceCount2);
Console.WriteLine("CreateDeviceList() ["+r2+"]: "+deviceCount2);
IntPtr pString;
int r3 = GetDeviceIdentifier(0, out pString);
string devname = Marshal.PtrToStringUni(pString);
Console.WriteLine("GetDeviceIdentifier() ["+r3+"]: "+devname);
DeviceInfo pDevInfo = new DeviceInfo();
pDevInfo.type = "";
int r4 = GetDeviceInfo(0, ref pDevInfo);
Console.WriteLine("GetDeviceInfo() ["+r4+"]: ");
Console.WriteLine(" - min: "+pDevInfo.minScanrate);
Console.WriteLine(" - max: " + pDevInfo.maxScanrate);
Console.WriteLine(" - points: " + pDevInfo.maxNumOfPoints);
Console.WriteLine(" - type: " + pDevInfo.deviceType);
Thread.Sleep(5000);
CloseDll();
}
}
}
On line 73 line 64 (cp. screenshot):
int r4 = GetDeviceInfo(0, ref pDevInfo);
I receive the following error:
An unhandled exception of type 'System.NullReferenceException' occured in MonchaTestSDK.exe
Additional information: Object reference not set to an instance of an object
This is the stack trace (can't provide better stack trace without the DLL's *.pdb file I guess):
MonchaTestSDK.exe!MonchaTestSDK.Program.Main(string[] args) Line 73 + 0xa bytes C#
mscoreei.dll!73a8d91b()
[Frames below may be incorrect and/or missing, no symbols loaded for mscoreei.dll]
mscoree.dll!73cae879()
mscoree.dll!73cb4df8()
kernel32.dll!74a08654()
ntdll.dll!77354b17()
ntdll.dll!77354ae7()
Some disassembly:
int r4 = GetDeviceInfo(0, ref pDevInfo);
05210749 int 3
0521074A push ebp
0521074B cwde
0521074C xor ecx,ecx
0521074E call 0521011C
05210753 int 3
05210754 test dword ptr [eax-1],edx
05210757 ?? ??
05210758 dec dword ptr [ebx-0AF7Bh]
0521075E dec dword ptr [ecx-6F466BBBh]
Any idea what I am doing wrong here?
Update 1: Suggested debug options:
As suggested in the comments, I tried to enable native/unmanaged code debugging:
Debug > Windows > Exceptions Settings > "Win32 Exceptions" checkbox ticked
Project > Properties > Debug tab > "Enable unmanaged code debugging" checkbox ticked
I still don't get any meaningful exception stack. The manufacturer can't supply me the DLL's *.pdb file.
Here's an image showing the debugger when stopped at the problematic line (debug settings are also shown):
Update 2: Minimal Required Code (cp. comment of mpromonet)
This is the minimal required code to be able to call GetDeviceInfo(...):
public static void Main(string[] args) {
OpenDll();
UInt32 deviceCount = 0;
CreateDeviceList(ref deviceCount);
DeviceInfo pDevInfo = new DeviceInfo();
GetDeviceInfo(0, ref pDevInfo); // error occurs on this line
CloseDll();
}
This leads to the exact same error as before:
An unhandled exception of type 'System.NullReferenceException' occured in MonchaTestSDK.exe
Additional information: Object reference not set to an instance of an object
Removing the call GetDeviceInfo(0, ref pDevInfo); from the code above allows the program to exit without any error.
Update 3: Removing char[] deviceType from DeviceInfo struct completely
I removed char[] deviceType from the struct defintion:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct DeviceInfo {
public UInt32 maxScanrate;
public UInt32 minScanrate;
public UInt32 maxNumOfPoints;
//[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
//public string deviceType;
}
When I run my C# test code now, I successfully receive maxScanrate, minScanrate and maxNumOfPoints back from the C++ DLL. Here's the corresponding console output:
GetDeviceInfo() [0]:
- min: 1000
- max: 40000
- points: 3000
Finally ending in the following error message:
Exception thrown at 0x67623A68 (clr.dll) in MonchaTestSDK.exe:
0xC0000005: Access violation reading location 0x00000000.
Final Update
I finally got an updated DLL from the manufacturer. There was indeed a bug within the SDK that caused the stack to get corrupted. So basically the following solution now works fine without any issues:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct DeviceInfo {
public UInt32 maxScanrate;
public UInt32 minScanrate;
public UInt32 maxNumOfPoints;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string deviceType;
}
private void queryDeviceProperties(UInt32 index) {
HwDeviceInfo pDevInfo = new HwDeviceInfo();
int code = GetDeviceInfo(index, ref pDevInfo);
if(code==0) {
Console.WriteLine(pDevInfo.minScanrate);
Console.WriteLine(pDevInfo.maxScanrate);
Console.WriteLine(pDevInfo.maxNumOfPoints);
Console.WriteLine(pDevInfo.type);
} else {
Console.WriteLine("Error Code: "+code);
}
}
Thank you all for the great support!
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] declares that the field is stored a char[32] array as in the header, i.e. space for 31 characters and a null terminator.
Marshalling this to a string shouldn't be a problem, nothing that the dll writes to the array should be able to cause a NullReferenceException.
I can compile a stub dll that loads fine using your C# code and can send back ANSI strings, with addition of typedef byte... and a stub method body e.g.:
int GetDeviceInfo(DWORD deviceIndex, DeviceInfo* pDeviceInfo)
{
std::string testString = "test string thats quite loooooooong";
pDeviceInfo->maxScanrate = 1234;
pDeviceInfo->minScanrate = 12345;
pDeviceInfo->maxNumOfPoints = 100 + deviceIndex;
sprintf_s(pDeviceInfo->type, "%.31s", testString.c_str());
return 0;
}
This works for me with VS2017 C++ and .Net 4.6.1.
What happens if you change the C# declaration to this:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct DeviceInfo
{
public UInt32 maxScanrate;
public UInt32 minScanrate;
public UInt32 maxNumOfPoints;
public UInt64 deviceTypePart1;
public UInt64 deviceTypePart2;
public UInt64 deviceTypePart3;
public UInt64 deviceTypePart4;
public string GetDeviceType()
{
if (Marshal.SizeOf(this) != 44) throw new InvalidOperationException();
List<byte> bytes = new List<byte>();
bytes.AddRange(BitConverter.GetBytes(deviceTypePart1));
bytes.AddRange(BitConverter.GetBytes(deviceTypePart2));
bytes.AddRange(BitConverter.GetBytes(deviceTypePart3));
bytes.AddRange(BitConverter.GetBytes(deviceTypePart4));
return Encoding.GetEncoding(1252).GetString(bytes.ToArray());
}
}
[Edit]
I've no idea why hand cranking the marshaling fixes this - be sure to 'load test' in case there are heap/stack corruption bugs still lurking.
In your old code, does Marshal.SizeOf return something other than 44?
A correct incantation is
string UnpackFixed(byte[] data, System.Text.Encoding encoding)
{
int i;
for (i = 0; i < data.Length; ++i)
if(data[i] == (byte)0)
break;
return encoding.GetString(data, i);
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
struct DeviceInfo
{
uint32 maxScanrate;
uint32 minScanrate;
uint32 maxNumOfPoints;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
byte type[];
};
DeviceInfo pDevInfo = new DeviceInfo();
pDevInfo.type = new byte[32];
int r4 = GetDeviceInfo(0, ref pDevInfo);
Console.WriteLine(" - type: " + UnpackFixed(pDevInfo.type));
I'm certain there is a way to do this with string but all the old obvious ways of doing it tended to pass the string to native code and get nothing back. Here, the exercise is to get a fixed-length byte string back. If you do solve it for string you will end up using System.Text.Encoding.Default which may or may not be right and there's no way to override it.
System.Text.Encoding.ASCII is plausibly wrong, in which case you need to deal with encodings. System.Text.Encoding.Default might work where ASCII didn't, in which case you should consider if you have weird failure modes on multi-byte character encodings. It's not clear if the device always uses the same encoding as the OS or if it assumes a fixed encoding (in which case you should specify the encoding).
I think you got a problem with public string type in DeviceInfo. If you needed to pass a string to the native part, that would be fine, but I understand that you're getting a char* from (allocated by) the native part, and in that case you're losing the address of type that is managed (and that cannot be known).
The right way to do this would be to have:
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct DeviceInfo {
public UInt32 maxScanrate;
public UInt32 minScanrate;
public UInt32 maxNumOfPoints;
public IntPtr type; // HERE: char*
}
and then process type in the managed part, for instance like this:
unsafe void GetType(IntPtr strPtr) => return new string((char*)strPtr);
If the native part doesn't do the allocation you'll need to use Marshal.AllocHGlobal.
I could use help with how to properly import several functions from a C++ DLL into my C# application. Here are a couple examples from the C++ side that show what I am trying to do in C#. I do not think I am properly marshaling either/or some of the return types and some of the parameters (especially pointers/ref/out).
C++ Header File Declarations:
unsigned long __stdcall mfcsez_initialisation(unsigned short serial);
unsigned char __stdcall mfcs_get_serial(unsigned long int handle,
unsigned short * serial);
unsigned char __stdcall mfcs_read_chan(unsigned long int handle,
unsigned char canal,
float * pressure,
unsigned short * chrono);
C++ Code:
/* Define functions prototype */
typedef unsigned long(__stdcall *init)(int);
typedef unsigned char(__stdcall *serial)(unsigned long handle, unsigned
short *serial);
typedef unsigned char(__stdcall *readChannel)(unsigned long handle,
unsigned char chan,
float * pressure,
unsigned short * chrono);
int main(int argc, char *argv[])
{
unsigned char pressureChannel = 1;
HINSTANCE hGetProcIDDLL=NULL;
/* Load DLL into memory */
hGetProcIDDLL = LoadLibrary(TEXT("mfcs64_c.dll"));
/* Declare pointers on dll functions */
init dll_init;
serial dll_serial;
readChannel dll_readChannel;
/* Link dll pointers with functions prototype */
dll_init = (init)GetProcAddress(hGetProcIDDLL,
"mfcsez_initialisation");
dll_serial = (serial)GetProcAddress(hGetProcIDDLL,
"mfcs_get_serial");
dll_readChannel = (readChannel)GetProcAddress(hGetProcIDDLL,
"mfcs_read_chan");
/* Define variables used for MFCS device */
unsigned long mfcsHandle;
unsigned short mySerial;
float read_pressure;
unsigned short chrono;
int loop_index;
if (hGetProcIDDLL != NULL)
{
std::cout << "mfcs_c.dll is loaded" << std::endl;
/* Initialize device */
if (dll_init != NULL)
{
/* Initialize the first MFCS in Windows enumeration list */
mfcsHandle = dll_init(0);
}
/* Read device serial number */
dll_serial(mfcsHandle, &mySerial);
for (loop_index = int(start_pressure);
loop_index<target_pressure; loop_index++)
{
Sleep(1000);
dll_readChannel(mfcsHandle, pressureChannel,
&read_pressure, &chrono);
}
}
return EXIT_SUCCESS;
}
I have tried importing them with various footprints. I am able to call mfcsez_initialisation and it works just fine as imported below. The other two I have tried many different ways and always get an exception - either from the DLL (unrecoverable) or from improper marshalling which I can try/catch.
Example of C# Import Statements:
[DllImport("mfcs_c_64.dll", CallingConvention =
CallingConvention.StdCall)]
protected static unsafe extern uint mfcsez_initialisation(ushort
serial_number);
[DllImport("mfcs_c_64.dll", CallingConvention =
CallingConvention.StdCall)]
public static unsafe extern byte mfcs_get_serial(uint handle, ref
ushort serial);
[DllImport("mfcs_c_64.dll", CallingConvention =
CallingConvention.StdCall)]
protected static unsafe extern byte mfcs_read_chan(ulong handle, byte
canal, ref float pressure, ref ushort chrono);
Example of C# Code:
unit mfcsHandle = mfcsez_initialisation(0); // Returns with valid handle
mfcs_get_serial(mfcsHandle, mySerial); // Memory write exception
float pressure = -1.0f;
ushort chrono = 0;
mfcs_read_chan(mfcsHandle, 1, ref pressure, ref chrono); // Same ex
Any and all help is appreciated!
As you have stated in comments (subsequently deleted), you can't be sure whether the problem lies in the interop or the parameters passed to the function. How are you going to resolve that doubt?
The way to do that is to create a test bed DLL that has functions with the same signatures, and then prove that you can move data correctly between that DLL and your C# p/invoke code. Once you can do that you can remove interop as a potential source of your problem, and concentrate on the parameters passed to the function. So, here is what is needed to make that test bed DLL.
dllmain.cpp
#include <Windows.h>
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Dll1.cpp
#include <iostream>
extern "C"
{
unsigned long __stdcall mfcsez_initialisation(unsigned short serial)
{
std::cout << "mfcsez_initialisation, " << serial << std::endl;
return 1;
}
unsigned char __stdcall mfcs_get_serial(unsigned long int handle,
unsigned short * serial)
{
std::cout << "mfcs_get_serial, " << handle << std::endl;
*serial = 2;
return 3;
}
unsigned char __stdcall mfcs_read_chan(unsigned long int handle,
unsigned char canal,
float * pressure,
unsigned short * chrono)
{
std::cout << "mfcs_read_chan, " << handle << ", " << static_cast<int>(canal) << std::endl;
*pressure = 4.5f;
*chrono = 5;
return 6;
}
}
Dll1.def
LIBRARY Dll1
EXPORTS
mfcsez_initialisation
mfcs_get_serial
mfcs_read_chan
Note that I am using a .def file to ensure that functions are exported using their undecorated names.
The C# program that calls this looks like so:
Program1.cs
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace ConsoleApp1
{
class Program
{
const string dllname = "Dll1.dll";
[DllImport(dllname, CallingConvention = CallingConvention.StdCall)]
static extern uint mfcsez_initialisation(ushort serial);
[DllImport(dllname, CallingConvention = CallingConvention.StdCall)]
static extern byte mfcs_get_serial(uint handle, out ushort serial);
[DllImport(dllname, CallingConvention = CallingConvention.StdCall)]
static extern byte mfcs_read_chan(uint handle, byte canal, out float pressure, out ushort chrono);
static void Main(string[] args)
{
uint retval1 = mfcsez_initialisation(11);
Console.WriteLine("return value = " + retval1.ToString());
Console.WriteLine();
ushort serial;
byte retval2 = mfcs_get_serial(12, out serial);
Console.WriteLine("serial = " + serial.ToString());
Console.WriteLine("return value = " + retval2.ToString());
Console.WriteLine();
float pressure;
ushort chrono;
byte retval3 = mfcs_read_chan(13, 14, out pressure, out chrono);
Console.WriteLine("pressure = " + pressure.ToString());
Console.WriteLine("chrono = " + chrono.ToString());
Console.WriteLine("return value = " + retval3.ToString());
Console.ReadLine();
}
}
}
The output is:
mfcsez_initialisation, 11
return value = 1
mfcs_get_serial, 12
serial = 2
return value = 3
mfcs_read_chan, 13, 14
pressure = 4.5
chrono = 5
return value = 6
As you can see, all the desired values travel between the two modules correctly. This demonstrates that the p/invoke interop code here is correct.
Notes:
In C++ on windows, both int and long int are 32 bit types. They therefore map to int on C#, oruint` for unsigned variants.
On C#, long and ulong are 64 bit types, and so do not match C++ int or long int`.
Map unsigned char on C++ to byte on C#.
There is no need for unsafe here. You would use unsafe if you needed to use pointers, but you don't, and seldom do.
I used out rather than ref because I infer that these parameters are only used for data flowing out of the DLL.
If you use this interop code against your DLL and still encounter failure then there are two plausible explanations:
The parameters you are passing to the DLL are incorrect.
The DLL is not what you think it is. Perhaps you have a version of the DLL which was built against different header files.
It depends what this DLL is besides written in C++.
If it is a C++ .NET DLL, you can use it like any other .NET DLL. And like the ones you already use provided by the Framework.
If it was written with .NET's predecessor COM in mind, you can use COM interop. Backwards compatibiltiy was thought about when making .NET
If it is neither of those, there is P/Invoke.
Note that COM interop and P/Invoke usually involve handling naked pointers. That means binarity issues and having to go into Unmanaged Code. I do not envy you for having to go that low level language.
This is the signature of my function in DLL:
int __stdcall myFun( void * const context, const char * const pszFileName, const unsigned int buffSize, void * const pWaveFormatex );
All parameters are [in]. The user should pass a pointer to a WAVEFORMATEX struct through the last parameter. Upon return, it will be filled. All that works very well in C++.
Now, I'm trying for days to use the same DLL from C# and it simply doesn't work. The problem is in the last parameter. Since I do not know C# at all, I would like to ask somebody if this is doable at all. If it is, I would appreciate an example.
One of my last attempts was this:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct WAVEFORMATEX
{
public ushort wFormatTag;
public ushort nChannels;
public uint nSamplesPerSec;
public uint nAvgBytesPerSec;
public ushort nBlockAlign;
public ushort wBitsPerSample;
public ushort cbSize;
}
Note: I also built my DLL written in C++ with the Struct Member Alignment = 1. Maybe I'm stupid, but I thought that Pack = 1 above is related with that in C++, but I have no idea if it is...
[DllImport("myLib.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public static extern int myFun( IntPtr context,
[MarshalAs( UnmanagedType.LPStr )]
string pszFileName,
int bufferSize,
ref IntPtr pWfx );
IntPtr unmanaged_pWfx = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WAVEFORMATEX)));
result = DLLWrapper.myFun(context,
"C:\\video.wmv",
176400,
ref unmanaged_pWfx );
WAVEFORMATEX wfxFormat = (WAVEFORMATEX)Marshal.PtrToStructure( unmanaged_pWfx, typeof(WAVEFORMATEX));
The behavior is undefined. Sometimes it hangs, sometimes it terminates... Something is very wrong. However, the problem is not in the DLL, the problem is on the C# side. Is C# capable of working with pointers at all?
Thanks for any feedback you provide.
EDIT (working C++ code):
void * context;
WAVEFORMATEX wfx;
int success = getContext( &context );
success = myFun( context, "C:\\video.wmv", 176400, &wfx );
The equivalent in C# is:
IntPtr context;
WAVEFORMATEX wfx;
int success = getContext( out context );
success = myFun( context, "C:\\video.wmv", 176400, out wfx );
extern "C" __stdcall int getContext( void ** pContext );
[DllImport("myLib.dll", CallingConvention = CallingConvention.StdCall)]
public static extern int getContext(out IntPtr context);
Well, based on the information you have provided I would say that you need to declare the C# function like this:
[DllImport("myLib.dll")]
public static extern int myFun(
IntPtr context,
string fileName,
uint bufferSize,
out WAVEFORMATEX wfx
);
And call the function like this:
WAVEFORMATEX wfx;
int result = DLLWrapper.myFun(context, #"C:\video.wmv", 176400, out wfx);
There's really no need for manual marshalling of this struct. It's a very simple blittable struct and it is much cleaner to let the framework handle the marshalling.
I am assuming that you are accurate when you state that the final struct parameter does not need to be initialised and its members are filled out by the function.
Packing looks reasonable. I don't think you need to build your DLL in any special way. I hope that you are picking up WAVEFORMATEX from the Windows header files and they already specify packing for that struct.
If you are still stuck then you should show the successful C++ calling code.
Judging from the comments, you still have a bug somewhere in your code. In such a situation, especially when you doubt the interop, it pays to make a simple reproduction to determine whether the interop is the problem, or not. Here is mine:
C++ DLL
#include <Windows.h>
#include <mmsystem.h>
#include <iostream>
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
__declspec(dllexport) int __stdcall myFun(void * const context,
const char * const pszFileName, const unsigned int buffSize,
void * const pWaveFormatex)
{
std::cout << context << std::endl
<< pszFileName << std::endl
<< buffSize << std::endl;
WAVEFORMATEX wfx;
wfx.cbSize = 1;
wfx.nAvgBytesPerSec = 2;
wfx.nBlockAlign = 3;
wfx.nChannels = 4;
wfx.nSamplesPerSec = 5;
wfx.wBitsPerSample = 6;
wfx.wFormatTag = 7;
CopyMemory(pWaveFormatex, &wfx, sizeof(wfx));
return 666;
}
C# console app
using System;
using System.Runtime.InteropServices;
namespace ConsoleApplication13
{
class Program
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct WAVEFORMATEX
{
public ushort wFormatTag;
public ushort nChannels;
public uint nSamplesPerSec;
public uint nAvgBytesPerSec;
public ushort nBlockAlign;
public ushort wBitsPerSample;
public ushort cbSize;
}
[DllImport(#"Win32Project1.dll", EntryPoint = "?myFun##YGHQAXQBDI0#Z")]
public static extern int myFun(
IntPtr context,
string fileName,
uint bufferSize,
out WAVEFORMATEX wfx
);
static void Main(string[] args)
{
WAVEFORMATEX wfx;
int result = myFun((IntPtr)42, #"C:\video.wmv", 176400, out wfx);
Console.WriteLine(result);
Console.WriteLine(wfx.cbSize);
Console.WriteLine(wfx.nAvgBytesPerSec);
Console.WriteLine(wfx.nBlockAlign);
Console.WriteLine(wfx.nChannels);
Console.WriteLine(wfx.nSamplesPerSec);
Console.WriteLine(wfx.wBitsPerSample);
Console.WriteLine(wfx.wFormatTag);
Console.ReadLine();
}
}
}
Output
0000002A
C:\video.wmv
176400
666
1
2
3
4
5
6
7
The problem is passing the IntPtr. You're passing stack allocated variable(the one that holds actual pointer) to your code, but you want to pass the pointer. Just remove the "ref" keyword from your code.
I am working with a 3. party SDK, which is made up from .dll, .lib and .h files. I am using the .dll's to PInvoke against. And the .h files to see the function names and parameters. (So I am not using the .lib files).
The SDK is rather complex, so making the PInvoke wrappers have proven to be a challenge. All the functions/structs/enums is defined in the .h files.
My question is how to implement a pinvoke for a function with 2 **.
I expect it is my C# function definition that is wrong.
When I call the function it simplely crashes, no exception throw or anything. The program just stops.
Function: GetInformatiuon(...)
//C Function: GetInformatiuon(...)
ERROR GetInformatiuon(
Component comp,
struct Information** Info);
//C# Function: GetInformatiuon(...)
[DllImport("externalSDK.dll", EntryPoint = "GetInformatiuon", CallingConvention = CallingConvention.Cdecl)]
public static extern ERROR GetInformatiuon(Component comp, ref Information Info);
);
Enum: ERROR
//C Enum: ERROR
typedef enum ERROR_E {
OK = 0, //Everything is ok
E_ARG = 1, //Error in the Arguments
E_DATA = 2 //Data error
//And more...
} ERROR;
//C# Enum: ERROR
public enum ERROR
{
OK = 0, //Everything is ok
E_ARG = 1, //Error in the Arguments
E_DATA = 2 //Data error
//And more...
}
Struct: Component
//C struct: Component
typedef struct Component_S
{
void* ObjPointer;
unsigned long number;
} Component;
//C# class: Component
[StructLayout(LayoutKind.Sequential)]
public class Component
{
public IntPtr ObjPointer;
public uint number; //uint because usigned long C is 4 bytes (32 bits) and C# ulong is 8 bytes (64 bits), where C# uint is 4 bytes(32 bits)
}
Struct: Information
//C struct: Information
typedef struct Information_S {
char* language;
unsigned long sampleFrequency;
unsigned long frameShift;
}Information;
//C# struct: Information
[StructLayout(LayoutKind.Sequential)]
public struct Information
{
public string language;
public uint sampleFrequency; //uint because usigned long C is 4 bytes (32 bits) and C# ulong is 8 bytes (64 bits), where C# uint is 4 bytes(32 bits)
public uint frameShift; //uint because usigned long C is 4 bytes (32 bits) and C# ulong is 8 bytes (64 bits), where C# uint is 4 bytes(32 bits)
}
I found the solution! (aka Hans Passant comment)
I changed the function to:
Function: GetInformatiuon(...)
//C Function: GetInformatiuon(...)
ERROR GetInformatiuon(
Component comp,
struct Information** Info);
//C# Function: GetInformatiuon(...)
[DllImport("externalSDK.dll", EntryPoint = "GetInformatiuon", CallingConvention = CallingConvention.Cdecl)]
public static extern ERROR GetInformatiuon(Component comp, out IntPtr InfoPtr);
);
Calling the function
IntPtr infoPtr;
lhErr = SDK.GetInformatiuon(component, out infoPtr);
Information info = (Information)Marshal.PtrToStructure(infoPtr, typeof(Information)) ;
I hav a function in c++ DLL that has following prototype
int function(RefPar ¶ms);
how can i call this function from a c# program using "DLLImport".
when i tried like below, AccessViolationException happened while running in visual studio 2008..
[DllImport("VistaGMMDLL.dll", EntryPoint = "function"]
unsafe static extern int function(ref RefPar params);
and called as..
int ret=function(ref params);
Note:RefPar structure has many
unsigned integer values and 1 enum
value as its members.
pls anyone help me to call the function correctly..
A couple of things jump out at me. First of all I don't see why you need to use unsafe. Secondly, you probably have a calling convention mismatch, cdecl in the C++ and stdcall in the C#.
I'd do it like this:
C++
struct RESOURCE_PARAMETERS{
unsigned int uSurfaceHeight;
unsigned int uSurfaceDepth;
unsigned int uSurfaceWidth;
unsigned int uMSAAHeight;
unsigned int uMSAAWidth;
unsigned int uArraySize;
unsigned int uNumSamples;
unsigned int uMaxLod;
unsigned int uBpp;
unsigned int uprefFlag;
unsigned int uusageFlag;
RESOURCE_TYPE_REC ResourceType;
int ResourceFormat;
int iBuildNumber;
};
int function(RefPar ¶meters)
{
}
C#
[StructLayout(LayoutKind.Sequential)]
public struct RESOURCE_PARAMETERS
{
uint uSurfaceHeight;
uint uSurfaceDepth;
uint uSurfaceWidth;
uint uMSAAHeight;
uint uMSAAWidth;
uint uArraySize;
uint uNumSamples;
uint uMaxLod;
uint uBpp;
uint uprefFlag;
uint uusageFlag;
[MarshalAs(UnmanagedType.U4)]
ResourceType ResourceType;
int ResourceFormat;
int iBuildNumber;
}
[DllImport("VistaGMMDLL.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int function(ref RESOURCE_PARAMETERS parameters);
RESOURCE_PARAMETERS parameters = new RESOURCE_PARAMETERS();
int result = function(ref parameters);
I'm not sure how big the enum is on the C++ size. That's why I've put an explicit MarshalAs in the C# code. If it's just a single byte, then use UnmanagedType.U1 instead. I trust you get the idea.
If your C++ function treats its parameter as an in/out parameter then using ref on the C# side is correct. If its actually an out parameter then change the code to be like this:
[DllImport("VistaGMMDLL.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int function(out RESOURCE_PARAMETERS parameters);
RESOURCE_PARAMETERS parameters;
int result = function(out parameters);
Try in this way:
[StructLayout(LayoutKind.Sequential)]
public struct RefPar
{
UInt32 uint1;
UInt32 unti2;
....
}
[DllImport("VistaGMMDLL.dll", EntryPoint = "function"]
unsafe static extern int function(IntPtr params);
//calling
//fill the refParStructure
//create the IntPtr
refParStruct rs = new RefPar();
IntPtr refparPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(refPar)));
Marshal.StructureToPtr(refParStruct, refparPtr,false);
ret=function(refParPtr);
Let Me know if you need more details
Another very simple way to call this function is:
Create a c++ dll warapper that link your original dll and contains this function
//c++ code
function2(uint param1, uint param2.....)
{
RefPar refpar;
refpar.param1=param1
refpar.param2=param2
function(&refpar)
}
in this way you have just to import (in C#) the dll wrapper function in this way
[DllImport("wrapperdll.dll", EntryPoint = "function2"]
static extern int function2(Uint32 param1,Uint32 param2....);
that is very simple to call.
Regards