經過大量的實驗和研究,我知道發生了什麼。這是Excel實施的一個不幸的副作用,也是本機Windows功能ShellExecuteEx
(其中System.Diagnostics.Process
使用)的工作方式。特別是,直到auto_run
宏已完成,並在Process
類使用它的方式調用時ShellExecuteEx
不會返回,直到出現這種情況或(非常重要)到2分鐘以上的通過Excel將不承認DDE命令完成ShellExecuteEx
。
(文檔說有一分鐘的暫停,但在我的Windows 8.1的機器上是兩分鐘)。
我發現一些變通,但沒有完全優雅所有這一切,但應該工作正常。
注:下面所有的代碼示例將被放置在點擊事件處理程序中,除非另有說明,否則(即互操作聲明)。
我的首選解決方法是簡單地使用單獨的線程來啓動過程。這並不能解決流程本身的問題。但這一過程的其餘部分可以關閉,留下孤獨的線程等待超時(或auto_open
完成,以先到者爲準):
Thread thread = new Thread(() => Process.Start(target));
thread.IsBackground = false;
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
一個在出現的問題時,ShellExecuteEx
沒有按」 t等待的是,Windows實際上需要您的STA線程掛起足夠長的時間,以便它將DDE命令發送到Excel以打開給定文件。這意味着任何試圖繞過ShellExecuteEx
延遲的嘗試都會導致Excel無法啓動,或者不打開請求的文件。也就是說,如果你願意接受這種風險,或者用更長的超時時間來緩解風險(但不一定只要Windows強加兩分鐘超時),那麼你可以採取其他一些方法。
第二種方法是排隊一個Close()
呼叫以供以後執行。這利用了ShellExecuteEx
仍然在運行消息泵的事實,因此即使Process.Start()
方法尚未返回,仍然可以在您的Form
子類中獲取代碼。這方面的一個例子是:
BeginInvoke((Action)(async() =>
{
await Task.Delay(1000);
Close();
}));
Process.Start(target);
這1秒,這在我的電腦上是足夠長的時間讓Process
和ShellExecuteEx
做必要得到運行Excel工作延遲Close()
命令。
注:我確實嘗試了更短的超時時間,並發現它不可靠。也就是說,在100ms而不是1000ms時,Excel根本沒有啓動。在500毫秒時,它開始但通常不會實際加載工作簿。整整一秒,我的SSD配備的筆記本電腦就可靠了。我實際上並不知道延遲是在哪裏,但可能是在驅動器較慢的計算機上,需要更長的超時時間。
我不喜歡上述的一件事是它在Process.Start()
方法實際返回之前將應用程序關閉。雖然它起作用,但這似乎有點過於「猶太教/非猶太教」的路線。 :)
因此,第三個選項是完全繞過Process
類,直接調用ShellExecuteEx
。這樣做,你仍然需要等待,否則Excel將無法可靠地啓動。但是你可以在之後等待完成ShellExecuteEx
的呼叫,因此應用程序清理對我來說似乎更清潔。也就是說,這是一個完全正常的程序退出,允許您可能想要做的所有常規內務。
它的互操作聲明的時間長一點到期,但它工作得很好:
SHELLEXECUTEINFO sei = new SHELLEXECUTEINFO();
sei.fMask = ShellExecuteMaskFlags.SEE_MASK_FLAG_NO_UI;
sei.nShow = ShowCommands.SW_NORMAL;
sei.lpFile = target;
if (!Interop.ShellExecuteEx(sei))
{
int hr = Marshal.GetLastWin32Error();
Exception e = Marshal.GetExceptionForHR(hr);
// Throw, display message box, whatever you like here
}
await Task.Delay(100);
Close();
在互操作的聲明是這樣的(未使用的枚舉值省略):
class Interop
{
[DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool ShellExecuteEx(SHELLEXECUTEINFO lpExecInfo);
}
[StructLayout(LayoutKind.Sequential)]
public class SHELLEXECUTEINFO
{
public int cbSize;
public ShellExecuteMaskFlags fMask;
public IntPtr hwnd;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpVerb;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpFile;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpParameters;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpDirectory;
public ShowCommands nShow;
public IntPtr hInstApp;
public IntPtr lpIDList;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpClass;
public IntPtr hkeyClass;
public uint dwHotKey;
public IntPtr hIcon;
public IntPtr hProcess;
public SHELLEXECUTEINFO()
{
this.cbSize = Marshal.SizeOf(this);
}
}
public enum ShowCommands : int
{
SW_NORMAL = 1,
}
[Flags]
public enum ShellExecuteMaskFlags : uint
{
SEE_MASK_FLAG_NO_UI = 0x00000400,
}
使用這項技術,我能夠使用更短的超時時間。 100毫秒似乎可靠地工作,而10毫秒沒有。
最後請注意,如果您可以更改Excel工作簿,則應該可以在auto_open
例程中設置一個計時器,該例程稍後將運行實際初始化代碼,讓auto_open
立即返回。這樣做會否定任何需要在C#程序中調用啓動代碼的需要。 :)
請顯示您的代碼。見http://stackoverflow.com/help/mcve – 2014-11-05 16:56:54
@PeterDuniho完成。希望這可以幫助你。 – 2014-11-05 17:06:18
嘗試System.Windows.Forms.Application.Exit() – 2014-11-05 17:23:14