I have the following C function from SDL:
/**
* Get a list of currently connected displays.
*
* \param count a pointer filled in with the number of displays returned, may
* be NULL.
* \returns a 0-terminated array of display instance IDs or NULL on failure;
* call SDL_GetError() for more information. This should be freed
* with SDL_free() when it is no longer needed.
*
* \since This function is available since SDL 3.1.3.
*/
extern SDL_DECLSPEC SDL_DisplayID * SDLCALL SDL_GetDisplays(int *count);
I'm trying to write a proper wrapper for this method in C#. The question is how to define the method signature correctly.
Here are the two approaches I've tried:
Option 1:
[LibraryImport(SDLLibrary, EntryPoint = "SDL_GetDisplays"), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial IntPtr GetDisplays(out IntPtr count);
This works. It's not very convenient to use count
as an IntPtr
, but it gets the job done.
Option 2:
[LibraryImport(SDLLibrary, EntryPoint = "SDL_GetDisplays"), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial IntPtr GetDisplays(out int count);
This also works. However, the original method allows count to be NULL
, which isn't possible with this signature. This could lead to undefined behavior. Despite that, it's more convenient to use.
My Question: Which of these options is preferable to use for wrapping this function in C#, considering both safety and usability? Or is there a better way to handle the nullable count parameter in the C# wrapper?
I tested this case in detail, and experimentally found out the following:
If you marshal out a parameter that.
null
without an explicit Nullable
wrapper that is not supported by the marshalizer (e.g. float
).float*
(a pointer to a memory region that may be empty)Thus, if the marshalizable function writes null
there, then in C# the value of such a variable will be 0
(since out will declare it as 0
when initializing the variable, and null
will simply not be written).
As a result, we will have out float = 0
without any error on the part of dotnet
, so, answering your own question, in this case you can disregard the compliance with the original documentation and use the more convenient variant with out float
instead of out IntPtr
, but keep in mind that in case of an error, in the place where null
should be, there will be 0
.
I hope I was able to explain it in a clear way, and that people who have the same question will understand me.
I would be glad if people who are more knowledgeable in the subject would complement me if I have made a mistake.