Joseph Nields
Joseph Nields

Reputation: 5661

How can you ensure an executable is opened via another executable?

There's a neat little trick that you can use to never have the OS hold a lock on the .dll's / .exe's an .exe needs to run.

Let's say our directory looks like this:

>opener.exe
>actualProgram.exe
>dependency.dll

As a basic example, we could do this:

namespace Opener
{
    class Program
    {
        static void Main(string[] args)
        {

            AppDomain domain = AppDomain.CurrentDomain;
            AppDomain newDomain = AppDomain.CreateDomain(
                domain.FriendlyName,
                domain.Evidence,
                domain.BaseDirectory,
                domain.RelativeSearchPath,
                true,  // "shadow copy" files: copy rather than lock
                domain.SetupInformation.AppDomainInitializer,
                domain.SetupInformation.AppDomainInitializerArguments
            );
            string assembly = 
                System.Reflection.Assembly.GetExecutingAssembly().Location 
                + "\\..\\actualProgram.exe";
            newDomain.ExecuteAssembly(assembly, args);
        }
    }
}

We could also put more logic in there, which we could use to actually replace/update actualProgram.exe despite an instance of the process being opened. Try it: you'll be able to delete/modify/replace dependencies and the "actual" program when it's loaded via the "opener". Obviously this has its benefits, namely making updates a lot easier.

But in your "actual.exe", how can you be sure that it was loaded via another executable, and know that it was loaded without having a lock taken on the .exe? If someone is to just load the "actual.exe", i.e. not via the "opener", I want to make sure that the process immediately exits.

Hints?

Upvotes: 8

Views: 260

Answers (2)

JosephStyons
JosephStyons

Reputation: 58755

How about sending a custom message to the application after it is launched? The code to launch would be something like this:

private void LaunchApplication()
{
  ProcessStartInfo start = new ProcessStartInfo();

  //you could pass in arguments here if you wanted
  start.Arguments = string.Empty;

  //your EXE name
  start.FileName = @"c:\your_program.exe";

  start.WindowStyle = ProcessWindowStyle.Normal;
  start.CreateNoWindow = true;

  Process p = Process.Start(start);
  while (0 == (int)p.MainWindowHandle)
  {
    System.Threading.Thread.Sleep(100);
  }
  processHandle = p.MainWindowHandle;
  label1.Text = processHandle.ToString();

  //the launched program will receive this message as a signal that it can continue
  SendNotifyMessage(processHandle, 99999, UIntPtr.Zero, IntPtr.Zero);
}

and the application you are running would need a short custom windows message handler to watch out for the "stay-alive" signal.

private bool allowedToLive = false;
//how long will we wait for the signal?  should arrive in < 1 second
private int TimeoutSeconds = 1;
private DateTime appStartTime = DateTime.Now;

protected override void WndProc(ref Message m)
{
  base.WndProc(ref m);
  //receiving the special message means we are allowed to live.
  if(!allowedToLive)
  {
    allowedToLive = (m.Msg == 99999);
    if (!allowedToLive && (TimeoutSeconds <= DateTime.Now.Subtract(appStartTime).Seconds))
    {
      //times up.  quit.
      Application.Exit();
    }
  }
}

If you don't want to rely on the timing of windows messages, you could alter your target program to launch, but not actually begin doing anything until it receives the custom message.

Upvotes: 0

theB
theB

Reputation: 6738

  1. Pass a command line parameter to actualProgram.exe that lets it know it was launched by the launcher. (Also suggested in a comment on the OP by oleksii)
  2. Set an environment variable in the launcher that can be inherited by the child process.
  3. Use one of the documented methods for getting the parent process id, and use that to get information about the launching process. (Of course if the parent process terminates, Windows is free to re-assign the PID to a new process.)
  4. Create a memory-mapped file/named pipe/etc. in the launcher that can be used to pass information to the child.
  5. Manually load and unload the library references in the child. If they are .Net assemblies, you can use the AssemblyResolve event, loading the file from disk into a byte array and using the Assembly.Load(byte[], byte[]) overload.

Note: As long as all of your assemblies are loaded manually or by the runtime before you modify/move/delete them, you're probably ok. Also, changing the DLL after it's been loaded will probably have no effect on the running process. I say probably in both cases, because it's generally a good idea to leave the libraries alone at runtime, especially if you aren't loading them manually.

See Also:

Upvotes: 2

Related Questions