Share via


.NET Matters

Restart Manager and Generic Method Compilation

Stephen Toub

Code download available at:  Net Matters 2007_04.exe(160 KB)

Q When my application attempts to access a file, I get access-denied errors because the file is in use by another application. In the past, I've used tools from Sysinternals (microsoft.com/technet/sysinternals) to figure out what that other application is, but I'd like to be able to discover this programmatically from within my application. Is there a way to programmatically determine what processes are currently using a specific file?

Q When my application attempts to access a file, I get access-denied errors because the file is in use by another application. In the past, I've used tools from Sysinternals (microsoft.com/technet/sysinternals) to figure out what that other application is, but I'd like to be able to discover this programmatically from within my application. Is there a way to programmatically determine what processes are currently using a specific file?

A I've been asked this question several times, and every time I've refrained from answering it because until very recently, I didn't have an answer. Information on the files currently open by an arbitrary process has been accessible to kernel-mode drivers but until now, no documented user-mode Win32® or Microsoft® .NET Framework APIs that exposed that information have existed. However, with the release of Windows Vista™, and a little ingenuity, it's now possible to determine this information from a user-mode application. Don't go running to the API documentation for an aptly named method, however. Rather, think outside the box a bit, and you'll find yourself staring squarely at the Restart Manager API.

A I've been asked this question several times, and every time I've refrained from answering it because until very recently, I didn't have an answer. Information on the files currently open by an arbitrary process has been accessible to kernel-mode drivers but until now, no documented user-mode Win32® or Microsoft® .NET Framework APIs that exposed that information have existed. However, with the release of Windows Vista™, and a little ingenuity, it's now possible to determine this information from a user-mode application. Don't go running to the API documentation for an aptly named method, however. Rather, think outside the box a bit, and you'll find yourself staring squarely at the Restart Manager API.

New to Windows Vista, the Restart Manager API was added to reduce the number of system restarts that are required during software installation. The main reason software installations and updates require system restarts is that running processes are using files that the installation needs to access. The Restart Manager APIs address this conflict by allowing an installer to politely shut down Restart Manager-aware applications that are holding onto resources the installer needs to access. And as part of this, the Restart Manager APIs allow an installer to query for processes that are holding onto resources the installer previously registered as needing. The beauty of this, though, is that any application can be considered an "installer," and thus any application can use these Restart Manager APIs to query for other processes that are holding onto specific resources. Thus, I can write a method that accepts as a parameter a path to the file in question, and that method can use the Restart Manager APIs to gather a list of processes using that file.

The .NET Framework 3.0 does not include managed equivalents for the Restart Manager APIs, so I need to provide my own through the magic of P/Invoke. For my purposes, I need access to four functions, all of which are exposed from rstrtmgr.dll, and for all of which P/Invoke definitions are shown in Figure 1 (note that the Restart Manager APIs include many more functions than these four, but these are all I need for this purpose).

Figure 1  P/Invoke Declarations for Key Restart Manager APIs

[DllImport(“rstrtmgr.dll”, CharSet = CharSet.Unicode)]
static extern int RmStartSession(
    out uint pSessionHandle, int dwSessionFlags, string strSessionKey);

[DllImport(“rstrtmgr.dll”)]
static extern int RmEndSession(uint pSessionHandle);

[DllImport(“rstrtmgr.dll”, CharSet = CharSet.Unicode)]
static extern int RmRegisterResources(uint pSessionHandle, 
    UInt32 nFiles, string[] rgsFilenames,
    UInt32 nApplications, [In] RM_UNIQUE_PROCESS[] rgApplications, 
    UInt32 nServices, string[] rgsServiceNames);

[DllImport(“rstrtmgr.dll”)]
static extern int RmGetList(uint dwSessionHandle,
    out uint pnProcInfoNeeded, ref uint pnProcInfo,
    [In, Out] RM_PROCESS_INFO[] rgAffectedApps,
    ref uint lpdwRebootReasons);

private const int RmRebootReasonNone = 0;
private const int CCH_RM_MAX_APP_NAME = 255;
private const int CCH_RM_MAX_SVC_NAME = 63;

