Reputation: 843
I have created some F# bindings for GLFW. For this, there are several callbacks. Right now, all of my set callback wrappers are basically identical aside from the callback definition (which is a delegate type), a more idiomatic F# callback function, the arguments of these function types, and the actual binding that marshals the callback function to GLFW.
Here are two bindings and callback delegates:
module Windowing.GLFW.Native.Window
open System.Runtime.InteropServices
[<Struct>]
type GLFWwindow = struct end
type GLFWwindowsizefun = delegate of window: nativeptr<GLFWwindow> * width_height: (int * int) -> unit
type GLFWwindowfocusfun = delegate of window: nativeptr<GLFWwindow> * focused: int -> unit
[<DllImport(glfwDLLPath, CallingConvention = CallingConvention.Cdecl)>]
extern GLFWwindowsizefun glfwSetWindowSizeCallback(GLFWwindow* window, GLFWwindowsizefun callback)
/// Sets the focus callback for the specified window.
[<DllImport(glfwDLLPath, CallingConvention = CallingConvention.Cdecl)>]
extern GLFWwindowfocusfun glfwSetWindowFocusCallback(GLFWwindow* window, GLFWwindowfocusfun callback
Then I have high-level wrappers that are meant to hide the interop details:
module Windowing.GLFW.FSharp
open Windowing.GLFW.Native
open FSharp.NativeInterop
open System.Runtime.InteropServices
type GLFWWindow = GLFWWindow of nativeptr<Window.GLFWwindow>
let setWindowSizeCallback (callbackRef: outref<Window.GLFWwindowsizefun>)
(callback: GLFWWindow * int * int -> unit)
(GLFWWindow window) =
let callback = Window.GLFWwindowsizefun(fun window (width, height) -> callback(GLFWWindow window, width, height))
callbackRef <- callback
Window.glfwSetWindowSizeCallback(window, callback) |> ignore
let setWindowFocusCallback (callbackRef: outref<Window.GLFWwindowfocusfun>)
(callback: GLFWWindow * int -> unit)
(GLFWWindow window) =
let callback = Window.GLFWwindowfocusfun(fun window focused -> callback(GLFWWindow window, focused))
callbackRef <- callback
Window.glfwSetWindowFocusCallback(window, callback) |> ignore
As you can see, these wrapper functions are practically identical. To create a new one, I just copy one, change the function delegate types, the callback type definition, and the GLFW binding.
What I would like is to make a generic function that handles all of these mechanisms, and then to define the actual wrappers, I just pass in the relevant type values. However, I have been unable to get this to work with generic delegate constraints.
For example, I have tried this:
let setGenericCallback (callbackDef: 'GLFWcallback when 'GLFWcallback: delegate<nativeptr<Window.GLFWwindow> * 'Args, unit>)
(setCallbackFun: nativeptr<Window.GLFWwindow> * 'GLFWcallback -> 'GLFWcallback)
(callback: GLFWWindow * 'Args -> unit)
(callbackRef: outref<'GLFWcallback>)
(GLFWWindow window) =
let callback = callbackDef(fun window args -> callback(GLFWWindow window, args))
callbackRef <- callback
setCallbackFun(window, callback) |> ignore
But on the line let callback = callbackDef ...
, I get the error "This value is not a function and cannot be applied". If I try callbackDef.Invoke
, I get the error "Lookup on object of indeterminate type", even though it's supposed to be a delegate. What is also weird is that I am not able to statically constraint 'GLFWcallback
with ^GLFWcallback
. It appears that outref<type>
cannot take a statically constrainted type for type
.
The two resources I have been referring to are:
If I had such a generic function, then I could redefine things as:
let setWindowSizeCallback callbackRef callback window =
setGenericCallback Window.GLFWwindowposfun
Window.glfwSetWindowPosCallback
callback
callbackRef
window
So, I am stuck. The errors I am getting don't make sense to me or tell me what's really wrong. It doesn't seem like the delegate constraint is working properly or that I'm using it properly.
How do I get this working? How do I properly define a function like this that allows me to use generic delegates? Note that my delegates are not all that generic. They are all of the type delegate of nativeptr<GLFWwindow> * 'T -> unit
.
Upvotes: 3
Views: 171
Reputation: 916
After running into the same problem, further research eventually turned up this question and the following bit of spec:
5.2.8 Delegate Constraints An explicit delegate constraint has the following form:
typar : delegate<tupled-arg-type, return-type>
During constraint solving (§14.5), the
constraint type : delegate<tupled-arg-type, return-types>
is met iftype
is a delegate typeD
with declarationtype D = delegate of object * arg1 * ... * argN
andtupled-arg-type = arg1 * ... * argN
. That is, the delegate must match the CLI design pattern where thesender
object is the first argument to the event.Note: This constraint form exists primarily to allow the definition of certain F# library functions that are related to event programming. It is rarely used directly in F# programming.
The
delegate
constraint does not imply anything about subtypes. In particular, a ‘delegate’ constraint does not imply that the type is a subtype ofSystem.Delegate
.The
delegate
constraint applies only to delegate types that follow the usual form for CLI event handlers, where the first argument is a “sender” object. The reason is that the purpose of the constraint is to simplify the presentation of CLI event handlers to the F# programmer.
So basically, the delegate
constraint is only tangentially related to the actual problem of constraining types to be delegates, and you'd be better off defining an interface like
type IInvokable<'a, 'b> = interface
abstract member Invoke: 'a -> 'b
end
and then constraining to :> IInvokable
.
Upvotes: 0
Reputation: 243051
You may be able to cast the typed delegate into an untyped delegate and then invoke it dynamically. I'm also not sure what the performance implications are - I suspect it may be slower, but I have not tried this.
I was not able to run this, but the following type-checks (but I made some edits as I was confused by the difference between GLFWindow
and GLFwindow
):
let inline setGenericCallback
(callbackDef: 'GLFWcallback when 'GLFWcallback: delegate<nativeptr<GLFWwindow> * 'Args, unit>)
(callback: GLFWWindow * (int * int) -> unit) (GLFWWindow window) =
let callback =
(callbackDef :> System.Delegate).DynamicInvoke
[| fun window args -> callback(GLFWWindow window, args) |]
()
That said, I agree with what other commenters said - it is just easier to live with some code duplication. This makes especially sense in code that is basically manually auto-generated binding.
Upvotes: 1