我在 c# 中的 openas_rundll 的帮助下使用 openfile 对话框打开一个文件。
Process.Start("rundll32.exe", string.Format("shell32.dll,OpenAs_RunDLL \"{0}\"", tempFilePath));
现在我想检测哪个程序用于打开文件。我想追踪这个过程。
我的目标是在用户关闭程序时删除文件。
我在 c# 中的 openas_rundll 的帮助下使用 openfile 对话框打开一个文件。
Process.Start("rundll32.exe", string.Format("shell32.dll,OpenAs_RunDLL \"{0}\"", tempFilePath));
现在我想检测哪个程序用于打开文件。我想追踪这个过程。
我的目标是在用户关闭程序时删除文件。
您可以通过找到它的 py 父进程 ID 来尝试捕捉实际应用程序关闭的时刻。如果你找到它,你可以等待它关闭,只要它是可以接受的。感谢jeremy-murray的GetAllProcessParentPids方法:
public void StartProcessAndWathTillTerminated(string tempFilePath)
{
// Show app selection dialog to user
Process rundll32 = Process.Start("rundll32.exe", string.Format("shell32.dll,OpenAs_RunDLL {0}", tempFilePath));
int rundll32id = rundll32.Id;
// Wait till dialog is closed
while (!rundll32.HasExited)
{
System.Threading.Thread.Sleep(50);
}
// Get all running processes with parent id
Dictionary<int, int> allprocparents = GetAllProcessParentPids();
int openedAppId = 0;
// Loop throu all processes
foreach (var allprocparent in allprocparents)
{
// Found child process, started by our rundll32.exe instance
if (allprocparent.Value == rundll32id)
{
openedAppId = allprocparent.Key;
break;
}
}
// Check if we actually found any process. It can not be found in two situations:
// 1) Process was closed too soon, while we was looking for it
// 2) User clicked Cancel and no application was opened
// Also it is possible that chesen application is already running. In this
// case new instance will be opened by rundll32.exe for a very short period of
//time needed to pass file path to running instance. Anyway, this case falls into case 1).
//If we ca not find process explicitly, we can try to find it by file lock, if one exists:
//I'm using here a code snippet from https://stackoverflow.com/a/1263609/880156,
//which assumes that there are possible more than one lock on this file.
//I just take first.
if (openedAppId==0)
{
Process handleExe = new Process();
handleExe.StartInfo.FileName = "handle.exe";
handleExe.StartInfo.Arguments = tempFilePath;
handleExe.StartInfo.UseShellExecute = false;
handleExe.StartInfo.RedirectStandardOutput = true;
handleExe.Start();
handleExe.WaitForExit();
string outputhandleExe = handleExe.StandardOutput.ReadToEnd();
string matchPattern = @"(?<=\s+pid:\s+)\b(\d+)\b(?=\s+)";
foreach(Match match in Regex.Matches(outputhandleExe, matchPattern))
{
openedAppId = int.Parse(match.Value);
break;
}
}
if (openedAppId != 0)
{
Process openedApp = Process.GetProcessById(openedAppId);
while (!openedApp.HasExited)
{
System.Threading.Thread.Sleep(50);
}
}
// When we reach this position, App is already closed or was never started.
}
public static Dictionary<int, int> GetAllProcessParentPids()
{
var childPidToParentPid = new Dictionary<int, int>();
var processCounters = new SortedDictionary<string, PerformanceCounter[]>();
var category = new PerformanceCounterCategory("Process");
// As the base system always has more than one process running,
// don't special case a single instance return.
var instanceNames = category.GetInstanceNames();
foreach(string t in instanceNames)
{
try
{
processCounters[t] = category.GetCounters(t);
}
catch (InvalidOperationException)
{
// Transient processes may no longer exist between
// GetInstanceNames and when the counters are queried.
}
}
foreach (var kvp in processCounters)
{
int childPid = -1;
int parentPid = -1;
foreach (var counter in kvp.Value)
{
if ("ID Process".CompareTo(counter.CounterName) == 0)
{
childPid = (int)(counter.NextValue());
}
else if ("Creating Process ID".CompareTo(counter.CounterName) == 0)
{
parentPid = (int)(counter.NextValue());
}
}
if (childPid != -1 && parentPid != -1)
{
childPidToParentPid[childPid] = parentPid;
}
}
return childPidToParentPid;
}
更新
由于多种原因,似乎没有100%保证成功的解决方案。我认为找到一个由 rundll32.exe 启动的进程是最可靠的。如果它失败了,你仍然可以用其他一些方法来完成它来确定进程 ID。
据我所知,还有其他几种方法可以找到仍在使用的文件。例如,Winword.exe 在同一目录中创建一些临时文件,并在关闭时将其删除。因此,如果您能够捕捉到临时文件删除的瞬间,那么您可能会认为该程序已关闭。
其他程序可能会通过设置锁定来保持您的文件处于打开状态。如果是这样,您可以通过查找锁所有者来找到该程序。我使用了来自这个答案https://stackoverflow.com/a/1263609/880156的外部程序 handle.exe 的解决方案,所以看看。
我不得不提一下,可能根本没有永久文件锁。它取决于程序架构。例如,如果您使用 Firefox 打开 html 文件,它会尽可能快地读取文件并关闭它,并且不再锁定文件。在这种情况下,即使您以某种方式找到进程名称(例如“firefox.exe”),您也无法找到用户关闭包含您的文件的选项卡的时刻。
如果我是你,我会实施这个解决方案,但仍然不理想,如果有必要我会在以后升级它。
只是一个简单的帮助类,它为您提供了一种使用 Windows 的 OpenWithDialog 打开文件的方法,并使用 WMI 监视启动的进程以识别所选的应用程序。
对于 WMI,添加 System.Management.dll 作为参考
注意:它不能识别 windows 照片查看器 - 这是一个 dllhost.exe
针对您的情况的示例调用:
using (OpenFileDialog ofd = new OpenFileDialog())
{
ofd.Filter = "All files(*.*)|*.*";
if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
using (Win.OpenWithDialogHelper helper = new Win.OpenWithDialogHelper())
{
helper.OpenFileAndWaitForExit(ofd.FileName);
File.Delete(helper.Filepath);
}
}
}
班上:
namespace Win
{
using System.Management;
using System.Threading;
using System.Diagnostics;
using System.IO;
public class OpenWithDialogHelper : IDisposable
{
#region members
private Process openWithProcess;
private ManagementEventWatcher monitor;
public string Filepath { get; set; }
public Process AppProcess { get; private set; }
#endregion
#region .ctor
public OpenWithDialogHelper()
{
}
public OpenWithDialogHelper(string filepath)
{
this.Filepath = filepath;
}
#endregion
#region methods
public void OpenFileAndWaitForExit(int milliseconds = 0)
{
OpenFileAndWaitForExit(this.Filepath, milliseconds);
}
public void OpenFileAndWaitForExit(string filepath, int milliseconds = 0)
{
this.Filepath = filepath;
this.openWithProcess = new Process();
this.openWithProcess.StartInfo.FileName = "rundll32.exe";
this.openWithProcess.StartInfo.Arguments = String.Format("shell32.dll,OpenAs_RunDLL \"{0}\"", filepath);
this.openWithProcess.Start();
//using WMI, remarks to add System.Management.dll as reference!
this.monitor = new ManagementEventWatcher(new WqlEventQuery("SELECT * FROM Win32_ProcessStartTrace"));
this.monitor.EventArrived += new EventArrivedEventHandler(start_EventArrived);
this.monitor.Start();
this.openWithProcess.WaitForExit();
//catching the app process...
//it can't catched when the process was closed too soon
//or the user clicked Cancel and no application was opened
Thread.Sleep(1000);
int i = 0;
//wait max 5 secs...
while (this.AppProcess == null && i < 3000)
{
Thread.Sleep(100); i++;
}
if (this.AppProcess != null)
{
if (milliseconds > 0)
this.AppProcess.WaitForExit(milliseconds);
else
this.AppProcess.WaitForExit();
}
}
public void Dispose()
{
if (this.monitor != null)
{
this.monitor.EventArrived -= new EventArrivedEventHandler(start_EventArrived);
this.monitor.Dispose();
}
if(this.openWithProcess != null)
this.openWithProcess.Dispose();
if (this.AppProcess != null)
this.AppProcess.Dispose();
}
#endregion
#region events
private void start_EventArrived(object sender, EventArrivedEventArgs e)
{
int parentProcessID = Convert.ToInt32(e.NewEvent.Properties["ParentProcessID"].Value);
//The ParentProcessID of the started process must be the OpenAs_RunDLL process
//NOTICE: It doesn't recognice windows photo viewer
// - which is a dllhost.exe that doesn't have the ParentProcessID
if (parentProcessID == this.openWithProcess.Id)
{
this.AppProcess = Process.GetProcessById(Convert.ToInt32(
e.NewEvent.Properties["ProcessID"].Value));
if (!this.AppProcess.HasExited)
{
this.AppProcess.EnableRaisingEvents = true;
}
}
}
#endregion
}
}