这是情况。
我有一个应用程序,出于所有意图和目的,我必须像黑盒子一样对待。
我需要能够使用一组文件打开此应用程序的多个实例。打开它的语法是executable.exe file1.ext file2.ext
。
如果我在没有参数的情况下运行executable.exe
x次,则新实例打开正常。
如果我运行executable.exe file1.ext
后跟executable.exe file2.ext
,那么第二个调用将在现有窗口中打开文件2而不是创建新实例。这会影响我的其余解决方案并且是问题所在。
我的解决方案包装了这个应用程序并对它执行了各种管理操作,这是我的一个包装类:
public class myWrapper
{
public event EventHandler<IntPtr> SplashFinished;
public event EventHandler ProcessExited;
private const string aaTrendLocation = @"redacted";
//private const string aaTrendLocation = "notepad";
private readonly Process _process;
private readonly Logger _logger;
public myWrapper(string[] args, Logger logger =null)
{
_logger = logger;
_logger?.WriteLine("Intiialising new wrapper object...");
if (args == null || args.Length < 1) args = new[] {""};
ProcessStartInfo info = new ProcessStartInfo(aaTrendLocation,args.Aggregate((s,c)=>$"{s} {c}"));
_process = new Process{StartInfo = info};
}
public void Start()
{
_logger?.WriteLine("Starting process...");
_logger?.WriteLine($"Process: {_process.StartInfo.FileName} || Args: {_process.StartInfo.Arguments}");
_process.Start();
Task.Run(()=>MonitorSplash());
Task.Run(() => MonitorLifeTime());
}
private void MonitorLifeTime()
{
_logger?.WriteLine("Monitoring lifetime...");
while (!_process.HasExited)
{
_process.Refresh();
Thread.Sleep(50);
}
_logger?.WriteLine("Process exited!");
_logger?.WriteLine("Invoking!");
ProcessExited?.BeginInvoke(this, null, null, null);
}
private void MonitorSplash()
{
_logger?.WriteLine("Monitoring Splash...");
while (!_process.MainWindowTitle.Contains("Trend"))
{
_process.Refresh();
Thread.Sleep(500);
}
_logger?.WriteLine("Splash finished!");
_logger?.WriteLine("Invoking...");
SplashFinished?.BeginInvoke(this,_process.MainWindowHandle,null,null);
}
public void Stop()
{
_logger?.WriteLine("Killing trend...");
_process.Kill();
}
public IntPtr GetHandle()
{
_logger?.WriteLine("Fetching handle...");
_process.Refresh();
return _process.MainWindowHandle;
}
public string GetMainTitle()
{
_logger?.WriteLine("Fetching Title...");
_process.Refresh();
return _process.MainWindowTitle;
}
}
我的包装器一切正常,直到我开始提供文件参数并且这种意外的实例化行为开始。
我无法修改目标应用程序,也无法访问其来源以确定此实例化是通过互斥锁还是通过其他功能进行管理。因此,我需要确定是否有办法阻止新实例查看现有实例。有人会有什么建议吗?
TLDR:如何阻止仅限于单个实例的应用程序确定已运行实例
为了澄清(跟随可疑的评论),我公司的研发团队写了executable.exe
但我没有时间等待他们的帮助(我有几天而不是几个月)并且有权做任何必要的事情以提供所需的功能(这里有)我的解决方案比这个问题更快地提到了。
通过一些反编译工作,我可以看到以下内容用于查找现有实例。
Process[] processesByName = Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName);
有没有办法搞砸这个用不同名称创建应用程序的多个副本?我考虑在运行中重命名Process
,但显然这不可能没有编写内核漏洞...
我过去通过创建源可执行文件的副本解决了这个问题。在您的情况下,您可以:
在Dave Lucre的回答基础上,我通过创建绑定到我的包装类的可执行文件的新实例来解决它。最初,我继承了IDisposable
并删除了Disposer中的临时文件,但由于某些原因导致清理会阻止应用程序的问题,所以现在我的主程序最后执行清理。
我的构造函数现在看起来像:
public AaTrend(string[] args, ILogger logger = null)
{
_logger = logger;
_logger?.WriteLine("Initialising new aaTrend object...");
if (args == null || args.Length < 1) args = new[] { "" };
_tempFilePath = GenerateTempFileName();
CreateTempCopy(); //Needed to bypass lazy single instance checks
HideTempFile(); //Stops users worrying
ProcessStartInfo info = new ProcessStartInfo(_tempFilePath, args.Aggregate((s, c) => $"{s} {c}"));
_process = new Process { StartInfo = info };
}
使用两种新方法:
private void CreateTempCopy()
{
_logger?.WriteLine("Creating temporary file...");
_logger?.WriteLine(_tempFilePath);
File.Copy(AaTrendLocation, _tempFilePath);
}
private string GenerateTempFileName(int increment = 0)
{
string directory = Path.GetDirectoryName(AaTrendLocation); //Obtain pass components.
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(AaTrendLocation);
string extension = Path.GetExtension(AaTrendLocation);
string tempName = $"{directory}\\{fileNameWithoutExtension}-{increment}{extension}"; //Re-assemble path with increment inserted.
return File.Exists(tempName) ? GenerateTempFileName(++increment) : tempName; //If this name is already used, increment an recurse otherwise return new path.
}
然后在我的主程序中:
private static void DeleteTempFiles()
{
string dir = Path.GetDirectoryName(AaTrend.AaTrendLocation);
foreach (string file in Directory.GetFiles(dir, "aaTrend-*.exe", SearchOption.TopDirectoryOnly))
{
File.Delete(file);
}
}
作为旁注,这种方法仅适用于使用(懒惰)方法确定依赖于Process.GetProcessByName()
的实例化的应用程序;如果使用Mutex
或者在清单中明确设置了可执行文件名,它将无法工作。