不知怎的,我最近发布的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);
}
现在,我有以下内容:
System.AccessViolationException
(**)我对我原来的帖子进行了大量编辑,因为我觉得我找到了一种更简单的方式来显示问题,而我之前的文字会让读者感到困惑。希望不是问题,但如果以前的读者希望我可以重新发布原始文本。
答案 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