将 C++ 结构编组到 C# 类时出现 AccessViolationException

Posted

技术标签:

【中文标题】将 C++ 结构编组到 C# 类时出现 AccessViolationException【英文标题】:AccessViolationException when marshaling a C++ struct to C# class 【发布时间】:2012-04-30 15:20:55 【问题描述】:

我正在尝试使用 PInvoke 将 C++ 库 (libclang) 包装到 C# 包装器中。

一切都很闪亮,直到我尝试调用返回结构的 C++ 方法。 我做了所有事情by the book,但是当这个方法被调用时,我得到了 AccessViolationException。

现在,我读到这可能是因为对象的内存映像出现了问题。我检查并仔细检查了我是否已经把所有的 arrtibutes 和什么不是无处不在但异常不会消失。 (我已经查看这段代码几个小时了,所以我可能遗漏了一些东西,你们没有)。

C++ 部分(不是我的代码,但我确信它可以工作):

CXString clang_formatDiagnostic(CXDiagnostic Diagnostic, unsigned Options) 

/* Parse the hell out of a lot of data, an then make a string
   In the end, string gets wrapped in a custom struct: CXString.
 */

    return createCXString(Out.str(), true);


这里是 CXString:

typedef struct 
  void *data;
  unsigned private_flags;
 CXString;

所以我有我的 C# 类来表示包装的 C++ 原件:

public class Diagnostic 
    
        private IntPtr _nativeObject;
        internal IntPtr NativeObject  get  return _nativeObject;  

        [DllImport("libclang.dll", EntryPoint = "clang_formatDiagnostic")]
        [return: MarshalAs(UnmanagedType.LPStruct)]
        private static extern CXString FormatDiagnostic(IntPtr diag, uint options);

        public Diagnostic(IntPtr native)
        
            _nativeObject = native;
        

        public string GetString(DiagnosticDisplayOptions options = DiagnosticDisplayOptions.DisplaySourceLocation)
        
            var cxstr = FormatDiagnostic(_nativeObject, (uint)options); //<-- I get the exception here
            return cxstr.GetString();
        
    

我需要的函数也以 C 风格(全局)实现,因此,我可以在我的 C# 类中给人一种 OO 的印象,但实际上我存储了 C++ 对象的 IntPtr 表示 (_nativeObject)。所以我很确定,存储在_nativeObject 中的对象实际上是一个 CXDiagnostic(我得到了从同一个库的另一个函数返回的引用)。

我尝试与 FormatDiagnostic 方法一起使用的实际对象从另一个包装类 (TranslationUnit) 的构造函数中初始化:

public TranslationUnit(/*lots of params to init the unit*/)
    
        //... there are some int conversions and initialization failsafe codes here

        //this is a property of TranslationUnit
        Diagnostics = new List<Diagnostic>();

        //GetDiagnosticsCount is also PInvoke to count CXDiagnostic objects related to the TranslationUnit
        var dgCnt = (int)GetDiagnosticsCount(_nativeObject);
        for (int i = 0; i < dgCnt; i++)
        
            //GetDiagnostic is also PInvoke, gets the CXDiagnostic at the given index
            var diag_ptr = GetDiagnostic(_nativeObject, (uint)i);
            Diagnostics.Add(new Diagnostic(diag_ptr));
        
        //up until now, all the calls seem to work
        //I get the expected count of diagnostics and none of the other 
        //PInvoke calls throw exceptions. They use IntPtrs, but none of them
        //use structs.
    

因此,正如 MSDN 教程所建议的,我创建了一个 C# 类来将 CXString 结构编组为,它看起来像这样:

[StructLayout(LayoutKind.Sequential)]
public class CXString

    public IntPtr data;
    public uint private_flags;

当代码到达FormatDiagnostic 调用时,我得到了 AccessViolationException。 任何可能出错的提示?

编辑:

CXDiagnostic 是原始 C++ 代码中的指针类型:

typedef void *CXDiagnostic;

【问题讨论】:

(1) 方法类型为采用 CXDiagnostic;你正在传递一个 IntPtr。 CXDiagnostic 首先是指针类型吗? (2) 您在此处显示的代码无处初始化您正在传递的 IntPtr,因此它将为空。您的非托管代码是否取消引用 null? (1) 是的,它的 typedef 是这样的: typedef void *CXDiagnostic; (2) 它是从外部代码初始化的,它在调试器中有一个值,它不是 0。我将在一分钟内更新帖子,以便它也显示。 (3) 为什么将其标记为 LPStruct?正如本文所指出的,LPStruct 的唯一有效用法是在编组 GUID 时:blogs.msdn.com/b/adam_nathan/archive/2003/04/23/56635.aspx——撇开设计上这只适用于 GUID 的事实不谈,您不会将 pointer 返回到CXString,你返回的是一个CXString,那你为什么说它应该被编组为指针间接呢? 我正在阅读本教程:msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx,示例 3 在结构上使用它。 不能一味地复制教程代码并期望它能工作;你必须了解它的含义。在第三个示例中,结构在 C# 代码中按值传递,并由编组层编组为指针。如果这不是你想要发生的,那么不要那样做 【参考方案1】:

我认为编组到 LPStruct 在这里不合适,CXString 类需要是结构。

试试下面的代码:

public class Diagnostic 

    ...
    [DllImport("libclang.dll", EntryPoint = "clang_formatDiagnostic")]
    private static extern CXString FormatDiagnostic(IntPtr diag, uint options);
    ...


[StructLayout(LayoutKind.Sequential)]
public struct CXString

    public IntPtr data;
    public uint private_flags;

此外,您应该注意调用约定(默认为 StdCall,但例如纯 C 使用 Cdecl)和结构字节对齐。

【讨论】:

以上是关于将 C++ 结构编组到 C# 类时出现 AccessViolationException的主要内容,如果未能解决你的问题,请参考以下文章

将具有 int* 成员的 C++ 结构编组到 C#

将复杂结构编组到 C#

将结构数组从 C#(.NET Core) 传递到 C++(未管理)

将 C++ 类编组为结构

将包含 int 和 int[] 的结构从 C# 编组到 C++

创建要在 C# 中编组的 C++ Dll 的最佳实践 [关闭]