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

时间:2018-05-02 15:41:46

标签: c# c++ dll interop dllimport

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

有问题的功能是:

int GetDeviceInfo(DWORD deviceIndex, DeviceInfo* pDeviceInfo);

这是我给出的C ++头文件。 That's a link to the very brief C++ SDK description。我没有重建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. screenshot):

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. 调试> Windows>例外设置> “Win32 Exceptions”复选框勾选

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

  4.   

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

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

Here's an image showing the debugger when stopped at the problematic line (debug settings are also shown)

更新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:从char[] deviceType结构中删除DeviceInfo

我从结构定义中删除了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)抛出异常:   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);
    }
}

感谢大家的大力支持!

2 个答案:

答案 0 :(得分:-1)

正确的咒语是

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没有的情况下工作,在这种情况下,您应该考虑在多字节字符编码上是否存在奇怪的故障模式。不清楚设备是否总是使用与操作系统相同的编码,或者它是否采用固定编码(在这种情况下,您应该指定编码)。

答案 1 :(得分:-1)

我认为public string type中的DeviceInfo出现了问题。如果你需要将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