c# - 将 IntPtr 转换为 Int64:conv.u8 还是 conv.i8?
问题描述
我正在开发一个ILGenerator
扩展来帮助使用Expression
. 一切都很好,直到我处理整数转换部分。有一些对我来说真的违反直觉的东西,比如:
- 用于
conv.i8
转换Int32
为UInt64
- 用于
conv.u8
转换UInt32
为Int64
它们都是因为评估堆栈不跟踪整数符号。我完全理解原因,只是处理起来有点棘手。
现在我想支持涉及的转换IntPtr
。它必须更棘手,因为它的长度是可变的。我决定看看 C# 编译器是如何实现它的。
现在专注于特定IntPtr
的Int64
转换。显然,期望的行为应该是:64 位系统上的无操作,或 32 位系统上的符号扩展。
由于在 C# 中native int
是由结构包装的IntPtr
,所以我必须查看其Int64 op_Explicit(IntPtr)
方法的主体。以下是 dnSpy 从 .NET core 3.1.1 反汇编的:
.method public hidebysig specialname static
int64 op_Explicit (
native int 'value'
) cil managed
{
.custom instance void System.Runtime.CompilerServices.IntrinsicAttribute::.ctor() = (
01 00 00 00
)
.custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor() = (
01 00 00 00
)
.maxstack 8
IL_0000: ldarga.s 'value'
IL_0002: ldfld void* System.IntPtr::_value
IL_0007: conv.u8
IL_0008: ret
}
出现在这里很奇怪conv.u8
!它将在 32 位系统上执行零扩展。我用以下代码确认了这一点:
delegate long ConvPtrToInt64(void* ptr);
var f = ILAsm<ConvPtrToInt64>(
Ldarg, 0,
Conv_U8,
Ret
);
Console.WriteLine(f((void*)(-1))); // print 4294967295 on x86
但是,在查看以下 C# 方法的 x86 指令时:
static long Convert(IntPtr intp) => (long)intp;
;from SharpLab
C.Convert(IntPtr)
L0000: mov eax, ecx
L0002: cdq
L0003: ret
事实证明,真正发生的是符号扩展!
我注意到它Int64 op_Explicit(IntPtr)
有一个Intrinsic
属性。是不是方法体被运行时 JIT 完全忽略,被一些内部实现替代了?
最后一个问题:我是否必须参考转换方法IntPtr
来实现我的转换?
附录我的ILAsm
实现:
static T ILAsm<T>(params object[] insts) where T : Delegate =>
ILAsm<T>(Array.Empty<(Type, string)>(), insts);
static T ILAsm<T>((Type type, string name)[] locals, params object[] insts) where T : Delegate
{
var delegateType = typeof(T);
var mi = delegateType.GetMethod("Invoke");
Type[] paramTypes = mi.GetParameters().Select(p => p.ParameterType).ToArray();
Type returnType = mi.ReturnType;
var dm = new DynamicMethod("", returnType, paramTypes);
var ilg = dm.GetILGenerator();
var localDict = locals.Select(tup => (name: tup.name, local: ilg.DeclareLocal(tup.type)))
.ToDictionary(tup => tup.name, tup => tup.local);
var labelDict = new Dictionary<string, Label>();
Label GetLabel(string name)
{
if (!labelDict.TryGetValue(name, out var label))
{
label = ilg.DefineLabel();
labelDict.Add(name, label);
}
return label;
}
for (int i = 0; i < insts.Length; ++i)
{
if (insts[i] is OpCode op)
{
if (op.OperandType == InlineNone)
{
ilg.Emit(op);
continue;
}
var operand = insts[++i];
if (op.OperandType == InlineBrTarget || op.OperandType == ShortInlineBrTarget)
ilg.Emit(op, GetLabel((string)operand));
else if (operand is string && (op.OperandType == InlineVar || op.OperandType == ShortInlineVar))
ilg.Emit(op, localDict[(string)operand]);
else
ilg.Emit(op, (dynamic)operand);
}
else if (insts[i] is string labelName)
ilg.MarkLabel(GetLabel(labelName));
else
throw new ArgumentException();
}
return (T)dm.CreateDelegate(delegateType);
}
解决方案
我犯了一个错误。Int64 op_Explicit(IntPtr)
有两个版本。64位版本位于“C:\Program Files\dotnet...”,其实现为:
.method public hidebysig specialname static
int64 op_Explicit (
native int 'value'
) cil managed
{
.maxstack 8
IL_0000: ldarga.s 'value'
IL_0002: ldfld void* System.IntPtr::_value
IL_0007: conv.u8
IL_0008: ret
}
32位版本位于“C:\Program Files (x86)\dotnet...”,其实现为:
.method public hidebysig specialname static
int64 op_Explicit (
native int 'value'
) cil managed
{
.maxstack 8
IL_0000: ldarga.s 'value'
IL_0002: ldfld void* System.IntPtr::_value
IL_0007: conv.i4
IL_0008: conv.i8
IL_0009: ret
}
谜题解决了!
不过,我认为可以在 32 位和 64 位版本中使用相同的实现。一个人conv.i8
将在这里完成工作。
事实上,我可以简化发出IntPtr
转换的任务,因为在运行时,'IntPtr' 的长度是已知的(据我所知是 32 或 64),并且大多数发出的方法不会被保存和重用。但我仍然想要一个独立于运行时的解决方案,而且我想我已经找到了。
推荐阅读
- javascript - Find all the same numbers in the array
- powershell - Why can't I use the same Get-Date variable twice without getting incorrect time
- c++ - error: static assertion failed: std::thread arguments must be invocable after conversion to rvalues
- angular - 离子图标没有出现
- r - How do I use safely with coxph and subset or weights?
- python - How can I keep the rows of a pandas data frame that match a particular condition using value_counts() on multiple columns
- javascript - 使用不同的参数加载相同的页面,相同的功能不起作用
- r - R Dygraph,如何显示来自其他时间序列的值
- android-studio - Error initializing ADB: Debug Bridge not found
- java - MyBatis Java - 查询不返回数据?