c# - pinvoke 在调用 C 代码时给出 AccessViolationException

标签 c# c pinvoke

我的一位同事用 C 编写了一个库,我想从 C# 调用它。我认为这几乎是正确的,但是当我调用第二个方法/函数时它得到了一个 AccessViolationException。我尝试了一些不同的东西:

  1. 类而不是结构
  2. 将 [MarshalAs(UnmanagedType.FunctionPtr)]​​ 添加到回调/委托(delegate)(这实际上会在第一次调用时抛出异常,而不是第二次调用)
  3. 为结构中的字符串删除 [MarshalAs(UnmanagedType.LPStr)](这将在第二个方法结束大括号后抛出异常)

下面的代码用于 C# 端和我正在使用的 C header 。

C:

#pragma once
#define MAX_FEATURE_LENGTH 30
#define HELPERDLL_API __declspec(dllimport) 

struct HelperAttributes {
  void(*SaveCallBack) ();
  void(*ExitCallBack) ();
  char* feature_name;
  int attempt_limit;
  int check_interval;   
}; 

extern "C"
{
  void HELPERDLL_API DoStartUp();
  void HELPERDLL_API ReSpecify();
  void HELPERDLL_API Initialise(HelperAttributes* attributes);
}

C#:

namespace test
{
  public partial class Form1 : Form
  {
    public delegate void SaveCallBack();
    public delegate void ExitCallBack();

    public Form1()
    {
        InitializeComponent();
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct HelperAttributes 
    {
        public SaveCallBack saveCallBack;
        public ExitCallBack exitCallBack;
        [MarshalAs(UnmanagedType.LPStr)]
        public string feature_name;
        public int attempt_limit;
        public int check_interval;
    };

    [DllImport("testing.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int DoStartUp();
    [DllImport("testing.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int ReSpecify();
    [DllImport("testing.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int Initialise(
                                        [In, MarshalAs(UnmanagedType.LPStruct)]
                                        HelperAttributes attributes
                                       );

    private void button1_Click(object sender, EventArgs e)
    {
        HelperAttributes attributes = new HelperAttributes();

        attributes.saveCallBack = saveCallBackDelegate;
        attributes.exitCallBack = exitCallBackDelegate;
        attributes.feature_name = "XXX";
        attributes.attempt_limit = 10;
        attributes.check_interval = 30;

        Initialise(attributes);
        DoStartUp();
    }

    public void saveCallBackDelegate()
    {
        this.textBox1.Text = "save callback made";
    }

    public void exitCallBackDelegate()
    {
        this.textBox1.Text = "exit callback made";
    }
  }
}

最佳答案

    HelperAttributes attributes = new HelperAttributes();

那是非常非常麻烦的。您在此代码中有明显的内存管理问题。您在此处分配的结构的生命周期非常有限。它仅在 Click 事件处理程序方法的持续时间内有效,最多纳秒。这以不止一种方式爆炸:

  • C 代码不能存储传递的指针,它必须复制结构。它可能不会那样做。你现在有一个“悬挂指针”,一个臭名昭著的 C 错误。稍后取消引用指针会产生任意垃圾。

  • 您的代码创建并分配给结构的 saveCallback 和 exitCallback 成员的委托(delegate)对象无法存活。垃圾收集器无法发现 C 代码要求它们保持事件状态。因此,下一次垃圾收集会销毁对象,当 C 代码进行回调时,Big Kaboom。另请注意,如果它们实际采用参数,您必须使用 [UnmanagedFunctionPointer] 声明它们以使它们成为 Cdecl。

解决这些问题的方法不止一种。到目前为止,最简单的方法是将变量移出方法并将其声明为static。这使它在程序的生命周期内有效。垃圾收集器总是可以通过这种方式看到对委托(delegate)对象的引用。但是,您不能绕过修复 C 代码并让它制作副本的需要,此结构不可 blittable,并且 C 代码获取指向 pinvoke 编码器创建的临时副本的指针。一旦 Initialise() 返回,它就会变成垃圾。

关于c# - pinvoke 在调用 C 代码时给出 AccessViolationException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24205025/

相关文章:

c# - 将值从 Angular 服务应用程序发送到 C# 后端 Controller

c - 堆损坏检测到 C 中的内存泄漏

c++ - (C/C++) Windows 7 中的 EOF

c# - 将指向 C# 结构的指针发送到 C++ DLL

c# - 在 P/Invoke 调用上固定 char[]

c# - Silverlight 5 + Internet Explorer 9 在后续 GET 中使用来自 POST 的旧内容类型?

C# 编译器没有找到 dll;在VS2010中编译的解决方案

c# - ASP.NET Core 中 Startup.cs 中的 Kestrel 关闭函数

c - 计算整数中不同数字的最有效方法是什么

c# - 如何在 C# 中使用 CreateUserProfileEx