Ich arbeite an einer ILGenerator
Erweiterung, mit deren Hilfe IL-Fragmente ausgegeben werden können Expression
. Alles war in Ordnung, bis ich am Integer-Konvertierungsteil arbeitete. Es gibt etwas, das mir wirklich nicht intuitiv ist, wie:
- Verwenden Sie
conv.i8
zum KonvertierenInt32
inUInt64
- Verwenden Sie
conv.u8
zum KonvertierenUInt32
inInt64
Sie sind alle, weil der Evaluierungsstapel die Ganzzahlsignatur nicht verfolgt. Ich verstehe den Grund voll und ganz, es ist nur ein bisschen schwierig damit umzugehen.
Jetzt möchte ich die Konvertierung mit einbeziehen IntPtr
. Es muss schwieriger sein, da seine Länge variabel ist. Ich habe mich entschlossen zu sehen, wie der C # -Compiler es implementiert.
Konzentrieren Sie sich nun auf das Besondere IntPtr
an der Int64
Konvertierung. Anscheinend sollte das gewünschte Verhalten sein: No-Op auf 64-Bit-Systemen oder Vorzeichenerweiterung auf 32-Bit-Systemen.
Da in C # das native int
von der IntPtr
Struktur umbrochen wird , muss ich mir den Hauptteil seiner Int64 op_Explicit(IntPtr)
Methode ansehen . Folgendes wird von dnSpy aus .NET Core 3.1.1 zerlegt:
.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
}
Es ist komisch, dass conv.u8
hier erscheint! Auf 32-Bit-Systemen wird eine Nullverlängerung durchgeführt. Ich habe das mit folgendem Code bestätigt:
delegate long ConvPtrToInt64(void* ptr);
var f = ILAsm<ConvPtrToInt64>(
Ldarg, 0,
Conv_U8,
Ret
);
Console.WriteLine(f((void*)(-1))); // print 4294967295 on x86
Wenn Sie sich jedoch die x86-Anweisungen der folgenden C # -Methode ansehen:
static long Convert(IntPtr intp) => (long)intp;
;from SharpLab
C.Convert(IntPtr)
L0000: mov eax, ecx
L0002: cdq
L0003: ret
Es stellt sich heraus, dass das, was wirklich passiert, eine Zeichenverlängerung ist!
Mir ist aufgefallen, dass Int64 op_Explicit(IntPtr)
das ein Intrinsic
Attribut hat. Ist es der Fall, dass der Methodenkörper von der Laufzeit-JIT vollständig ignoriert und durch eine interne Implementierung ersetzt wird?
ENDGÜLTIGE Frage: Muss ich mich auf die Konvertierungsmethoden von beziehen IntPtr
, um meine Konvertierungen zu implementieren?
Anhang Meine ILAsm
Implementierung:
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);
}
quelle
Int64 op_Explicit(IntPtr)
als im x64-Modus. Wie wird das erreicht? Ich habe den Dateipfad untersucht, aus dem dieSystem.Private.CoreLib
Assembly geladen wird (vonAssembly.Location
), aber sie sind zwischen x86 und x64 identisch.Antworten:
Ich habe einen Fehler gemacht.
Int64 op_Explicit(IntPtr)
hat zwei Versionen. Die 64-Bit-Version befindet sich unter "C: \ Programme \ dotnet ..." und implementiert:Die 32-Bit-Version befindet sich unter "C: \ Programme (x86) \ dotnet ..." und hat folgende Implementierung:
Puzzle gelöst!
Dennoch denke ich, dass es möglich ist, eine identische Implementierung sowohl im 32-Bit- als auch im 64-Bit-Build zu verwenden. Man
conv.i8
wird die Arbeit hier machen.In der Tat könnte ich meine Aufgabe,
IntPtr
Konvertierungen zu senden, vereinfachen , da zur Laufzeit die Länge von 'IntPtr' bekannt ist (meines Wissens entweder 32 oder 64) und die meisten ausgegebenen Methoden nicht gespeichert und wiederverwendet werden. Aber ich möchte immer noch eine laufzeitunabhängige Lösung, und ich glaube, ich habe bereits eine gefunden.quelle