Что такое тип (fnptr)* и как его создать?
Следующий код IL создает экземпляр типа с именем (fnptr)*
(токен 0x2000000 - неверный, модуль mscorlib.dll).
ldtoken method void* ()*
call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
Какова цель этого типа? Можно ли создать этот экземпляр типа в C# без написания кода IL, может быть, с отражением? Module.ResolveType для токена создает исключение ArgumentOutOfRangeException.
Редактировать:
Это понятно (fnptr)
тип - это внутреннее представление типа CLR типа указателя метода IL, хотя при удалении последнего *
просто возвращается IntPtr
,
Редактировать № 2:
(fnptr)
происходит от функции, которую можно увидеть в SSCLI typestring.cpp:
// ...or function pointer else if (ty.IsFnPtrType()) { // Don't attempt to format this currently, it may trigger GC due to fixups. tnb.AddName(L"(fnptr)"); }
Почему базовый fnptr возвращает IntPtr, можно увидеть в typehandle.cpp:
OBJECTREF TypeHandle::GetManagedClassObject() const {
[...]
switch(GetInternalCorElementType()) { case ELEMENT_TYPE_ARRAY: case ELEMENT_TYPE_SZARRAY: case ELEMENT_TYPE_BYREF: case ELEMENT_TYPE_PTR: return ((ParamTypeDesc*)AsTypeDesc())->GetManagedClassObject(); case ELEMENT_TYPE_VAR: case ELEMENT_TYPE_MVAR: return ((TypeVarTypeDesc*)AsTypeDesc())->GetManagedClassObject(); // for this release a function pointer is mapped into an IntPtr. This result in a loss of information. Fix next release case ELEMENT_TYPE_FNPTR: return TheIntPtrClass()->GetManagedClassObject(); default: _ASSERTE(!"Bad Element Type"); return NULL; } } }
Похоже, они забыли это исправить.
4 ответа
Можно загрузить подпись указателя на указатель функции:
public static unsafe Type GetTypeFromFieldSignature(byte[] signature, Type declaringType = null)
{
declaringType = declaringType ?? typeof(object);
Type sigtype = typeof(Type).Module.GetType("System.Signature");
Type rtype = typeof(Type).Module.GetType("System.RuntimeType");
var ctor = sigtype.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new[]{typeof(void*), typeof(int), rtype}, null);
fixed(byte* ptr = signature)
{
object sigobj = ctor.Invoke(new object[]{(IntPtr)ptr, signature.Length, declaringType});
return (Type)sigtype.InvokeMember("FieldType", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetProperty, null, sigobj, null);
}
}
var fnptrPtr = GetTypeFromFieldSignature(new byte[]{6, 15, 27, 0, 0, 1});
6 - поле, 15 - указатель, 27 - указатель функции, а 0, 0, 1 - сигнатура метода без возврата или параметров.
Я понятия не имею, о чем вы спрашиваете и почему вы думаете, что что-то не так. (fnptr)* - имя типа указателя на указатель неуправляемой функции. То, что он получает специальное лечение в CLR, действительно странно, я подозреваю, что это археологический артефакт. Встречаясь со временем до.NET и до изобретения делегатов. CLR начал свою жизнь как "универсальная среда выполнения" в Project 42, неудачном проекте до.NET.
Может быть, какой-нибудь код C++/CLI, демонстрирующий, как его сгенерировать, полезен, покажет, как его создать:
#include "stdafx.h"
using namespace System;
typedef void (*functionPointer)(int);
ref class Example {
public:
functionPointer* fp;
};
int main(array<System::String ^> ^args)
{
auto field = Example::typeid->GetField("fp");
auto name = field->FieldType->FullName;
Console::WriteLine(name);
return 0;
}
Выход: (fnptr)*
Не уверен, где вы видите объявление FNPTR.
Для этого кода:
.assembly extern mscorlib {}
.assembly Test
{
.ver 1:0:1:0
}
.module test.exe
.method static void main() cil managed
{
.maxstack 1
.entrypoint
ldtoken method void* ()*
call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
ldtoken method void* ()
call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
ret
}
ILASM (4.5.22.0) выдает следующее:
.method privatescope static void main$PST06000001() cil managed
{
.entrypoint
// Code size 21 (0x15)
.maxstack 1
IL_0000: ldtoken method void *()*
IL_0005: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_000a: ldtoken method void *()
IL_000f: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_0014: ret
} // end of method 'Global Functions'::main
Обновление № 1:
Возможно, я здесь плотный, но я не вижу, как FNPTR генерируется из этого кода:
typeof(StringBuilder).ToString();
IL выглядит так:
IL_0000: nop
IL_0001: ldtoken [mscorlib]System.Text.StringBuilder
IL_0006: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_000b: callvirt instance string [mscorlib]System.Object::ToString()
IL_0010: pop
IL_0011: ret
Type.ToString()
вызов callvirt
операция, поскольку ToString() является виртуальным методом.
Виртуальные функции обычно проявляются в виде структур указателей на функции, которые, я полагаю, приведут к генерации FNPTR. Если вы опустите *
в ()*
в результате чего ()
Теперь вы описываете функцию, а не указатель на функцию.
Какую версию.NET вы используете, когда видите FNPTR? Что вы используете для извлечения IL?
Отвечая в контексте C# 9 ‒ указатели на функции теперь можно представлять и использовать напрямую, черезdelegate*
синтаксис. Однако среда выполнения (.NET 7) по-прежнему имеет те же ограничения, что и 7 лет назад:
Console.WriteLine(typeof(delegate*<void>)); // IntPtr
Console.WriteLine(typeof(delegate*<void>*)); // (fnptr)*