将IntPtr转换为Int64:conv.u8或conv.i8?

问题描述 投票:5回答:1

我正在研究ILGenerator扩展名,以帮助使用Expression发出IL片段。一切都很好,直到我从事整数转换部分的工作。有些东西对我来说确实是违反直觉的,例如:

  • 使用conv.i8Int32转换为UInt64
  • 使用conv.u8UInt32转换为Int64

都是因为评估堆栈无法跟踪整数有符号性。我完全理解其原因,处理起来有些棘手。

现在,我想支持涉及IntPtr的转换。由于它的长度是可变的,因此必须更加棘手。我决定看一下C#编译器如何实现它。

[现在专注于特定的IntPtrInt64转换。显然,所需的行为应该是:在64位系统上为无操作,或在32位系统上为符号扩展。

由于在C#中native intIntPtr结构包装,所以我必须查看其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);
}
c# .net cil reflection.emit
1个回答
1
投票

我犯了一个错误。 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),并且大多数发出的方法都不会保存和重用。但是我仍然希望有一个独立于运行时的解决方案,并且我想我已经找到了。

© www.soinside.com 2019 - 2024. All rights reserved.