Chuyển đổi IntPtr thành Int64: conv.u8 hoặc conv.i8?


8

Tôi đang làm việc trên một ILGeneratorphần mở rộng để giúp phát ra các đoạn IL bằng cách sử dụng Expression. Mọi thứ đều ổn, cho đến khi tôi làm việc trên phần chuyển đổi số nguyên. Có một cái gì đó thực sự phản trực giác với tôi, như:

  • Sử dụng conv.i8để chuyển đổi Int32sangUInt64
  • Sử dụng conv.u8để chuyển đổi UInt32sangInt64

Tất cả đều vì ngăn xếp đánh giá không theo dõi được chữ ký số nguyên. Tôi hoàn toàn hiểu lý do, nó chỉ là một chút khó khăn để xử lý.

Bây giờ tôi muốn hỗ trợ chuyển đổi liên quan IntPtr. Nó phải phức tạp hơn, vì chiều dài của nó là thay đổi. Tôi quyết định xem xét trình biên dịch C # thực hiện nó như thế nào.

Bây giờ tập trung vào cụ thể IntPtrđể Int64chuyển đổi. Rõ ràng hành vi mong muốn phải là: không hoạt động trên các hệ thống 64 bit hoặc mở rộng đăng nhập trên các hệ thống 32 bit.

Vì trong C # native intđược bao bọc bởi IntPtrstruct, tôi phải xem xét phần thân của Int64 op_Explicit(IntPtr)phương thức của nó . Phần sau được phân tách bởi dnSpy từ .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
}

Thật kỳ lạ khi conv.u8xuất hiện ở đây! Nó sẽ thực hiện mở rộng bằng không trên các hệ thống 32 bit. Tôi xác nhận rằng với đoạn mã sau:

delegate long ConvPtrToInt64(void* ptr);
var f = ILAsm<ConvPtrToInt64>(
    Ldarg, 0,
    Conv_U8,
    Ret
);
Console.WriteLine(f((void*)(-1)));  // print 4294967295 on x86

Tuy nhiên, khi xem hướng dẫn x86 của phương pháp C # sau:

static long Convert(IntPtr intp) => (long)intp;
;from SharpLab
C.Convert(IntPtr)
    L0000: mov eax, ecx
    L0002: cdq
    L0003: ret

Nó chỉ ra rằng những gì thực sự xảy ra là một dấu hiệu mở rộng!

Tôi nhận thấy rằng Int64 op_Explicit(IntPtr)có một Intrinsicthuộc tính. Có phải là trường hợp cơ thể phương thức bị bỏ qua hoàn toàn bởi JIT thời gian chạy và được thay thế bằng một số thực hiện nội bộ?

Câu hỏi cuối cùng: Tôi có phải tham khảo các phương pháp chuyển đổi IntPtrđể thực hiện chuyển đổi của mình không?

Phụ lục Tôi ILAsmthực hiện:

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);
}

Đó là một trường hợp góc khó khăn, không có giải pháp lý tưởng. Điều khiến bạn vấp ngã hầu hết là không thấy từ IL rằng có hai chuyển đổi riêng biệt . Truyền (int) được sử dụng trong nền tảng 32 bit không phù hợp với hương vị 64 bit.
Hans Passant

@HansPassant Bạn nói đúng. Trong chế độ x86, tôi nhận được mảng byte IL khác Int64 op_Explicit(IntPtr)với trong chế độ x64. Làm thế nào đạt được điều này? Tôi đã nghiên cứu đường dẫn tệp mà từ đó System.Private.CoreLiblắp ráp được tải (bởi Assembly.Location), nhưng chúng giống nhau giữa x86 và x64.
kevinjwz

Không cùng đường dẫn, tệp c: \ chương trình so với tệp chương trình c: \ (x86). Nhưng đó không phải là vấn đề, đó là một sự hốt hoảng nội tại rất khác nhau. Không dễ để thấy, bạn sẽ phải sử dụng trình gỡ lỗi không được quản lý.
Hans Passant

@HansPassant Một lần nữa bạn đúng. Tôi không biết rằng tùy chọn "Thích 32-bit" bị bỏ qua sau .Net Core 3.0 và có lẽ tôi đã nhầm lẫn về điều đó. Quả thực có tập tin lắp ráp khác nhau.
kevinjwz

Tôi sẽ tự viết một câu trả lời.
kevinjwz

Câu trả lời:


3

Tôi đã phạm một sai lầm. Int64 op_Explicit(IntPtr)có hai phiên bản. Phiên bản 64 bit nằm trong "C: \ Program Files \ dotnet ..." và cách thực hiện của nó là:

.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
}

Phiên bản 32 bit nằm trong "C: \ Program Files (x86) \ dotnet ..." và việc triển khai của nó là:

.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
}

Câu đố đã được giải!

Tuy nhiên, tôi nghĩ có thể sử dụng một triển khai giống hệt nhau trong cả bản dựng 32 bit và 64 bit. Một người conv.i8sẽ làm công việc ở đây.

Thật vậy, tôi có thể đơn giản hóa nhiệm vụ phát ra các IntPtrchuyển đổi của mình, vì trong thời gian chạy, độ dài của 'IntPtr' đã được biết, (theo 32 hoặc 64 theo hiểu biết của tôi) và hầu hết các phương thức được phát ra sẽ không được lưu và sử dụng lại. Nhưng tôi vẫn muốn một giải pháp độc lập với thời gian chạy và tôi nghĩ rằng tôi đã tìm thấy một giải pháp.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.