【问题标题】:Odd Errors from PInvoke struct/function来自 PInvoke 结构/函数的奇怪错误
【发布时间】:2011-07-29 13:47:18
【问题描述】:

我目前正在为 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 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 编组为字符串,但我不确定问题是否在于编组、内存管理(没有?)或我没有完全理解的东西。

非常感谢任何和所有反馈,我已经为此困扰了好几个星期了......

【问题讨论】:

  • 您正在传递一个引用,但它需要一个数组。
  • 还要确保 C# 中的 sizeof == C 中的 sizeof。我怀疑该名称被声明为错误。改用 byte[] 并应用值数组大小常量。
  • 您应该发布答案,而不是编辑问题。你愿意这样做吗?
  • @David Heffernan 啊,感谢您的提醒(我知道我来晚了),编辑了帖子并发布了答案。

标签: c# memory struct pinvoke marshalling


【解决方案1】:

您得到的异常表明您正在调用的非托管代码正在破坏垃圾收集堆。这不是很明显的原因,但是您没有给 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】:

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

特别是这个声明:

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

【讨论】:

  • 你说得对,我重新评估了我对该函数的使用,并将我的 DllImport 更改为你所拥有的,它成功了!谢谢! (澄清作为对原始帖子的编辑添加)
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 2014-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-16
    • 2020-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多