C# 调用C代码时pinvoke给出AccessViolationException

C# 调用C代码时pinvoke给出AccessViolationException,c#,c,pinvoke,C#,C,Pinvoke,我的一位同事用C语言制作了一个图书馆,我想从C#开始称之为图书馆。我认为这几乎是对的,但是当我调用第二个方法/函数时,它会得到一个AccessViolationException。我试过几种不同的方法: 类而不是结构 将[Marshallas(UnmanagedType.FunctionPtr)]添加到回调/委托(这实际上会在第一次调用而不是第二次调用时引发异常) 正在删除struct中字符串的[Marshallas(UnmanagedType.LPStr)](这将在右大括号上的第二个方法之后引

我的一位同事用C语言制作了一个图书馆,我想从C#开始称之为图书馆。我认为这几乎是对的,但是当我调用第二个方法/函数时,它会得到一个AccessViolationException。我试过几种不同的方法:

  • 类而不是结构
  • 将[Marshallas(UnmanagedType.FunctionPtr)]添加到回调/委托(这实际上会在第一次调用而不是第二次调用时引发异常)
  • 正在删除struct中字符串的[Marshallas(UnmanagedType.LPStr)](这将在右大括号上的第二个方法之后引发异常)
  • 下面是我正在使用的C端和C头的代码

    C:

    C#:


    在C dll中,您需要使用
    dllexport
    而不是
    dllimport

    #define HELPERDLL_API __declspec(dllexport)
    
    在C#代码导入函数中

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
    

    在C dll中,您需要使用
    dllexport
    而不是
    dllimport

    #define HELPERDLL_API __declspec(dllexport)
    
    在C#代码导入函数中

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
    

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

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

    通过如下方式标记学员来处理此问题:

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
    public delegate void SaveCallBack();
    
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
    public delegate void ExitCallBack();
    
    初始化
    可以更简单地声明为:

    public static extern void Initialise(ref HelperAttributes attributes);
    

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

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


    而且,正如Hans所解释的,即使函数正确地复制了结构,您也需要保持委托的活动性

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

        HelperAttributes attributes = new HelperAttributes();
    
    回调函数指针使用
    cdecl
    调用约定。从托管代码传递的代理使用
    stdcall
    调用约定

    通过如下方式标记学员来处理此问题:

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
    public delegate void SaveCallBack();
    
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
    public delegate void ExitCallBack();
    
    初始化
    可以更简单地声明为:

    public static extern void Initialise(ref HelperAttributes attributes);
    

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

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

    而且,正如Hans所解释的,即使函数正确地复制了结构,您也需要保持委托的活动性

        HelperAttributes attributes = new HelperAttributes();
    
    这非常非常麻烦。您在这段代码中有明确的内存管理问题。这里分配的结构的生命周期非常有限。它仅在Click事件处理程序方法的持续时间内有效,最多为纳秒。这不仅仅是一种方式:

    • C代码不能存储传递的指针,它必须复制结构。它可能不会这样做。现在您有了一个“悬空指针”,一个非常臭名昭著的C bug。随后,取消对指针的引用会产生任意垃圾

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

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

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

    • C代码不能存储传递的指针,它必须复制结构。它可能不会这样做。现在您有了一个“悬空指针”,一个非常臭名昭著的C bug。随后,取消对指针的引用会产生任意垃圾

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

    解决这些问题的方法不止一种。到目前为止,最简单的方法是将变量移到方法之外,并将其声明为静态。这使它对t有效