F# Passing Nulls to Unmanaged Imported DLL

271 views Asked by At

In F# i'm using an external DLL (in this case SDL Graphics library) I'm importing the method I require as follows...

[<DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)>]
extern int SDL_QueryTexture(nativeint texture, uint32& format, int& access, int& w, int& h)

This works fine and I can successfully call the method using the following...

let result = SDLDefs.SDL_QueryTexture(textTexture, &format, &access, &w, &h)

The problem is that the native SDL methods accept null values for many pointer arguments. This is required in some scenarios (which function like overloaded methods). I can't find any way to call these methods from F# passing nulls.

For example, this fails with "does not have null as proper value"

let result = SDLDefs.SDL_QueryTexture(textTexture, &format, null, &w, &h)

I read about the attribute [AllowNullLiteral] but it seems like I can only apply it to types I define, and not pre-defined types which are used in my imported DLL.

Is there any way I can do this?

1

There are 1 answers

1
Fyodor Soikin On BEST ANSWER

If you want to specify nulls, you need to use "raw pointers", which are represented by types nativeint and nativeptr<T>.

[<DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)>]
extern int SDL_QueryTexture(nativeint texture, uint32& format, nativeint access, int& w, int& h)

// Call without null
let access = 42
let pAccess = NativePtr.stackalloc<int> 1
NativePtr.write pAccess access
SQL_QueryTexture( textTexture, &format, NativePtr.toNativeInt pAccess, &w, &h )
let returnedAccess = NativePtr.read pAccess

// Call with null
SQL_QueryTexture( textTexture, &format, null, &w, &h )

NOTE: be careful with stackalloc. Allocating memory on the stack is quite handy, because you don't need to explicitly release it, but pointers to it will become invalid once you exit the current function. So you can only pass such pointers to an external function if you're sure that the function won't store the pointer and try to use it later.

If you need to pass a pointer to real heap memory that's not going anywhere, you'll need Marshal.AllocHGlobal. But don't forget to release! (or else :-)

let access = 42
let pAccess = Marshal.AllocHGlobal( sizeof<int> ) 
NativePtr.write (NativePtr.ofNativeInt pAccess) access
SQL_QueryTexture( textTexture, &format, pAccess, &w, &h )
Marshal.FreeHGlobal( pAccess )