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

4

12

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.

Ism answered 28/11, 2014 at 20:28 Comment(2)
Interesting. How did you come across this? From C++/CLI generated code?Andrej
@Andrej Yeah, I just had an idea to look at C++/CLI assembly in ildasm and saw *()* there. This attracted my attention so I've created the above code.Ism
I
0

It is possible to load the signature of a pointer to a function pointer:

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 is field, 15 is pointer, 27 is function pointer and 0, 0, 1 is a method signature without return or parameters.

Ism answered 2/4, 2015 at 21:43 Comment(0)
M
5

I have no real idea what you are asking and why you think there is anything wrong. (fnptr)* is the type name of a pointer to an unmanaged function pointer. That it gets special treatment in the CLR is indeed odd, I suspect it is an archeological artifact. Dating back to a time before .NET and before delegates were invented. The CLR started life as the "universal runtime" in Project 42, a failed project before .NET.

Maybe some C++/CLI code to demonstrate how to generate one is useful, shows you how to create it:

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

Output: (fnptr)*

Maggiemaggio answered 28/11, 2014 at 20:28 Comment(3)
Thanks, it is possible in C++/CLI, but can it be done in pure C#?Ism
No, you can't declare unmanaged function pointers in C#, it only supports delegates. Very hard to guess why you'd ask this btw, I have absolutely no clue what your intentions might be.Maggiemaggio
I mean I just want to obtain the Type instance. And the reason? Curiosity. ☺Ism
P
0

Not sure where you're seeing the FNPTR being declared.

For this code:

.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) outputs the following:

.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

Update #1:

Perhaps I am being dense here, but I am not seeing FNPTR being generated from this code:

typeof(StringBuilder).ToString();

The IL looks like this:

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

The Type.ToString() call is a callvirt operation since ToString() is a virtual method.

Virtual functions are usually manifested as structs of function pointers which would, I guess, result in a FNPTR being emitted. If you omit the * in ()* resulting in (), you're now describing a function, not a function pointer.

What version of .NET are you using when you see the FNPTR? What are you using to extract the IL?

Prussiate answered 17/12, 2014 at 21:22 Comment(4)
The CLR outputs (fnptr)* from Type.ToString.Ism
Again, if you compile the code I posted in my question, it returns a Type, which, being called ToString on, returns (fnptr)*.Ism
I did compile your code - see my 1st sample and the resulting output. Can you post a repro'?Prussiate
And you see my first and second answer. And I wonder where you've found StringBuilder.Ism
I
0

It is possible to load the signature of a pointer to a function pointer:

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 is field, 15 is pointer, 27 is function pointer and 0, 0, 1 is a method signature without return or parameters.

Ism answered 2/4, 2015 at 21:43 Comment(0)
I
0

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)*
Ism answered 1/12, 2022 at 13:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.