Whitney Kew
Whitney Kew

Reputation: 225

C# -> C++/CLI -> native method - need SafeProcessHandle?

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?

Upvotes: 1

Views: 622

Answers (1)

Lucas Trzesniewski
Lucas Trzesniewski

Reputation: 51330

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.

Upvotes: 1

Related Questions