System.AccessViolationException,同时通过实现ICustomMarshaler从本机编组到托管

时间:2014-06-03 19:50:05

标签: c# c++ pinvoke

不知怎的,我最近发布的question的延续,我在尝试从原生到托管(通过使用System.AccessViolationException)编组时遇到了令人讨厌的ICustomMarshaler,我不明白。这是一个重现错误的示例代码(**)。 C ++方面:

typedef struct Nested1{
    int32_t         n1_a; // (4*)
    char*           n1_b;
    char*           n1_c;
} Nested1;
typedef struct Nested3{
    uint8_t         n3_a;
    int64_t         n3_b;
    wchar_t*        n3_c;
} Nested3;
typedef struct Nested2{
    int32_t         n2_a;
    Nested3         nest3;
    uint32_t        n2_b;
    uint32_t        n2_c;
} Nested2;
typedef struct TestStruct{
    Nested1     nest1; // (2*)
    Nested2     nest2;
} TestStruct;

void ReadTest(TestStruct& ts)
{
    ts.nest2.n2_c = 10; // (3*)
}

在C#端假冒TestStruct只是为了显示错误和ICustomMarshaler实现:

class TestStruct{};
[DllImport("MyIOlib.dll", CallingConvention = CallingConvention.Cdecl)]
extern static void ReadTest([Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(CustomMarshaler))]TestStruct ts);

class CustomMarshaler : ICustomMarshaler
{
    public static ICustomMarshaler GetInstance(string Cookie) { return new CustomMarshaler(); }

    public object MarshalNativeToManaged(IntPtr pNativeData)
    {
        return new TestStruct();
    }
    public void CleanUpNativeData(IntPtr pNativeData) 
    { 
    } // (1*) 
    public int GetNativeDataSize() { return 40; }
    public IntPtr MarshalManagedToNative(object ManagedObj)
    {
        TestStruct ts = (TestStruct)ManagedObj;
        IntPtr intPtr = Marshal.AllocHGlobal(GetNativeDataSize());

        return intPtr;
    }
 }

private void Form1_Load(object sender, EventArgs e)
{
     TestStruct ts = new TestStruct();
     ReadTest(ts);
}

现在,我有以下内容:

  • 正是这段代码我在行(1 *);
  • 之后得到System.AccessViolationException
  • 如果我注释掉行(2 *)行(3 *)我没有例外,一切正常;
  • 如果我评论其他几个结构域中的一个,例如line(3 *)我得到一个“托管调试助手''FatalExecutionEngineError'检测到[...]中的问题。这个错误可能是CLR中的错误,也可能是用户代码中不安全或不可验证的部分。此常见来源错误包括COM-interop或PInvoke的用户封送错误,这可能会破坏堆栈“

(**)我对我原来的帖子进行了大量编辑,因为我觉得我找到了一种更简单的方式来显示问题,而我之前的文字会让读者感到困惑。希望不是问题,但如果以前的读者希望我可以重新发布原始文本。

2 个答案:

答案 0 :(得分:1)

您需要In属性以及Out。如果没有In属性,则永远不会调用MarshalManagedToNative。并且没有分配非托管内存。因此违反了访问权限。

extern static void ReadTest(
    [In, Out, MarshalAs(UnmanagedType.CustomMarshaler, 
        MarshalTypeRef = typeof(CustomMarshaler))]
    TestStruct ts
);

严格地说,非托管代码应该使用指向struct的指针而不是引用参数。您可以从托管代码传递null,但那么作为C ++参考参数无效。

void ReadTest(TestStruct* ts)
{
    ts->nest2.n2_c = 10;
}

答案 1 :(得分:0)

大卫·哈弗南的回复(非常感谢BTW!)是正确的(请仔细阅读,以便快速回复),在这里我只添加一些注意事项,这可能对读者有所帮助(即使我'我不是这方面的专家,所以请带上一点盐)。当调用MarshalManagedToNative将结构传递给本机代码(仅[In]属性)时,代码类似于:

public IntPtr MarshalManagedToNative(object managedObj)
{
    IntPtr intPtr = MarshalUtils.AllocHGlobal(GetNativeDataSize());

    // ...
    // use Marshal.WriteXXX to write struct fields and, for arrays, 
    // Marshal.WriteIntPtr to write a pointer to unmanaged memory 
    // allocated with Marshal.AllocHGlobal(size)

    return intPtr;
}

现在,当需要从本机代码中读取结构时,David Haffernan说我们仍然需要分配内存(不能像Hans Passant建议的那样在本机端完成),因此需要添加内存这种情况是[In]属性(除[Out]之外)。

但是我只需要内存用于第一级结构而不是所有其他内存用于存储数组(我有几个内存消耗数组),它在本机端分配(例如使用malloc())并且应该稍后将被释放(通过调用使用free()来回收本机内存的方法),因此我使用{{{1}中的公共静态变量检测到MarshalManagedToNative将用于此目的。我知道我需要从本机代码中读取结构(我知道它不是一个优雅的解决方案,但它可以帮助我节省时间):

CustomMarshaler