3

我在 c# 中的 openas_rundll 的帮助下使用 openfile 对话框打开一个文件。

Process.Start("rundll32.exe", string.Format("shell32.dll,OpenAs_RunDLL \"{0}\"", tempFilePath));

现在我想检测哪个程序用于打开文件。我想追踪这个过程。

我的目标是在用户关闭程序时删除文件。

4

2 回答 2

0

您可以通过找到它的 py 父进程 ID 来尝试捕捉实际应用程序关闭的时刻。如果你找到它,你可以等待它关闭,只要它是可以接受的。感谢jeremy-murrayGetAllProcessParentPids方法

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”),您也无法找到用户关闭包含您的文件的选项卡的时刻。

如果我是你,我会实施这个解决方案,但仍然不理想,如果有必要我会在以后升级它。

于 2012-07-30T10:52:21.193 回答
0

只是一个简单的帮助类,它为您提供了一种使用 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
    }
}
于 2012-07-30T13:43:16.033 回答