如何使用C#中的struct pointer参数调用C ++函数?

还有一个function,它还没有工作。 我基本上是通过使用P / Invoke从C#调用一些C ++函数。 有问题的function确实向显示激光设备查询一些设备相关信息,例如最小和最大扫描速率以及每秒最大点数。

有问题的function是:

int GetDeviceInfo(DWORD deviceIndex, DeviceInfo* pDeviceInfo); 

这是我给出的C ++头文件。 这是非常简短的C ++ SDK描述的链接 。 我没有重建DLL文件的源代码,我也没有* .pdb文件(制造商无法提供):

 #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); 

这是我目前使用的完整C#测试代码。 除了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(); } } } 

第73行第64行( cp。截图 ):

 int r4 = GetDeviceInfo(0, ref pDevInfo); 

我收到以下错误:

 An unhandled exception of type 'System.NullReferenceException' occured in MonchaTestSDK.exe Additional information: Object reference not set to an instance of an object 

这是堆栈跟踪(如果没有DLL的* .pdb文件,我无法提供更好的堆栈跟踪):

MonchaTestSDK.exe!MonchaTestSDK.Program.Main(string [] args)第73行+ 0xa字节C#mscoreei.dll!73a8d91b()
[下面的框架可能不正确和/或缺失,没有为mscoreei.dll加载符号]
mscoree.dll中!73cae879()
mscoree.dll中!73cb4df8()
KERNEL32.DLL!74a08654()
ntdll.dll中!77354b17()
ntdll.dll中!77354ae7()

一些反汇编:

  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] 

知道我在这里做错了吗?


更新1:建议的调试选项:

正如评论中所建议的那样,我尝试启用本机/非托管代码调试:

  1. 选中Debug> Windows> Exceptions Settings>“Win32 Exceptions”复选框

  2. 选中项目>属性>调试选项卡>“启用非托管代码调试”复选框

我仍然没有得到任何有意义的exception堆栈。 制造商无法提供DLL的* .pdb文件。

这是一个显示调试器停止在有问题的行时的图像(还显示了调试设置):

这是一个显示调试器停止在有问题的行时的图像(还显示了调试设置)


更新2:最低要求代码(cp。评论mpromonet

这是能够调用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(); } 

这导致与以前完全相同的错误:

 An unhandled exception of type 'System.NullReferenceException' occured in MonchaTestSDK.exe Additional information: Object reference not set to an instance of an object 

删除调用GetDeviceInfo(0, ref pDevInfo); 从上面的代码允许程序退出没有任何错误。


更新3:完全从DeviceInfo结构中删除char[] deviceType

我从struct char[] deviceType删除了char[] deviceType

 [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; } 

当我现在运行我的C#测试代码时,我成功地从C ++ DLL接收了maxScanrateminScanratemaxNumOfPoints 。 这是相应的控制台输出:

 GetDeviceInfo() [0]: - min: 1000 - max: 40000 - points: 3000 

最后以下面的错误消息结束:

MonchaTestSDK.exe中的0x67623A68(clr.dll)抛出exception:0xC0000005:访问冲突读取位置0x00000000。


最后更新

我终于从制造商那里得到了一个更新的DLL。 SDK中确实存在一个导致堆栈损坏的错误。 所以基本上以下解决方案现在正常工作没有任何问题:

 [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); } } 

谢谢大家的大力支持!

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]声明该字段存储在标头中的char[32]数组中,即31个字符的空间和空终止符。

将其编组为字符串应该不是问题,dll写入数组的任何内容都不应该导致NullReferenceException

我可以使用您的C#代码编译一个可以正常加载的存根dll,并且可以发送回ANSI字符串,添加了typedef byte...和一个存根方法体,例如:

 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; } 

这适用于VS2017 C ++和.Net 4.6.1。

如果您将C#声明更改为此会发生什么:

  [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 bytes = new List(); 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()); } } 

[编辑]

我不知道为什么手动编组修复了这个问题 – 请确保’加载测试’以防万一仍存在堆/堆栈损坏错误。

在您的旧代码中, Marshal.SizeOf返回44以外的其他内容吗?

正确的咒语是

 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)); 

我确定有一种方法可以使用string来完成此操作,但所有旧的显而易见的方法都倾向于将字符串传递给本机代码并且什么也得不回来。 这里,练习是获取一个固定长度的字节串。 如果你为字符串解决它,你将最终使用System.Text.Encoding.Default ,它可能是也可能不对,并且没有办法覆盖它。

System.Text.Encoding.ASCII似乎是错误的,在这种情况下你需要处理编码。 System.Text.Encoding.Default可能在ASCII没有的情况下工作,在这种情况下,您应该考虑在多字节字符编码上是否有奇怪的故障模式。 目前尚不清楚设备是否始终使用与操作系统相同的编码,或者它是否采用固定编码(在这种情况下,您应指定编码)。

我认为你在DeviceInfo遇到了public string type的问题。 如果你需要将一个string传递给本机部分,那就没问题了,但是我知道你从本机部分获得了一个char* (由它分配),在这种情况下你丢失了那个type的地址管理(并且无法知道)。

正确的方法是:

 [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)] public struct DeviceInfo { public UInt32 maxScanrate; public UInt32 minScanrate; public UInt32 maxNumOfPoints; public IntPtr type; // HERE: char* } 

然后在托管部分中处理type ,例如:

 unsafe void GetType(IntPtr strPtr) => return new string((char*)strPtr); 

如果本机部分没有进行分配,则需要使用Marshal.AllocHGlobal 。