Как вызвать функцию C ++ с параметром указателя структуры из C #? - PullRequest
0 голосов
/ 02 мая 2018

Хорошо, еще одна функция, которая еще не работает. Я в основном вызываю некоторые функции C ++ из C # с помощью P / Invoke. Проблемная функция запрашивает у устройства показа лазера некоторую информацию, относящуюся к устройству, такую ​​как минимальная и максимальная скорости сканирования и максимальные точки в секунду.

Проблемная функция:

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

Это трассировка стека (я не думаю, что лучше обеспечить трассировку стека без файла * .pdb DLL):

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»

  2. Проект> Свойства> вкладка Отладка> Поставлен флажок «Включить отладку неуправляемого кода»

Я до сих пор не получил никакого значимого стека исключений. Производитель не может предоставить мне файл DLL * .pdb.

Вот изображение, показывающее отладчик при остановке на проблемной строке (также отображаются настройки отладки):

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


Обновление 2: минимальный необходимый код (см. Комментарий 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 struct полностью

Я удалил 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 #, я успешно получаю maxScanrate, minScanrate и maxNumOfPoints обратно из C ++ DLL. Вот соответствующий вывод консоли:

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

Наконец заканчивается следующим сообщением об ошибке:

Исключение, сгенерированное в 0x67623A68 (clr.dll) в MonchaTestSDK.exe: 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);
    }
}

Спасибо всем за отличную поддержку!

Ответы [ 3 ]

0 голосов
/ 10 мая 2018

Я думаю, у вас проблема с 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 .

0 голосов
/ 11 мая 2018

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] объявляет, что в поле хранится массив char[32], как в заголовке, то есть пространство для 31 символа и нулевой терминатор.

Выделение этого в строку не должно быть проблемой, ничто из того, что dll записывает в массив, не должно вызывать NullReferenceException.

Я могу скомпилировать заглушку dll, которая прекрасно загружается с использованием вашего кода C # и может отправлять обратно строки 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<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());
        }
    }

[Изменить]

Я понятия не имею, почему ручная прокрутка маршалинга исправляет это - обязательно «загрузите тест» на случай, если все еще скрываются ошибки повреждения кучи / стека.

В вашем старом коде Marshal.SizeOf возвращает что-то отличное от 44?

0 голосов
/ 08 мая 2018

Правильное заклинание

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, и в этом случае вы должны рассмотреть, есть ли у вас странные режимы сбоев в многобайтовых кодировках символов. Не ясно, использует ли устройство ту же кодировку, что и в ОС, или предполагает фиксированную кодировку (в этом случае следует указать кодировку).

...