2

I am in the process of converting my Win32 p/invoke code to use SafeHandle classes instead of the typical IntPtr handles.

While everything works pretty well in DllImport method signatures, I cannot for the life of me get them to work when marshaling Win32 structs (i.e. PROCESS_INFORMATION).

// This works without issue.
[StructLayout(LayoutKind.Sequential)]
internal struct Win32ProcessInformation
{
    public IntPtr ProcessHandle { get; set; }
    public IntPtr ThreadHandle { get; set; }
    public int ProcessId { get; set; }
    public int ThreadId { get; set; }
}

// This does not work!
[StructLayout(LayoutKind.Sequential)]
internal struct Win32ProcessInformation
{
    public ProcessSafeHandle ProcessHandle { get; set; }
    public ThreadSafeHandle ThreadHandle { get; set; }
    public int ProcessId { get; set; }
    public int ThreadId { get; set; }
}

The ProcessSafeHandle and ThreadSafeHandle classes work just fine with methods like ReadProcessMemory or WriteProcessMemory, but I cannot use them in Win32 structs like above.

Am I missing some kind of annotation magic?

Erik
  • 12,730
  • 5
  • 36
  • 42
  • I don't really know anything about .net but just looking at the `SafeHandle` class, its constructor takes two arguments (an `IntPtr` and a `bool` indicating ownership). I'm not sure how the marshaller could know whether the `bool` should be true or false. – Jonathan Potter Jan 09 '16 at 05:40
  • Good point. I have tried making the constructor in my `SafeHandle` subclass take only the `IntPtr` (assuming `true` for the bool always). Still no dice unfortunately. – Erik Jan 09 '16 at 05:43
  • Just don't try to hammer it all into a single hole. Use two declarations, one that you'll use *just* for the interop call and another, a class typically, to store the safe handles. Compare to the way the [framework does it](http://referencesource.microsoft.com/#System/services/monitoring/system/diagnosticts/Process.cs,c50d8ac0eb7bc0d6), note the call to SetProcessHandle at the end. – Hans Passant Jan 09 '16 at 11:35
  • @JonathanPotter, The marshaller uses the default constructor and has special knowledge of SafeHandle allowing it to set the handle. The specific subclass of SafeHandle is expected to provide a default constructor that passes in a suitable bool. – Brian Reichle Jan 09 '16 at 12:06
  • @Erik_at_Digit, did you pre-populate the handle field with instances representing invalid handles? I find I need to pre-populate SafeHandle variables used for ref parameters, structs might have the same requirement. – Brian Reichle Jan 09 '16 at 12:15

1 Answers1

3

As far as I know*, the interop marshaler does not support the use of SafeHandles in classes/structures.

So it works fine to replace IntPtr with SafeHandle in a P/Invoke function declaration, but it doesn't work to replace it in a struct. The handles in the PROCESS_INFORMATION structure have to be initialized by unmanaged code that doesn't know anything about the managed SafeHandle class, so the CLR would need to have specialized knowledge of how to do the required [out] marshaling.

But no worries. There's nothing wrong with your declaration of the struct as-is:

[StructLayout(LayoutKind.Sequential)]
internal struct Win32ProcessInformation
{
    public IntPtr ProcessHandle { get; set; }
    public IntPtr ThreadHandle { get; set; }
    public int ProcessId { get; set; }
    public int ThreadId { get; set; }
}

If you want, once you've called the function that fills a Win32ProcessInformation struct, you can create a SafeHandle object for each of the handle values stored in the IntPtrs. This will ensure the handles get closed when the object gets garbage collected (if you forget to call Dispose() earlier).

For process handles (as in this example), SafeWaitHandle would be a good choice, since all process handles are waitable. This saves you from having to do any extra work because SafeWaitHandle is provided already as a public specialization of SafeHandle. (Speaking of doing extra work, I assume you've already checked to be sure that the Process class doesn't already wrap the reason you're P/Invoking process APIs?)

* This may have changed on some very recent version of the CLR; my knowledge is slightly out-of-date.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • Thanks, totally forgot about `SafeWaitHandle`. Saves me from having to write the subclasses. Unfortunately, the built-in `Process` class does not expose any way to access the process memory (`ReadProcessMemory` or `WriteProcessMemory`). I have created a stream subclass that wraps these methods. Also, the built-in `Process` class does not allow you to create processes with flags (like suspended). So I have to use the `CreateProcess` Win32 API for this. – Erik Jan 09 '16 at 20:47