C# 使用 Process.Start 執行外部程式


C# 執行外部程式的方式並不難,也很簡單,首先引入
using System.Diagnostics;
然後使用 Process.Start("OtherProgram.exe")方式就可以呼叫並執行。



但是,某些情況下會導致程式無法被執行(或是無法正常執行),
很多原因在於『執行程式的起始位置』,由於被呼叫的程式執行時,
它所啟動的路徑其實是在呼叫程式的路徑下,
如果被呼叫程式需要讀取與程式相同路徑下的文件檔案(或是INI),可能會導致找不到的問題。

為了測試這樣的現象,我準備了2支程式,
一支是被呼叫端的程式 HelloConsole.exe
另外一支就是呼叫端程式 CallOtherProgram.exe

HelloConsole.exe 這支程式在啟動時會讀取相同路徑下的 Doc.txt 並將內容顯示出來,如果有參數也會把參數列出來。而這支程式我固定放在 D:\TEST\ 下,以方便測試,如果不是在這路徑下,那呼叫端的程式碼裡面的路徑也要變更。

HelloConsole.exe

using System;
using System.IO;

namespace HelloConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("This is HelloConsole.exe");
            Console.WriteLine(" ");
            Console.WriteLine("Environment.CurrentDirectory is ");
            Console.WriteLine(Environment.CurrentDirectory);
            Console.WriteLine(" ");
            Console.WriteLine("AppDomain.CurrentDomain.BaseDirectory is ");
            Console.WriteLine(AppDomain.CurrentDomain.BaseDirectory);
            Console.WriteLine(" ");

            if (args.Length > 0)
            {
                Console.WriteLine("You passed arguments is :");
                foreach(string arg in args)
                {
                    Console.WriteLine(arg);
                }
            }

            string fileName = "Doc.txt";

            if (File.Exists(fileName))
            {
                Console.WriteLine("Retrieve Doc.txt content ...");
                using (StreamReader reader = new StreamReader(fileName))
                {
                    string content = reader.ReadToEnd();
                    Console.WriteLine("===== Start of content =====");
                    Console.WriteLine(content);
                    Console.WriteLine("====== End of content ======");
                }
            }
            else
            {
                Console.WriteLine(" >> Doc.txt can not be read because it not exists !!");
            }
            Console.WriteLine(" ");
            Console.Write("Press Enter to Close Program....");
            Console.ReadLine();
        }
    }
}


另外 Doc.txt 內容:
1.This is a document file with program and in same folder
2.Use to test program can read text normal.

注意上面程式碼開啟 Doc.txt 的方式是不帶絕對路徑的方式,意思是希望以程式當下啟動路徑下直接開啟 Doc.txt。(下面會解釋呼叫會發生的問題)

我們先單獨執行 HelloConsole.exe 看看是甚麼樣子

從畫面可以發現執行後 Environment.CurrentDirectory 這樣的環境變數確實是在程式存在的地方(D:\TEST),因此讀取/開啟 Doc.txt 時可以正常讀取。


接下來我們看看呼叫端程式 CallOtherProgram.exe ,這支程式會提供 4 種呼叫方式來測試呼叫的結果

  1. 簡單呼叫方式
  2. 簡單呼叫方式(帶參數)
  3. 標準呼叫方式(提供環境設定)
  4. 標準呼叫方式(提供環境設定/帶參數)


CallOtherProgram.exe

using System;
using System.Diagnostics;
using System.IO;

namespace CallOtherProgram
{
    class Program
    {
        static void Main(string[] args)
        {
            string fileName = "D:\\TEST\\HelloConsole.exe";
            Console.WriteLine("This Program use to test call other program by different method ,");
            Console.WriteLine("It will call " + fileName + " to run and watch it execute result");
            Console.WriteLine(" ");
            Console.WriteLine("1.Default call(no change path)");
            Console.WriteLine("2.Default call with argument string");
            Console.WriteLine("3.Change execution environment path");
            Console.WriteLine("4.Change execution environment path with argument string");
            Console.Write("\r\n >> ");
            var key = Console.ReadKey(true);
            if (key.KeyChar == '1')
            {
                //第一種
                Process.Start(fileName);
            }
            else if(key.KeyChar == '2')
            {
                //第二種
                Console.WriteLine("\r\n Input string (separated by space cahracter and Enter to be end): ");
                Console.Write("\r\n >> ");
                string argStr = Console.ReadLine();
                Process.Start(fileName, argStr);
            }
            else if (key.KeyChar == '3')
            {
                //第三種
                string path = Path.GetDirectoryName(fileName);
                string file = Path.GetFileName(fileName);
                var startInfo = new ProcessStartInfo();
                startInfo.WorkingDirectory = path;
                startInfo.FileName = file;
                Process.Start(startInfo);
            }
            else if (key.KeyChar == '4')
            {
                //第四種
                Console.WriteLine("\r\n Input string (separated by space cahracter and Enter to be end): ");
                Console.Write("\r\n >> ");
                string argStr = Console.ReadLine();

                string path = Path.GetDirectoryName(fileName);
                string file = Path.GetFileName(fileName);
                var startInfo = new ProcessStartInfo();
                startInfo.WorkingDirectory = path;
                startInfo.FileName = file;
                startInfo.Arguments = argStr;
                Process.Start(startInfo);
            }
            Console.WriteLine(" ");
            Console.WriteLine("Press Enter to Close ....");
            Console.ReadLine();
        }
    }
}


※其實上面一堆 Console.WriteLine 是可以簡化啦,只是懶惰了點都是搞複製/貼上所以才這麼多.....

執行結果:
第一種,基本用法 Process.Start(fileName),不帶參數,直接呼叫執行檔。
可以看到,被呼叫的 HelloConsole.exe 無法讀到跟它放在一起的 Doc.txt,從觀察中不難發現 HelloConsole.exe 的 Environment.CurrentDirectory 卻出現了 D:\MYAPP ??

也就是說 HelloConsole.exe 的執行環境變成呼叫者 CallOtherProgram.exe 的執行環境了。

第二種,基本用法的延伸,附帶參數 Process.Start(fileName, argStr) ,看得出來狀況跟第一種相同,無法讀取 Doc.txt ,但卻可以正確接收到參數傳入。

第三種,使用 ProcessStartInfo 傳入相關的參數值給 Process.Start
Process.Start(startInfo),利用這種方式要求 Process 建立自己的環境位置,
有了 startInfo.WorkingDirectory = path 這樣的設置,可以讓 HelloConsole.exe 在指定的路徑下被執行,因此可以正確讓  HelloConsole.exe 找到 Doc.txt 並顯示。

第四種,把第三種呼叫加入傳遞參數,當然這參數不適合使用第二種方法呼叫,而是把參數設定在 startInfo.Arguments 裡面,再以 Process.Start(startInfo) 方式執行,這樣 HelloConsole.exe  可以正確讀取 Doc.txt ,並且可以接收參數。


當然,最好處理方式是在 HelloConsole.exe 裡面讀取 Doc.txt 指定位置,當然可以使用 AppDomain.CurrentDomain.BaseDirectory 來強制取得執行程式路徑,只是實際使用時,可能也有需要在呼叫端的路徑下處理檔案。

只能說,程式寫法很自由,使用上要很小心,先確認開發的程式可能的執行方式,包含被呼叫執行(像是 Windows 工作排程就是一種呼叫端程式)的狀況下,也能正確執行,才是上上策。







留言