我正在为C ++ API编写一个C#包装器,但是一个特定的结构和一个依赖于这个结构的函数在调试时给出了非常奇怪的错误。
C ++ Struct:
typedef struct
{
unsigned __int handle;
char name[80];
unsigned int unique_ID;
} DeviceInfo;
接下来是这个功能:
int __stdcall get_device_info(DeviceInfo di[], const int length_of_di_array, int* p_numValidDevices);
结构和函数按原样导入:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DeviceInfo
{
public UInt32 handle;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 80)]
public String name;
public UInt32 unique_ID;
}
[DllImportAttribute("MyC++API.dll", EntryPoint = "get_device_info", CallingConvention = CallingConvention.StdCall)]
public static extern int get_device_info(ref DeviceInfo di, int length_of_di_array, ref numValidDevices);
此结构和功能的预期用途只是从我访问的板上获取一些设备信息。目前我无法访问C ++中的函数体,所以我只能假设它100%工作(在C ++中工作正常)。
问题是当我使用该函数运行一个结构数组时,它会输出我正在寻找的数据,但也会在运行时开始失败,给我各种错误窗口。
C#代码:
static void Main()
{
int numValidDevices = 0; //initialize variable
DeviceInfo[] di = new DeviceInfo[16]; //max of 16 devices
for (int i = 0; i < numValidDevices; ++i) //sorts through all validated devices
{
rc = get_device_info(ref di[i], 16, ref numValidDevices); //accesses each device element and returns the data
Console.WriteLine("Handle: {0}\nName: {1}\nUnique ID: {2}", di[i].handle, di[i].name, di[i].unique_ID);
}
Console.ReadLine(); //stops console from closing prematurely
API_close(); //custom close function from the C++ API
}
调试时出错(信息仍然显示): “System.dll中发生了'System.Threading.ThreadStateException'类型的未处理异常
其他信息:线程尚未启动。“
“mscorlib.dll中发生了'System.ExecutionEngineException'类型的未处理异常”
调试时出错(未显示信息,程序无法执行): “mscorlib.dll中发生了'System.AccessViolationException'类型的未处理异常
附加信息:尝试读取或写入受保护的内存。这通常表明其他内存已损坏。“
关闭控制台窗口时: “'0x7c9113c0'处的指令在'0x00000000'处引用了内存。内存无法'写'。” (有时候说'读'而不是'书面')。
我已经对PInvoke进行了大量研究并遇到Microsoft InteropAssistant application,各种堆栈溢出文章如this one和this post似乎更接近我正在做的事情,但是我还在深入研究如何使用Marshal.CoTaskMemAlloc / Free,看看它是否会做任何事......
到目前为止,我的结构和函数都是正确的,我已经尝试更改结构以使用IntPtr但是不返回di.name值而di.unique_ID变为乱码(奇怪的是di。句柄保持有效)
C#代码:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DeviceInfo
{
public UInt32 handle;
IntPtr p_name;
public String name { get { return Marshal.PtrToStringAnsi(p_name); } }
public UInt32 unique_ID;
}
预期输出:
Handle: 3126770193
Name: DEVICE_A
Unique ID: 12345678
IntPtr输出:
Handle: 3126770193
Name:
Unique ID: 1145128264
奇怪的是,使用IntPtr不会导致上述任何错误,并且运行正常。 这让我相信问题在于将C ++ char编组到一个字符串上,但是我不确定问题是由于编组,内存管理(没有?),还是我没有完全捕获的东西。
任何和所有的反馈都会非常感激,我已经被困了好几个星期了......
答案 0 :(得分:2)
这里有些东西没有加起来。我不清楚该函数应该如何被调用。
特别是,这个声明:
int __stdcall get_device_info(DeviceInfo di[], const int length_of_di_array, int* p_numValidDevices);
与您使用它的方式不符:
[DllImportAttribute("MyC++API.dll", EntryPoint = "get_device_info", CallingConvention = CallingConvention.StdCall)]
public static extern int get_device_info(ref DeviceInfo di, const int length_of_di_array, int* p_numValidDevices);
...
DeviceInfo[] di = new DeviceInfo[16]; //max of 16 devices
for (int i = 0; i < numValidDevices; ++i) //sorts through all validated devices
{
rc = get_device_info(ref di[i], 16, ref numValidDevices); //accesses each device element and returns the data
}
你告诉它数组长度为16
,从索引i
开始,这是错误的。你的意思是一次只传递一个数组元素吗?
DeviceInfo[] di = new DeviceInfo[16]; //max of 16 devices
for (int i = 0; i < numValidDevices; ++i) //sorts through all validated devices
{
rc = get_device_info(ref di[i], 1, ref numValidDevices); //accesses each device element and returns the data
}
或者你的意思是将整个数组传递一次?
DeviceInfo[] di = new DeviceInfo[16]; //max of 16 devices
rc = get_device_info(ref di[0], 16, ref numValidDevices); //accesses each device element and returns the data
for (int i = 0; i < numValidDevices; ++i) //sorts through all validated devices
{
Console.WriteLine(...);
}
P.S。我会考虑将你的p / invoke声明更改为:
[DllImportAttribute("MyC++API.dll", EntryPoint = "get_device_info", CallingConvention = CallingConvention.StdCall)]
public static extern int get_device_info(
[In, Out] [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] DeviceInfo[] di,
int length_of_di_array,
ref int p_numValidDevices);
答案 1 :(得分:2)
您获得的异常表明您正在进行异步操作的非托管代码正在销毁垃圾回收堆。它不是水晶为什么,但是你没有给pinvoke marshaller很多机会做正确的事。它无法正确固定阵列。首先正确声明函数,它需要一个数组,因此声明一个:
[DllImportAttribute("MyC++API.dll", CallingConvention = CallingConvention.StdCall)]
public static extern int get_device_info(
DeviceInfo[] di,
int length_of_di_array,
out int p_numValidDevices
);
你的第一个DeviceInfo声明是正确的,第二个声明是因为字符串不是指针。
答案 2 :(得分:0)
因此,正如下面的答复所指出,我有两个问题:
1.我没有正确地调用我的DllImport,我采用它的方式是试图将输出组合在一起,这样做我把内存分配搞砸了结构数组。
2.我试图破解输出并将代码搞砸了(尝试将DeviceInfo数组di作为单个元素di [number]而不是整体传递)。
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DeviceInfo
{
public UInt32 handle;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 80)]
public String name;
public UInt32 unique_ID;
}
[DllImportAttribute("MyC++API.dll", EntryPoint = "get_device_info", CallingConvention = CallingConvention.StdCall)]
public static extern int get_device_info(
[In, Out] [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] DeviceInfo[] di,
int length_of_di_array,
ref int p_numValidDevices);
static void Main()
{
int numValidDevices = 0;
DeviceInfo[] di = new DeviceInfo[16];
get_device_info(di, 16, ref numValidDevices);
for (int i = 0; i < numValidDevices; ++i)
{
Console.WriteLine("Handle: {0}\nName: {1}\nUnique ID: {2}", di[i].handle, di[i].name, di[i].unique_ID);
}
Console.ReadLine();
API_close();
}