Как вызвать функцию C++ с параметром указателя структуры из C#?
хорошо еще одна функция, которая еще не работает. Я в основном вызываю некоторые функции C++ из C# с помощью P / Invoke. Проблемная функция запрашивает лазерное устройство show для некоторой информации, связанной с устройством, такой как минимальная и максимальная скорости сканирования и максимальные точки в секунду.
проблемная функция:
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("....dllStclDevices.dll", CallingConvention = CallingConvention.Cdecl)] // OK
public static extern int OpenDll();
[DllImport("....dllStclDevices.dll", CallingConvention = CallingConvention.Cdecl)] // OK
public static extern void CloseDll();
[DllImport("....dllStclDevices.dll", CallingConvention = CallingConvention.Cdecl)] // OK
public static extern int SearchForNETDevices(ref UInt32 pNumOfFoundDevs);
[DllImport("....dllStclDevices.dll", CallingConvention = CallingConvention.Cdecl)] // OK
public static extern int CreateDeviceList(ref UInt32 pDeviceCount);
[DllImport("....dllStclDevices.dll", CallingConvention = CallingConvention.Cdecl)] // OK
public static extern int GetDeviceIdentifier(UInt32 deviceIndex, out IntPtr ppDeviceName);
[DllImport("....dllStclDevices.dll", CallingConvention = CallingConvention.Cdecl)] // OK
public static extern int SendFrame(UInt32 deviceIndex, LaserPoint[] pData, UInt32 numOfPoints, UInt32 scanrate);
[DllImport("....dllStclDevices.dll", CallingConvention = CallingConvention.Cdecl)] // OK
public static extern int CanSendNextFrame(UInt32 deviceIndex, ref bool pCanSend);
[DllImport("....dllStclDevices.dll", CallingConvention = CallingConvention.Cdecl)] // OK
public static extern int SendBlank(UInt32 deviceIndex, UInt16 x, UInt16 y);
[DllImport("....dllStclDevices.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 SDKn");
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 линия 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.Программа.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. предлагаемые варианты отладки:
как было предложено в комментариях, я попытался включить отладку собственного / неуправляемого кода:
Debug > Windows > настройки исключений > флажок "исключения Win32" отмечен
проект > свойства > вкладка "отладка" > "флажок включить отладку неуправляемого кода" тикали
я все еще не получаю никакого значимого стека исключений. Производитель не может предоставить мне DLL *.PDB-файл.
вот изображение, показывающее отладчик при остановке в проблемной строке (также показаны настройки отладки):
Обновление 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
полностью структуры
я убрал char[] deviceType
от 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;
}
когда я запускаю свой тестовый код C# сейчас, я успешно получаю maxScanrate
, minScanrate
и maxNumOfPoints
назад из библиотеки DLL C++. Вот соответствующий консольный вывод:
GetDeviceInfo() [0]:
- min: 1000
- max: 40000
- points: 3000
наконец заканчивается следующее сообщение об ошибке:
исключение, брошенное в 0x67623A68 (clr.dll) в MonchaTestSDK.исполняемый: 0xC0000005: место чтения нарушения доступа 0x00000000.
финал Update
я, наконец, получил обновленную 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 ответов
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
объявляет, что поле хранится char[32]
array как в заголовке, т. е. пробел для 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());
}
}
[Edit]
Я понятия не имею, почему ручное сгибание маршалинга исправляет это - обязательно "нагрузочный тест" в случае, если все еще скрываются ошибки повреждения кучи/стека.
в вашем старом коде делает 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
не сделал, и в этом случае вы должны рассмотреть, есть ли у вас странные режимы сбоя в многобайтовых кодировках символов. Неясно, всегда ли устройство использует ту же кодировку, что и ОС, или предполагает фиксированную кодировку (в этом случае вы должны указать кодировку).
Я думаю, у вас проблема с 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);
если родная часть не выполняет выделение, вам нужно будет использовать Маршал.AllocHGlobal.