2

I'm trying to write a wrapper around a method with this signature:

[DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern unsafe Window* SDL_CreateWindow(string title, int x, int y, int w, int h, WindowFlags flags);

You will notice it returns a pointer to a Window which is a struct.

If I expose this method as-is, then any code that uses it will have to be marked as unsafe, which I would prefer to avoid. As such, I've written a public wrapper method that de-references it:

public static unsafe Window CreateWindow(string title, int x, int y, int w, int h, WindowFlags flags)
{
    return *SDL_CreateWindow(title, x, y, w, h, flags);
}

But I'm not 100% sure what this is doing now. The DLL is creating the Window object; when I de-reference it, is it copied back into a managed value-type object?


I was surprised to find I could pass references in place of pointers, as this worked perfectly:

[DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_DestroyWindow")]
public static extern void DestroyWindow(ref Window window);

But I can't do that for return types. Regardless though, is it safe to do this? I figured by de-referencing and then re-referencing the Window when I pass it back into DestroyWindow, the DLL wouldn't be able to find the correct object to destroy as the memory would have been shifted around; but seeing as that the code runs perfectly, I guess this is fine?

(Actually, after looking at how the struct is defined, I see that it has an "id" which I assume it uses as the handle, not the pointer itself)

Community
  • 1
  • 1
mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • Are you actually using the `Window` you make in `CreateWindow`, or just storing it? If you're just storing it, then why not just use `IntPtr`? That's what it's designed for. – Scott Mermelstein Jul 09 '13 at 02:04
  • @ScottMermelstein: That's a good question. It actually does have a bunch of read-only properties I might as well expose; furthermore I was going to move a bunch of the functions that operate on windows into the struct itself to give it a more object-oriented API. – mpen Jul 09 '13 at 02:23
  • The few times I've dealt with pointers from DllImport, that's what I did - make a managed object that stores the pointer as an IntPtr, and encapsulates it so no other code needs to see it. – Scott Mermelstein Jul 09 '13 at 02:24

1 Answers1

2

You can manually marshal the return object if you change SDL_CreateWindow to return an IntPtr

DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr SDL_CreateWindow(string title, int x, int y, int w, int h, WindowFlags flags);

public static Window CreateWindow(string title, int x, int y, int w, int h, WindowFlags flags) {
    IntPtr windowPtr = SDL_CreateWindow(title, x, y, w, h, flags);
    return (Window)Marhsal.PtrToStructure(windowPtr, typeof(Window));
}

However this is not safe. As you noted above this will create a managed copy of the Window structure, so even if you pass it back you will not be passing back the same object. This has some serious issues:

  1. The library may be using the pointer value as a token and will not recognize the copied Window object as the same window.
  2. It will cause a memory leak, because the pointer that SDL_CreateWindow returns will never be deallocated in a call to DestroyWindow.
  3. In fact the call to DestroyWindow will most likely crash or corrupt the heap, because it will try to deallocate memory that it did not allocate.

What this means is that you need to pass the same IntPtr that SDL_CreateWindow created to DestoryWindow

To be able to keep both the IntPtr and the Window class, create a WindowClass wrapper that stores both the Window struct so you can access its properties and store the IntPtr returned and use it in subsequence calls to the unmanaged DLL.

public class MyWindow {
    public Window Window { get; set; }
    public IntPtr WindowPtr { get; set; }
}

public static MyWindow CreateWindow(string title, int x, int y, int w, int h, WindowFlags flags) {
    IntPtr windowPtr = SDL_CreateWindow(title, x, y, w, h, flags);
    return new MyWindow {
         WindowPtr = windowPtr,
         Window = (Window)Marhsal.PtrToStructure(windowPtr, typeof(Window))
    }
}
shf301
  • 31,086
  • 2
  • 52
  • 86
  • What's the difference between marshalling the pointer to a struct vs de-referencing it with the `*` operator? – mpen Jul 09 '13 at 04:08