[StructLayout(LayoutKind.Sequential)]
struct RM_UNIQUE_PROCESS
{
    public int dwProcessId;
    public FILETIME ProcessStartTime;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct RM_PROCESS_INFO
{
    public RM_UNIQUE_PROCESS Process;
    [MarshalAs(UnmanagedType.ByValTStr, 
               SizeConst = CCH_RM_MAX_APP_NAME + 1)]
    public string strAppName;
    [MarshalAs(UnmanagedType.ByValTStr, 
               SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
    public string strServiceShortName;
    public RM_APP_TYPE ApplicationType;
    public uint AppStatus;
    public uint TSSessionId;
    [MarshalAs(UnmanagedType.Bool)]
    public bool bRestartable;
}

enum RM_APP_TYPE
{
    RmUnknownApp = 0,
    RmMainWindow = 1,
    RmOtherWindow = 2,
    RmService = 3,
    RmExplorer = 4,
    RmConsole = 5,
    RmCritical = 1000
}

The RmStartSession function is used to create a new Restart Manager session, and the RmEndSession function is used to close a previously started session. RmStartSession returns a handle to the created session, which is then provided to other functions, including RmEndSession, that need to refer to that session.

The other two functions I need deal specifically with resources. The RmRegisterResources function is used to specify a list of files, applications, and services that my installer wants access to, and the RmGetList function returns information about other processes that are using those resources. RmGetList returns that information as an array of RM_PROCESS_INFO structures, each of which contains the process ID of the offending process.

With these interop signatures and structures in place, I can write my method, which is listed in Figure 2. My GetProcessesUsingFiles method accepts a list of file paths and returns a list of System.Diagnostic.Process objects representing any processes using the files at the specified paths. To start the procedure, the method calls the RmStartSession method to let the Restart Manager know we're getting underway; the string key provided as the last parameter to RmStartSession must be unique, so I'm using a GUID.

Figure 2 Using Restart Manager to Detect In-Use Files

public static IList<Process> GetProcessesUsingFiles(
    IList<string> filePaths)
{
    uint sessionHandle;
    List<Process> processes = new List<Process>();

    // Create a restart manager session
    int rv = RmStartSession(out sessionHandle, 
        0, Guid.NewGuid().ToString(“N”));
    if (rv != 0) throw new Win32Exception();
    try
    {
        // Let the restart manager know what files we’re interested in
        string[] pathStrings = new string[filePaths.Count];
        filePaths.CopyTo(pathStrings, 0);
        rv = RmRegisterResources(sessionHandle,
            (uint)pathStrings.Length, pathStrings,
            0, null, 0, null);
        if (rv != 0) throw new Win32Exception();

        // Ask the restart manager what other applications 
        // are using those files
        const int ERROR_MORE_DATA = 234;
        uint pnProcInfoNeeded = 0, pnProcInfo = 0, 
             lpdwRebootReasons = RmRebootReasonNone;
        rv = RmGetList(sessionHandle, out pnProcInfoNeeded, 
            ref pnProcInfo, null, ref lpdwRebootReasons);
        if (rv == ERROR_MORE_DATA)
        {
            // Create an array to store the process results
            RM_PROCESS_INFO[] processInfo = 
                new RM_PROCESS_INFO[pnProcInfoNeeded];
            pnProcInfo = (uint)processInfo.Length;

            // Get the list
            rv = RmGetList(sessionHandle, out pnProcInfoNeeded, 
                ref pnProcInfo, processInfo, ref lpdwRebootReasons);
            if (rv == 0)
            {
                // Enumerate all of the results and add them to the 
                // list to be returned
                for(int i; i<pnProcInfo; i++) 
                {
                    try
                    {
                        processes.Add(Process.GetProcessById(
                            processInfo[i].Process.dwProcessId));
                    }
                        // in case the process is no longer running
                    catch (ArgumentException) { }
                }
            }
            else throw new Win32Exception();
        }
        else if (rv != 0) throw new Win32Exception();
    }
        // Close the resource manager
    finally { RmEndSession(sessionHandle); }

    return processes;
}

If the session is started successfully, I pass the file paths provided by the user to the RmRegisterResources function (since I only care about files, the parameters dealing with applications and services are ignored). With the resources registered, I call the RmGetList function. The function accepts several parameters, including the session handle, an out parameter that will store the number of processes affected, a parameter detailing the number of elements in the array I'm providing, the array to store returned process information, and a parameter that dictates why a restart is needed (in our case, it's not needed, so RmRebootReasonNone is specified). On this call to RmGetList, I don't know yet how many processes are involved, so I pass 0 into the pnProcInfo parameter. If there are no affected processes, RmGetList will return 0. If it returns the value 234, or ERROR_MORE_DATA, the array I provided is not big enough to store information about the affected processes, which really means that there are affected processes, since the array I passed in was of length 0. But now that I know how many processes are affected, I can call RmGetList again, this time with the correctly sized array. Assuming it returns 0 this time, I loop through the returned RM_PROCESS_INFO structures, using the Process.GetProcessById method and the Process.dwProcessId from each to create a List<Process> collection that I can return to the caller.

This approach to calling RmGetList twice does have a drawback, but it's necessary given the model in which RmGetList is expected to be used. It's possible that between the first call and the second call, the number of processes using the file has changed. As such, even on the second call the array provided might not be big enough to handle all of the information that needs to be returned. In this scenario, I've chosen to throw an exception, but you can modify the method to try calling RmGetList in a loop, either until the array is the correct size or until a certain retry count has been reached.

With this method in hand, it's now a simple task to enumerate all of the processes accessing a specific file, as is demonstrated by the following application. This is a useful tool to compile and leave on your desktop; if you ever have a problem accessing a file, just drag and drop that file onto the app, and it'll tell you what applications/processes are using it:

class Program {
    static void Main(string[] args) {
        IList<Process> processes = 
            InUseDetection.GetProcessesUsingFiles(args);
        Console.WriteLine(processes.Count + " processes found:");
        foreach (Process p in processes) {
            Console.WriteLine(p.ProcessName + ": " + p.Id);
        }
        Console.ReadLine();
    }
}

With the resulting Process instances, you can do whatever you want with those processes found to be using your files, including gathering information about them or even killing them. However, think twice (or three times) before doing the latter; that's the whole reason the Restart Manager APIs exist, to politely shut down other applications using resources you want access to while allowing them to save state so they can be restarted without losing any data. Restart Manager is able to shut down applications as well as services. Currently, however, it can only shut down applications that exist in the same user session as the Restart Manager session (so, for example, an application you run can't use Restart Manager to shut down an application being run by another user logged in through Terminal Services). Finally, note that this technique will only return information about processes that are holding an active file handle to the file in question; if an application opened a file, grabbed some data from it, closed the file, and then displayed that data, it may look to you like the file is open by the application, but this procedure won't show that (which is, of course, correct behavior).

For more information on using the Restart Manager APIs, see Daniel Moth's screencast about the subject on Channel 9 at channel9.msdn.com/Showpost.aspx?postid=251492.

Q I've written a generic method, and I understand that this method may be compiled multiple times based on different type arguments provided to it. How can I tell if the method is being reused based on the arguments with which I'm using it, or if it is, in fact, being recompiled for different invocations?

Q I've written a generic method, and I understand that this method may be compiled multiple times based on different type arguments provided to it. How can I tell if the method is being reused based on the arguments with which I'm using it, or if it is, in fact, being recompiled for different invocations?

A In short, the native code generated for a generic method is shared when the type arguments are all reference types, and is not shared when any are value types (there are some subtleties here, however, so see blogs.msdn.com/259224.aspx for a bit more information). However, using Visual Studio® 2005, you can pretty easily figure out when, in fact, the generated native code for your managed method is being reused. Simply set a breakpoint in the method and at run time open the Registers debugging window. One of the registers shown will be the EIP register, which is the register storing the address in memory of the next instruction to execute. Every time the breakpoint in your method is hit, check the value of EIP. If the method was only compiled once, every time you record the value of EIP, it'll be the same. And if it ever changes, you know there are multiple instantiations of the generic method.

A In short, the native code generated for a generic method is shared when the type arguments are all reference types, and is not shared when any are value types (there are some subtleties here, however, so see blogs.msdn.com/259224.aspx for a bit more information). However, using Visual Studio® 2005, you can pretty easily figure out when, in fact, the generated native code for your managed method is being reused. Simply set a breakpoint in the method and at run time open the Registers debugging window. One of the registers shown will be the EIP register, which is the register storing the address in memory of the next instruction to execute. Every time the breakpoint in your method is hit, check the value of EIP. If the method was only compiled once, every time you record the value of EIP, it'll be the same. And if it ever changes, you know there are multiple instantiations of the generic method.

This is demonstrated by the program shown in Figure 3. I set a breakpoint on the highlighted line and ran the app within Visual Studio. The breakpoint was hit six times, resulting in the following EIP values (these may change from run to run, but the relationships about to be discussed between the values will remain the same):

0073123A
00731297
00731297
00731312
00731297
0073123A

Figure 3 Generic Method to Test Compilation

class Program
{
    static void Main() {
        Test.WriteType<int>();
        Test.WriteType<string>();
        Test.WriteType<object>();
        Test.WriteType<float>();
        Test.WriteType<Test>();
        Test.WriteType<int>();
    }

    class Test {
        public static void WriteType<T>() {
            Console.WriteLine(typeof(T).ToString());
        }
    }
}

As you can see, the values for the 2nd, 3rd, and 5th entries are all the same, since those correspond to the reference type arguments. The 1st and 6th entries are equal but are different from all the others, as the same value type is being used. And the 4th entry is unique as it's the only call using the float value type.

Of course, this technique will only work with JIT compiler implementations that don't support code pitching (the ability to free JIT-compiled code). Thus, it would be ineffective, for example, with code running on the .NET Compact Framework.

Send your questions and comments to  netqa@microsoft.com.

Stephen Toub is the Technical Editor for MSDN Magazine.