C# com

最近接元大期貨的API,提供的是古老的COM元件、ActiveX控制項,目標是在Console project下包成grpc service。開啟了一系列的採坑遊記,比起其他家真是”特殊”很多…

照著example寫了一陣子,編譯後執行發現會噴找不到COM元件的錯誤,跟之前寫過Interop概念類似,加入參考以後,會變成用Interop的方式呼叫COM元件,結果是要特別選擇使用x86還是x64編譯專案,如果選擇Any CPU,不管x86還是x64的dll都沒辦法執行。

過了這關以後發現有點不對勁,function呼叫後,註冊的callback都沒有打回來過,把example編譯執行後發現運作正常,因為example有點古老,一開始以為跟.net版本有關係。從4.8試到2.0都沒有作用。後來想到會不會跟專案類型有關係,選擇了4.8的WinForm專案,果然就會動了,傻眼..該不會裡面偷偷用到WinForm的東西。

最後猛然一看發現,WinForm好像跟Console的程式進入點有些不一樣,WinForm是用STAThread啟動的..突然想到WinForm那套單執行緒和Invoke才能呼叫的作法。於是跑去Google COM跟STAThread有什麼關係,結果發現有一些古代COM需要在STAThread下執行,如果用MTAThread,.net會自動幫忙包裝並偷偷建Wrapper呼叫。雖然function可以呼叫,但是callback就不會回來..老實說我也不知道為什麼,基本在網路上查不到相關資訊。

如果堅持要用Console專案的話,要自己建立一個Thread,並且用設定成STAThread,在裡面呼叫COM提供的function才能正確運作。因為.net會”偷偷”幫你做初始化,如果在這個Thread之外呼叫了根據spec表示會發生各種奇怪的事。所以需要在Thread裡面實作task queue,把function丟進這個Thread執行。

但是這樣做了之後還是沒辦法運作,因為STAThread有message pump的概念,也就是沒有主動處理message的話,callback還是不會回來,腦殘的解法是不斷呼叫DoEvents()…。這個解法真的太智障了,我不能接受。自己處理的方法是大概這麼做…之前寫winsock程式的時候就體會過類似的東西,很快就搞定了,自己呼叫winapi

while(GetMessage(...))
{
  TranslateMessage(...);
  DispatchMessage(...);
}

當然這樣是最愚蠢的blocking wait,實際上要用PeekMessage()防止程式永遠在等訊息,而不會消耗task queue,變成無限迴圈,另外還需要MsgWaitForMultipleObjects(),避免程式瘋狂的呼叫PeekMessage(),變成busy loop。

最後記得new COM元件的時候也要在同樣的STAThread裡面,原因一樣是.net會偷偷的幫你做一些事..類似CoInitialize()之類的東西。

仔細研究完這個古董實在是太浪費人生了,2019年了誰還在用COM元件,許多地方得過且過,會動就好。