pinvoke在调用C代码时给出AccessViolationException

时间:2014-06-13 12:13:40

标签: c# c pinvoke

我的同事有一个用C制作的图书馆,我想用C#来称呼它。我认为这几乎是正确的但是当我调用第二个方法/函数时它会得到一个AccessViolationException。我尝试过几件不同的事情:

  1. 类而不是struct
  2. 将[MarshalAs(UnmanagedType.FunctionPtr)]添加到回调/委托中(这实际上会在第一次调用而不是第二次调用时抛出异常)
  3. 在struct中删除[MarshalAs(UnmanagedType.LPStr)] for string(这将在关闭大括号后的第二个方法后抛出异常)
  4. 下面的代码用于C#端和我正在使用的C头。

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

3 个答案:

答案 0 :(得分:3)

    HelperAttributes attributes = new HelperAttributes();

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

  • C代码不能存储传递的指针,它必须复制struct。它可能不会这样做。你现在有一个“悬垂的指针”,一个非常臭名昭着的C bug。稍后取消引用指针会产生任意垃圾。

  • 您的代码创建并分配给struct的saveCallback和exitCallback成员的委托对象无法生存。垃圾收集器无法发现C代码要求它们保持活动状态。因此,当C代码进行回调时,下一个垃圾收集会破坏对象Big Kaboom。另请注意,必须使用[UnmanagedFunctionPointer]声明它们,如果它们实际接受参数,则使它们成为Cdecl。

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

答案 1 :(得分:2)

这些函数在非托管端都是void,在托管端以int返回类型声明。必须纠正这种不匹配。

您的回调函数指针使用cdecl调用约定。您从托管代码传递的代理使用stdcall调用约定。

通过标记代表来解决这个问题:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
public delegate void SaveCallBack();

[UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
public delegate void ExitCallBack();

Initialise可以更简单地声明为:

public static extern void Initialise(ref HelperAttributes attributes);

除此之外,我们必须猜测我们看不到的代码。例如,传递Initialise复制结构,还是记住结构的地址。

它是后者,那是一个致命的问题。解决方案是修复非托管代码,使其不记住地址,而是复制内容。即使它复制结构,它是做深度复制,还是将指针复制到字符数组?要了解这一点,需要查看问题中没有的代码。

即使函数确实正确地复制了struct,你也需要让代表保持活着,正如Hans所解释的那样。

答案 2 :(得分:0)

在您的C dll中,您需要使用dllexport代替dllimport

#define HELPERDLL_API __declspec(dllexport)

在C#代码导入函数中

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]