1

I'm targeting .NET 4.6.1, and I have C# code that calls C++/CLI code that calls the native Win32 method EnumProcessModulesEx, which needs a HANDLE as its first input parameter:

// C#
System.Diagnostics.Process process = // ...
var allModuleNames = ProcessHelper.GetAllModuleNames(process);

// C++/CLI
#include <Psapi.h>
// ...
using namespace System;
using namespace System::Diagnostics;
// ...
array<String^>^ ProcessHelper::GetAllModuleNames(Process^ process)
{
    // Should I use a SafeProcessHandle instead?
    HANDLE processHandle = static_cast<HANDLE>(process->Handle.ToPointer());
    BOOL result = EnumProcessModulesEx(processHandle, /*...*/);
    // ...
}

I have control over both the C# and C++/CLI code, and I'm not doing any P/Invoke. My C++/CLI method currently accepts a Process parameter, but it's only using the Process.Handle property (and doing a cast to obtain the necessary HANDLE value). Is this safe, or do I need a SafeProcessHandle somewhere? If so, how do I pass the SafeProcessHandle value to EnumProcessModulesEx? Do I have to call SafeHandle.DangerousGetHandle?

Whitney Kew
  • 215
  • 3
  • 13
  • 1
    You don't like the word "dangerous". Buy Microsoft a cigar for good naming, it doesn't solve the real problem. Yes, you are using a property of a class object that can be garbage collected while EnumProcessModules() is running. Low odds, not zero. Excessively low odds btw, nothing you can ever debug. While you can fix it by changing the argument type to HandleRef, the friendlier way is to add `GC::KeepAlive(process)` at the bottom of your method. – Hans Passant Jun 29 '16 at 22:09

1 Answers1

1

The purpose of SafeHandle is to make sure the handle will get released when the SafeHandle is finalized, so there are no resource leaks. In other words, it lets the GC manage the lifetime of the handle, if you ever neglect to release it manually. P/Invoke understands it and makes sure it's not finalized during the WinAPI call even if you don't hold a reference to it anywhere else.

You can't really use it when using the Win32 API directly, you'd have to call DangerousGetHandle() which simply returns the raw handle you already have.

In your case, the handle is retrieved with the OpenProcess WinAPI function, by the Process class. The docs state that:

When you are finished with the handle, be sure to close it using the CloseHandle function.

Which means the Process class will free it. It does so on the Dispose call, and also in Close (Dispose calls Close). If you don't dispose the Process object, the GC will close the handle for you.

So all you have to do is to make sure the GC won't collect the process object while you're using the handle, or the handle will be invalid. One simple way to ensure that is to call:

System::GC::KeepAlive(process);

after all the code that makes use of the handle. When the JIT sees this function call, it will ensure the process reference will be marked as used at least until that line (the JIT will report reference reachability to the GC).

If you don't do that, and if you don't use the process object later in your code, and if a GC occurs, the GC will notice the object is unreachable and collect it (yes, even though you have a local variable referencing it in your code - if you don't access it in the remaining code chunk it will get collected, the GC is very aggressive about that).

See my answer here for more details, although it's about P/Invoke usage.

Community
  • 1
  • 1
Lucas Trzesniewski
  • 50,214
  • 11
  • 107
  • 158
  • From what you're saying it sounds the like local handle to process is released after its last use, before the call to EnumPorcess ModuleEx. Is that correct? I'm used to C++ scoping where things don't get released until the end of the block. – tukra Jul 03 '16 at 18:10
  • @tukra unless you `Dispose()` it explicitly, the `SafeHandle` will be released **nondeterministically**. The GC will spot it as an unreachable object, it will put it on the finalizer queue, and it will get finalized on a different thread. The `KeepAlive` call makes sure none of this happens at least until the point where you call it. Things would be simpler if you had explicit control over the handle, which you do not, as the `Process` class owns it. – Lucas Trzesniewski Jul 03 '16 at 18:22
  • @"Lucas Trzesniewski" Please bear with me as I'm a few steps behind. The process object has a Handle property that returns an IntPtr. I would have guessed the validity of that IntPtr is tied to the lifetime of the Process object. It seems like you're saying it's not. I'm not sure where a SafeHandle object is coming into the picture. Is that a member object of Process that the IntPtr is coming from? – tukra Jul 03 '16 at 18:38
  • Ok, I found this: "the JIT compiler can do lookahead optimization, and may mark any object for collection after what it considers it's last use, ignoring scope." Which seems to answer my original question. The Process object can be marked for cleanup before the call to EnumPorcessModuleEx, unlike normal C++ scope rules. Is that correct? What does KeepAlive even do then? Wouldn't any trivial use of process keep the object alive? – tukra Jul 03 '16 at 20:35
  • @tukra The `Process` class exposes an `IntPtr`, but internally it holds a `SafeProcessHandle`, take a look at the [source code](http://referencesource.microsoft.com/#System/services/monitoring/system/diagnosticts/Process.cs), you guessed right. The `Process` keeps a reference to the `SafeHandle`, which indeed keeps it alive as long as `Process` is alive itself. But the `Process` object may be collected at any time when not used. You're right that *any* trivial usage of the `process` reference will have the same effect as `GC::KeepAlive`, but this function conveys the intent, without overhead. – Lucas Trzesniewski Jul 03 '16 at 22:15
  • Thanks for your help understanding this. I find it sort of astonishing that traditional scope rules would not apply to gc handles. That seems like a huge gotcha with little upside. – tukra Jul 03 '16 at 22:39
  • @tukra you can still get RAII with `IDisposable`, you just need to write a destructor in your class (`~TheClass()`, not `!TheClass()` which is the finalizer), and use stack semantics. C++/CLI has all the gotchas of C++, and all the gotchas of .NET - you simply need to understand how everything works together. – Lucas Trzesniewski Jul 04 '16 at 07:58