❔ `delegate*` wrapper throws `System.BadImageFormatException`
I wrote some wrapper for
delegate* unmanaged
to use it in F# since it doesn't support function pointers:
But when I try to call Call()
method:
I get System.BadImageFormatException: "Bad element type in SizeOf."
. What I am doing wrong?43 Replies
For those who don't know: roughly, F#
Unit
is same as C# void
and it is assumed that any value of type Unit
is null
You can pass the default value for the TArg type instead of null. For example:
This does not matter at all, because this is just an example and this struct is intended to be used in F#, where
Unit
is treated as void
and can be not passed at all:
Sorry, didn't read that part. Uhm you can make changes to the Call. I think this will work.
Is Unit a struct or a class?
class
Then why do you expect it to work with an unmanaged function pointer
Make it an empty struct
Unit
is a part of F#
's stdlib, I can't change itHave you tried my suggested code?
Adding
default
is just a visual change that doesn't do anything
(but yes, I tried)
And both 'TArg
and 'TRet
are either Unit
or some value type, this is checked at F# sitewhat unmanaged function would you call that takes an object reference as an argument? It doesn't make sense
Well a class won't work with an unmanaged function pointer
So it probably refuses to load il with one
Even if the branch isn't reachable
You'd have to make some hidden static method that'd be constrained to unmanaged and pass a dummy valuetype there for Unit
Also you'd probably want to make this a readonly struct and use a nuint or a void* instead of nint
@tannergooding which is more appropriate for storing a function pointer, nuint or a void*
void*
, but nuint
is also "fine" and at least matches on being unsigned
The general issues is that Unit
is an F# only and a managed only concept
Because it doesn't get niche filled
to void
and because it is not runtime specialized, it has hidden overhead/cost/incompatibilities with a non-F# ABI
It would work fine for a managed function pointer
that binds to an F# function
but not for a managed function pointer for a C# function
and not for an unmanaged function pointerAlthough
ldftn
is documented to load a signed native int
on the stackthe runtime spec doesn't have unsigned types on the stack
see "cls compliance"
(while
calli
is documented to take a function pointer
)everything on the runtime stack is a signed, and often normalized type
it is up to the subsequent instructions to determine how it is handled (signed or unsigned)
and how much of the value is used
The ecma says that there are signed types in cil?
I said unsigned and on the stack
The runtime is aware of signed vs unsigned types
the runtime stack only contains signed and normalized types
e.g. there is no
float
vs double
on the stack
only R
likewise there is no string
vs Attribute
vs ..., only O
and there is no byte/sbyte/short/ushort/uint, only int
?
again, the runtime stack
the instruction, such as
cgt
vs cgt.un
dictates whether the values are interpreted as signed
vs unsigned
intermediate types – only a subset of the built-in value types can be represented on the evaluation stack (§I.12.1). Values of other built-in value types are translated to/from their intermediate type when loaded onto/stored from the evaluation stack. The intermediate types are a subset of the verification types plus the floating-point type F (which is not a member of the above four subsets).
this is why, in the runtime HIL, you only get
TYP_INT
and why you see me regularly complaining about the lack of tracking for "small types"
and for unsigned types in general
yes, that's the part that covers that the evaluation stack is not signed vs unsigned
some implementation could do that, but none really do
its up to the instruction to determine how the evaluation stack entry is handled
particularly given that some instructions allow implicit upcastingI created
unmanaged
constrained version of Call
, CallImpl
and some reflection wrapper, Call
to still use it with Unit
https://paste.mod.gg/rfyuzaszrvnr/0
All delegate creation logic (in FuncPtr
static constructor) works fine, so unmanaged
constraints are passed...
...but it still throwsBlazeBin - rfyuzaszrvnr
A tool for sharing your source code with the world!
What is the definition of
FuncPtr
?
There is a fundamental incompatibility between Unit
and unmanaged function pointers, the runtime will completely block it due to being a managed type
You could create a managed wrapper which tracks an unmanaged fnptr
field and then dispatches to it that way
but in general it's not going to be niceJust look sources
I check if
TArg
and TRet
are Unit
or not, and choose which backing function to call
So delegate*
logic will take unmanaged
types (including struct PseudoUnit
)I think you should probably just wait for the F# support
even if you got this working "as is", you're allocating a delegate and losing all benefits of function pointers
There are potentially things you could do to workaround this, but in general its not going to work or run well
You'd be better off not supporting
Unit
here at all
and just providing 1-to-1 ABI equivalent fnptrs
and then requiring users to not pass down units themselvesI don't want to have some special benefits of function pointers, I just need to call runtime generated machine code
Use a delegate then
its cheaper and more efficient than trying to hack something together
Looks like it's the only good option right now
Function pointers are a lowlevel utility designed for a very specific use/purpose
they are not designed to work "well" with higher level language concepts, like
Unit
they are meant to provide a 1-to-1 with the actual ABI and do so in a non-allocating and non GC tracked fashionruntime generated machine codeI just have an address where it is located, so I have to use function pointers
Marshal.GetDelegateForFunctionPointer
-or- just don't support unit
and require the caller to handle the ABI difference
for the common case, you can write an F# inline
function to do thatDoesn't support generic delegates, so no
Action
and Func
That is, if you define this in C#:
You can then write an F#
inline fn
that does the relevant unit
handling to call Invoke()
you'd still need to have a different fnptr
type for "action" vs "func", but that's expected at this level
and is something that the actual F# support will likely be limited around as wellOh you're right
Thanks a lot
@TIHan might have a good suggestion on a way to simplify that
he's a lot more familiar with F# than I am, given he used to be on the team
You probably want to use
Marshal.GetDelegateForFunctionPointer
and create a delegate in F# -> https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/delegatesDelegates in F#
Learn how to work with delegates in F#.
I wanted to implement function pointer support in F# but it wasn't a priority so it never happened
Doesn't support generic delegates
Wait
.
The problem was even not in
Unit
class
Looks like delegate* unmanaged
can't handle any genericsSharpLab
C#/VB/F# compiler playground.
Managed types are not a problem at all
https://sharplab.io/#v2:C4LghgzgtgNAJiA1AHwAICYCMBYAUKgBgAIBlACzACcAHAGTACMA6AJQFcA7YASygFMA3HjypMANiKcIYAGZ8iAewYArPgGNgRAMJgANroAUqACwAqImDhxKfCBBiKV6zVQDmASiJ4A3niL+iVAB2IgMDOD5dPlcwYD5zTigwDjBXPjgAHiVVDQds5wA+d0trWwh3Azd3IVwAXyA
SharpLab
C#/VB/F# compiler playground.
Was this issue resolved? If so, run
/close
- otherwise I will mark this as stale and this post will be archived until there is new activity.