0

I am trying to use a subclass of System.Runtime.InteropServices.SafeHandle to manage pointers to Win32 GUI objects (namely, HWND and HMENU). I am then using these handles to implement a Win32-based GUI library in C#. In this process, I create a menu and assign it to a window. However, suppose that I do not retain the SafeHandle containing HMENU, so that it is garbage collected before the window that owns it is. I cannot simply destroy all HMENU pointers when their SafeHandles go away, because then something like this happens:

  1. I create an HMENU and assign it into a subclass of SafeHandle that calls DestroyMenu() automatically.
  2. I create a top-level HWND, and assign it the HMENU using the SetMenu() Win32 API call.
  3. Because the HWND now retains the HMENU, it should not be destroyed (through DestroyMenu()) until the HWND that owns it is destroyed, at the earliest.
  4. However, because I wrapped the HMENU in a SafeHandle, the GC would soon deallocate the SafeHandle, and thus destroy the HMENU, while it is still referenced by the HWND.
  5. My program then crashes due to the use of an explicitly deleted handle.

Is there anything I can do to ensure that the HMENU stays around as long as it is needed, but is still destroyed once the owning HWND is GCed? I looked at this question, which would be nearly ideal, except it talks about using a native library that has reference counting built-in. Win32 does not — a handle either exists, or is destroyed. I considered adding my own reference-counting using a class like SafeHandle, but was stymied due to the fact that not all of the parts of the program (namely, Win32) referencing the HMENU have access to my reference-counting class. Is this a concern for my use-case? Are there any other ways I can/should implement such an API wrapper?

Community
  • 1
  • 1
wjk
  • 1,219
  • 11
  • 27

1 Answers1

0

The short answer is no, there's no way to make Win32 API hold a reference to a .net object until an HMENU is no longer needed.

What you can do is wrap HWND with your own class and on your SetMenu hold a reference inside your object - but this is not going to work because:

  1. Destroying GDI objects from a finalizer isn't allowed because the finalizer is called from a dedicated thread in the GC and you are only allowed to touch a GDI object from the same thread that created it (it will sort-of work most of the time but you are using the API in an unsupported way)

  2. Releasing an HWND from a finalizer (even if it was allowed) is going to make windows disappear from the screen at totally unpredictable times

This works at reference counted native frameworks because, unlike GC, releasing a reference is 100% predictable and controllable.

(basically, WinForms is designed the way it is for a reason)

Nir
  • 29,306
  • 10
  • 67
  • 103