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:
- https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/delegates
- https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/generics/constraints
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.
byrefs,delegates, and P/Invoke there isn't an elegant solution. You will be better off just duplicating those few lines.