PInvoke 结构/函数中的奇怪错误

发布于 2024-11-26 21:10:53 字数 3334 浏览 0 评论 0原文

我目前正在为 C++ API 编写 C# 包装器,但特定的结构和依赖于该结构的函数在调试时给出了非常奇怪的错误。

C++ 结构:

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 应用程序,各种堆栈溢出文章,例如 这个,以及 这篇文章似乎更接近什么我正在做,但我仍在深入研究如何使用 Marshal.CoTaskMemAlloc/Free,看看它是否会做任何事情...

到目前为止,我的结构和函数是正确的,我已经尝试更改struct 使用 IntPtr 但不返回 di.name 值,并且 di.unique_ID 变得乱码(奇怪的是 di.handle 保持有效)

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++ 字符封送到字符串,但我不确定问题是否在于封送、内存管理(没有?),或者是我没有完全理解的东西。

任何和所有的反馈都将非常感激,我已经被这个问题困扰了好几个星期了......

Im currently writing a C# wrapper for a C++ API, but a specific struct and a function that relies on this struct have been giving very strange errors when debugging.

C++ Struct:

typedef struct  
{  
    unsigned __int handle;  
    char name[80];  
    unsigned int unique_ID;  
} DeviceInfo;

Followed by this function:

int __stdcall get_device_info(DeviceInfo di[], const int length_of_di_array, int* p_numValidDevices);

The struct and function is imported as such:

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

The intended use of this struct and function is just to obtain some device info from the board Im accessing. Currently I do not have access to the function body in C++, so I can only assume it's working 100% (works fine in C++).

The issue is that when I use the function to run through an array of structs, it outputs the data I'm looking for, but also will begin to fail at runtime, giving me various error windows.

C# code:

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
}

Errors while debugging (information is still shown):
"An unhandled exception of type 'System.Threading.ThreadStateException' occurred in System.dll

Additional information: Thread has not been started."

"An unhandled exception of type 'System.ExecutionEngineException' occurred in mscorlib.dll"

Error while debugging (information is not shown, program fails to execute):
"An unhandled exception of type 'System.AccessViolationException' occurred in mscorlib.dll

Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt."

When closing the console window:
"The instruction at '0x7c9113c0' referenced memory at '0x00000000'. The memory could not be 'written'." (sometimes says 'read' instead of 'written').

I've done a lot of research on PInvoke and came across the Microsoft InteropAssistant application, various stack overflow articles such as this one, and this post seems even closer to what Im doing, but I'm still digging into how to use the Marshal.CoTaskMemAlloc/Free, and see if it even will do anyhting...

Thus far what I have for my struct and function are correct, I've tried changing the struct to use an IntPtr but that does not return a di.name value and the di.unique_ID becomes jibberish (oddly enough the di.handle stays valid)

C# code:

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

Intended output:

Handle: 3126770193  
Name: DEVICE_A  
Unique ID: 12345678  

IntPtr output:

Handle: 3126770193  
Name:  
Unique ID: 1145128264  

Oddly enough, using an IntPtr results in none of the errors above, and runs fine.
This leads me to believe the issue lies with marshaling over the C++ char to a string, but I'm not sure if the issue lies with the marshaling, memory management (there is none?), or something I'm not catching entirely.

Any and all feedback would be really appreciated, I've been stumped on this for a number of weeks now...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

机场等船 2024-12-03 21:10:53

您收到的异常表明您正在调用的非托管代码正在破坏垃圾收集堆。这并不是很明显的原因,但你没有给 pinvoke 编组器太多机会去做正确的事情。它无法正确固定阵列。首先正确声明该函数,它需要一个数组,因此声明一个:

[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 的第一个声明是正确的,第二个声明是错误的,因为该字符串不是指针。

The exceptions you get indicate that the unmanaged code you are pinvoking is destroying the garbage collected heap. It isn't crystal why, but you don't give the pinvoke marshaller much of a chance to do the Right Thing. It cannot properly pin the array. Start by declaring the function properly, it takes an array so declare one:

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

Your first declaration of DeviceInfo is correct, the 2nd isn't since the string isn't a pointer.

涙—继续流 2024-12-03 21:10:53

这里有些东西没有加起来。我不清楚该函数应该如何调用。

特别是,此声明:

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(...);
}

PS 我会考虑将您的 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);

Something's not adding up, here. It's not clear to me how the function is supposed to be called.

In particular, this declaration:

int __stdcall get_device_info(DeviceInfo di[], const int length_of_di_array, int* p_numValidDevices);

doesn't match how you're using it:

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

You're telling it that the array length is 16, starting at index i, which is wrong. Did you mean to only pass one element of the array at a time?

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
}

Or did you mean to pass the entire array once?

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. I would consider changing your p/invoke declaration to be:

[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);
任性一次 2024-12-03 21:10:53

因此,正如下面的回复所指出的,我遇到了两个问题:
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();
}

So, as pointed out in the replies below, I had two problems:
1. I wasn't calling my DllImport correctly, the way I had it was in an attempt to hack together an output, in doing so I screwed up the memory allocation to the array of structs.
2. I tried to hack together an output and screwed up the code even more (tried to pass in the DeviceInfo array di as a single element di[number] instead of as a whole).

[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();
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文