为什么编组对象不保留在本机代码中修改的值?
why doesn't a marshalled object retain the values modified inside the native code?
我确信有一个简单的答案,但经过一些研究我找不到它。我阅读并证明(除非我写的是错误的)在托管内存中分配的按引用传递的自动编组结构(或 类)由本机代码正确读取和写入,但是一旦代码执行 returns 到托管层,不保留本机代码内更改的值。举个例子:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class DirtyWordsCheckResult
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]
public string replace_string;
public EnumDirtyWordsType dirty_type;
public DirtyWordsCheckResult()
{
replace_string = new string(' ', 1024);
}
}
public enum EnumDirtyWordsType
{
kDirtyWordsTypeNormalAllowWords = 0, // normal allow words
kDirtyWordsTypeEvil = 1, // illegal,can not be displayed
kDirtyWordsTypeSensitive = 2, // legal, but contain sensitive
}
public override EResult DirtyWordsFilter(string words, bool replace_sensitive, out DirtyWordsCheckResult check_result)
{
check_result = new DirtyWordsCheckResult();
var result = Utils.DirtyWordsFilter(utils_, words, replace_sensitive, check_result);
return result;
}
本机函数 DirtyWordsFilter 确实正确获取分配的对象并且可以毫无问题地写入其中,但是不会保留值。
现在我知道我可以使用 Marshal.AllocHGlobal 来传递预分配的 IntPTR,因此我不是在寻找解决方案,我只是想知道为什么原来的机制不起作用。
您使用的结构不是 blittable。一个昂贵的词,意味着本机布局与托管布局不同。字符串导致它。不仅仅是因为 CharSet,.NET 字符串看起来一点也不像 char[]。这需要 pinvoke 编组器在将指针传递给本机代码可以使用的指针之前创建正确大小的副本。
但默认情况下它不会复制修改后的结构。您必须将 [Out]
放在参数上才能使其改变主意。我们看不到 [DllImport] 声明,但它应该类似于:
[DllImport(...)]
private static extern EResult DirtyWordsFilter(..., [Out] DirtyWordsCheckResult check_result);
pinvoke 编组器没有数据流的具体知识,即使您在声明中使用 ref
或 out
(通常出现在结构声明中)也是如此。它仅看到参数通过引用传递,并假定 [In]
为默认值。通常是正确且最佳的猜测,此处不适用。 Fwiw,请注意 Pack=1 几乎永远不会正确,它需要匹配本机代码中使用的包装,它的默认值也是 8。在这种特定情况下碰巧无关紧要。
我确信有一个简单的答案,但经过一些研究我找不到它。我阅读并证明(除非我写的是错误的)在托管内存中分配的按引用传递的自动编组结构(或 类)由本机代码正确读取和写入,但是一旦代码执行 returns 到托管层,不保留本机代码内更改的值。举个例子:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class DirtyWordsCheckResult
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]
public string replace_string;
public EnumDirtyWordsType dirty_type;
public DirtyWordsCheckResult()
{
replace_string = new string(' ', 1024);
}
}
public enum EnumDirtyWordsType
{
kDirtyWordsTypeNormalAllowWords = 0, // normal allow words
kDirtyWordsTypeEvil = 1, // illegal,can not be displayed
kDirtyWordsTypeSensitive = 2, // legal, but contain sensitive
}
public override EResult DirtyWordsFilter(string words, bool replace_sensitive, out DirtyWordsCheckResult check_result)
{
check_result = new DirtyWordsCheckResult();
var result = Utils.DirtyWordsFilter(utils_, words, replace_sensitive, check_result);
return result;
}
本机函数 DirtyWordsFilter 确实正确获取分配的对象并且可以毫无问题地写入其中,但是不会保留值。
现在我知道我可以使用 Marshal.AllocHGlobal 来传递预分配的 IntPTR,因此我不是在寻找解决方案,我只是想知道为什么原来的机制不起作用。
您使用的结构不是 blittable。一个昂贵的词,意味着本机布局与托管布局不同。字符串导致它。不仅仅是因为 CharSet,.NET 字符串看起来一点也不像 char[]。这需要 pinvoke 编组器在将指针传递给本机代码可以使用的指针之前创建正确大小的副本。
但默认情况下它不会复制修改后的结构。您必须将 [Out]
放在参数上才能使其改变主意。我们看不到 [DllImport] 声明,但它应该类似于:
[DllImport(...)]
private static extern EResult DirtyWordsFilter(..., [Out] DirtyWordsCheckResult check_result);
pinvoke 编组器没有数据流的具体知识,即使您在声明中使用 ref
或 out
(通常出现在结构声明中)也是如此。它仅看到参数通过引用传递,并假定 [In]
为默认值。通常是正确且最佳的猜测,此处不适用。 Fwiw,请注意 Pack=1 几乎永远不会正确,它需要匹配本机代码中使用的包装,它的默认值也是 8。在这种特定情况下碰巧无关紧要。