Что такое тип (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)*
Другие вопросы по тегам