将 ref T 值固定为 void* 的 IL 方法(从并行代码处理 Span<T>)
Posted
技术标签:
【中文标题】将 ref T 值固定为 void* 的 IL 方法(从并行代码处理 Span<T>)【英文标题】:IL method to pin a ref T value as void* (to work on Span<T> from parallel code) 【发布时间】:2018-06-14 04:14:07 【问题描述】:在实际问题之前,一个小小的免责声明:
这是this one 的相关/后续问题,但因为这里我在谈论一个更普遍的问题(如何固定
ref T
变量以便能够对其执行指针操作),我我提出了一个可能的(可能是错误的)解决方案,我提出了一个单独的问题。
所以,问题是:给定一个 ref T
变量(假设它是数组的第一项),我如何固定它,以便 GC 在处理底层数组时不会引起问题不安全的指针?
我不确定这在 C# 中是否可行(但我希望我错了),但是查看 IL 代码以找到一种仅修复 ref int
变量的方法,我试图想出一个变体致力于泛型类型。这是我的想法:
假设我在“MyTestClass”类中有这个delegate
:
public unsafe delegate void UnsafeAction(void* p);
然后,在 IL:
.method public hidebysig static void
Foo<valuetype .ctor (class [netstandard]System.ValueType) T>(
!!0/*T*/& r,
class MyTestClass/UnsafeAction action
) cil managed
.maxstack 2
.locals init (
[0] void* p,
[1] !!0/*T*/& pinned V_1
)
// Load the ref T argument into the pinned variable (as if fixed were used)
IL_0000: nop
IL_0001: ldarg.0 // r
IL_0002: stloc.1 // V_1
// Cast the T* pointer to void*
IL_0003: ldloc.1 // V_1
IL_0004: conv.u
IL_0005: stloc.0 // p
// Invoke the action passing the p pointer
IL_0006: ldarg.1 // action
IL_0007: ldloc.0 // p
IL_0008: callvirt instance void MyTestClass/UnsafeAction::Invoke(void*)
IL_000d: nop
// Set the pinned variable V_1 to NULL
IL_000e: ldc.i4.0
IL_000f: conv.u
IL_0010: stloc.1 // V_1
IL_0011: ret
我们的想法是能够像这样使用这种方法:
public static void Test<T>(this Span<T> span, Func<T> provider)
void Func(void* p)
// Do stuff with p, possibly in parallel
// eg. Unsafe.Write(p, provider());
Foo(ref span.DangerousGetPinnableReference(), Func);
这样的东西会起作用吗?如果是这样,将这种用 IL 编写的方法包含到现有 .NET Standard 2.0 项目中的最佳方法是什么?
谢谢!
额外问题:我已经看到 CoreFX 存储库对具有 IL 类的项目使用“.ilproj”文件,但 VS 实际上并不支持它们。这是一些扩展还是他们使用自己的自定义脚本来支持该项目格式?我知道有一个 ILProj extension 可用,但它既不是官方的,也不兼容 VS2017。
编辑:我可能刚刚知道如何解决这个问题,考虑一下:
public static void Foo<T>(ref T value)
fixed (void* p = &Unsafe.As<T, byte>(ref value))
// Shouldn't the ref T be correctly fixed here, since
// the "dummy" byte ref had the same address?
【问题讨论】:
我已经看到 CoreFX 存储库使用“.ilproj”文件来处理具有 IL 类的项目,但 VS 实际上并不支持它们。那是一些扩展还是他们使用自己的自定义脚本来支持该项目格式?如果你用谷歌搜索ilproj
,前三个链接会帮助你......
@ta.speot.is 是的,我这样做并看到了这些结果,但这些扩展似乎不是官方的(即不是来自 MS),第一个的描述说的是那些项目不能从 C# 项目中引用,您必须构建它们并链接 dll 本身。我希望有一个更简单的解决方案。
【参考方案1】:
是的,将引用存储在固定本地就足够了。在方法执行过程中,引用的内存不会被移动,因此指针将保持有效。
您也可以从您的方法中删除nop
s(分析在Debug下编译的代码的结果),之后将变量设置为零也是不必要的,因为该方法在它之后退出。
至于如何在 C# 项目中分发 CIL 代码,我会使用 DynamicMethod,但我不知道 .NET Standard 中是否可用。如果没有,将 IL 编译为 .dll
或 .netmodule
并引用它或自动将其链接到主项目并不是什么大问题。
【讨论】:
谢谢!是的,我留下了那些nop
s,因为我不确定它们是用于对齐还是只是调试模式的剩余部分,很高兴听到我可以删除它们。不幸的是,DynamicMethod
在 .NET Standard 2.0 上不存在,我将尝试弄清楚如何从 .il 文件中正确获取 dll。另外,看看这个问题和那个代码,你认为这是正确的方法和唯一可能的解决方案,还是可能有另一种更简单的方法来实现这一点?
@Sergio0694 ilasm /dll
应该可以工作,如果你删除了.assembly
部分,它也会创建一个.netmodule
。我不经常使用Span<T>
,所以我不知道是否有更好的解决方案来解决您的根本问题。但是,您创建的方法与我在库中使用的方法相似 (here)。唯一的区别是我将转换为指针与固定分开。
感谢您的提醒,该仓库看起来很棒!我没有看到 NuGet 链接,所以如果您还没有看到,那么您绝对应该考虑在那里发布您的库!
谢谢!已经有人建议我在 NuGet 上发布它,我很乐意,但我想在此之前将库拆分成更小的连贯包。
这是个好主意。另外,我想我刚刚对如何使用现有 API 解决这个问题有了一个想法,你介意看看我的解决方案***.com/a/48105559/3813976 和这个问题底部的编辑,让我知道你怎么看?以上是关于将 ref T 值固定为 void* 的 IL 方法(从并行代码处理 Span<T>)的主要内容,如果未能解决你的问题,请参考以下文章
System.InvalidProgramException:移动第 3 方 .NET 组件时 IL 代码无效(Dundas Gauge for .NET)