本文探討兩個 .NET 進階實務議題:以程式碼偵測哪個進程正在佔用特定檔案,以及泛型方法在 JIT 編譯時的程式碼共享規則。
傳統需借助 Sysinternals Handle.exe 等工具,Windows Vista+ 引入 Restart Manager API (rstrtmgr.dll),允許使用者模式程式查詢檔案佔用者。
[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);
public static IList<Process> GetProcessesUsingFiles(IList<string> filePaths) {
uint sessionHandle;
var processes = new List<Process>();
int rv = RmStartSession(out sessionHandle, 0, Guid.NewGuid().ToString("N"));
if (rv != 0) throw new Win32Exception();
try {
string[] paths = filePaths.ToArray();
rv = RmRegisterResources(sessionHandle, (uint)paths.Length, paths, 0, null, 0, null);
if (rv != 0) throw new Win32Exception();
const int ERROR_MORE_DATA = 234;
uint pnProcInfoNeeded = 0, pnProcInfo = 0, lpdwRebootReasons = 0;
rv = RmGetList(sessionHandle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);
if (rv == ERROR_MORE_DATA) {
var processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
pnProcInfo = (uint)processInfo.Length;
rv = RmGetList(sessionHandle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
if (rv == 0) {
foreach (var info in processInfo) {
try {
processes.Add(Process.GetProcessById(info.Process.dwProcessId));
} catch (ArgumentException) { /* 進程已結束 */ }
}
} else throw new Win32Exception();
} else if (rv != 0) throw new Win32Exception();
} finally {
RmEndSession(sessionHandle);
}
return processes;
}
var processes = GetProcessesUsingFiles(new[] { @"C:\test.txt" });
foreach (var p in processes) Console.WriteLine($"{p.ProcessName} (PID: {p.Id})");
注意:僅偵測持有開啟檔案控制代碼的進程;已讀取並關閉檔案的進程不會被列出。
.NET 執行階段對泛型方法的原生碼生成策略:
string、object、自訂類別等int、float、struct 等各產生一份專屬程式碼EIP(下一條指令位址)class Program {
static void Main() {
Test.WriteType<int>(); // 實值型別 - 獨立程式碼
Test.WriteType<string>(); // 參考型別 - 共享程式碼
Test.WriteType<object>(); // 參考型別 - 共享程式碼
Test.WriteType<float>(); // 實值型別 - 獨立程式碼
Test.WriteType<Test>(); // 參考型別 - 共享程式碼
Test.WriteType<int>(); // 同 int - 共享程式碼
}
}
class Test {
public static void WriteType<T>() {
Console.WriteLine(typeof(T).ToString()); // 設中斷點於此
}
}
string、object、Test → 相同 EIPint(第一次與第六次)→ 相同 EIPfloat → 獨特 EIP此技巧僅適用於不支援程式碼捨棄的 JIT(如桌面版 .NET Framework),.NET Compact Framework 不適用。