Search code examples
c#.netreflectionclrcil

What is the (fnptr)* type and how to create it?


The following IL code creates a Type instance named (fnptr)* (token 0x2000000 - invalid, module mscorlib.dll).

ldtoken method void* ()*
call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)

What's the purpose of this type? Is it possible to create this type instance in C# without writing any IL code, maybe with reflection? Module.ResolveType on the token throws ArgumentOutOfRangeException.

Edit:

It's clear the (fnptr) type is an internal CLR type representation of an IL method pointer type, though when removing the last *, it simply returns IntPtr.

Edit #2:

The (fnptr) comes from a function that can be seen in the 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)");
}

Why basic fnptr returns IntPtr can be seen in 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;
        }
    }
}

So it looks they've forgotten to fix it.


Solution

  • Answering in the context of C# 9 ‒ functions pointers are now possible to represent and consume directly, via the delegate* syntax. However, the runtime (.NET 7) still has the same limitations it had 7 years ago:

    Console.WriteLine(typeof(delegate*<void>)); // IntPtr
    Console.WriteLine(typeof(delegate*<void>*)); // (fnptr)*