Windows平台Go調用DLL的坑(居然有這麼多沒聽過的名詞)

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

最近的項目中,使用了GO來開發一些服務中轉程式。業務比較簡單,但是有一些業務需要複用原有C++開發的代碼。而在WINDOWS,用CGO方式來整合C/C++代碼並不是太方便。所以用DLL把C++的代碼封裝起來,然後提供基本的API來完成複用。在這個過程中遇到了一些問題及解決方案,記錄下來,也給遇到類似或者同樣問題的人一個借鑒。

如果你還不清楚怎麼在GO中調用DLL,可以參考這篇文章《WindowDLLs》。

Callback的限制

在WINDOWS下調用一些API時會要求傳入回呼函數,在C/C++下使用非常簡單,直接傳入函數指標就可以了。但是在GO這種有GC特性,又有執行階段程式庫的語言要稍微麻煩一點。

GO為瞭解決這種回調要求,在syscall包裡提供了NewCallback和NewCallbackCDecl兩個函數來協助使用者解決回調的問題。具體的Callback機制這裡先不說了,只是說一下在GO裡,Callback的使用是有限制的。而且對傳入的GO函數也是有對應的要求。在我看的go1.4正式版本中,src/runtime/syscall_windows.go line 71:會檢查callback的數量是否超過了最大的限制值,這個限制值目前是2000。如果超過就會拋出一個異常。這個是非常蛋疼的事情。而且從GO的ISSUE庫裡,這個問題的解決是一推再推。

GO的ISSUE裡關於CALLBACK的事情已經很明確了,就是要讓大家複用CALLBACK,也就是用全域函數來搞。在golang-china討論群組裡,minux給予了這樣的回答:

我說一下callback上限的原因。由於系統調用callback函數的時候不提供任何其他的參數,導致區分不同的callback只能通過被調用函數的地址,也就是說,一個Go的callback函數必須對應一個單獨的C函數地址

舊的機制是對每個Go函數,在堆上動態構造一個對應的C函數。這樣在 Go 1.1 之前的時候沒問題,因為當時函數閉包也需要可執行檔堆,但是 1.1 修改了函數的表示方式,閉包不再需要動態代碼產生了,為了把 Windows 上也不再需要可執行檔堆,必須想另外一個辦法來實現 callback,新的機制參加 issue 5494,是我提議的。既然不動態產生代碼,很明顯的一個問題就是 callback 的總數會有一個上限。這是這有任何辦法的了。

其實一般的 Windows 程式也不會有那麼多 callback,2000個絕對是綽綽有餘的;之所以 Go 這裡很容易用光,是因為可能大家願意使用 closure,而不是一個全域函數做 callback,使用全域函數做 callback (C/C++程式就是這麼做的),2000個絕對是綽綽有餘;但是由於閉包每次建立都是不同的,就算你其實就一個地方需要建立 callback,用閉包的話2000次就用光了所有的 callback。使用 callback 的時候建議用全域函數,也不要用 method,因為那樣也是閉包,2000 個絕對足夠足夠。

所以如果你要使用CALLBACK的時候,盡量的想辦法複用,否則會很囧。

棧溢出(0xC00000FD, _chkstk)

這個問題比較囧。我封裝的DLL裡用到了另外同事寫的代碼,他的風格是把函數寫的巨大,在C++下運行一切正常,但是當在GO寫的程式裡調用的時候,內部拋出了0xC00000FD的錯誤,也就是棧溢出。

發生的地方是_chkstk。這個函數是VS下的C++編譯器在代碼產生的時候加進去的,所以別想著通過參數啊什麼的幹掉了。這個函數的作用是說當你的函數內有超過了一頁大小的變數時,編譯器會把在函數頭插入對_chkstk的調用代碼,_chkstk會檢查棧的大小是否足夠該函數的局部變數使用,如果不夠,它會訪問棧的GUARD PAGE,然後會觸發系統核心檢查到該錯誤,這個時候作業系統會擴充棧的大小。首先這裡就有矛盾了,GO語言本身會擴充棧,而為了做到這點就要把堆拿來當棧使用,但是這個時候的問題是,它和OS預設的棧空間不同,導致核心檢查到的錯誤是不可擴充的,這個時候OS也搞不定了,只能讓程式掛掉了。而且OS本身支援的棧擴充是有限制的,不像GO實現的棧擴充,WINDOWS下這個擴充最終會觸發到一個無法申請的棧地址,如果無法擴充棧,還是要讓程式掛掉。可以點這裡《What is the purpose of the _chkstk() function》查看更詳細的解釋。所以問題的本質就是局部變數太大(自己反組譯碼DLL發現確實有個物件變數體積巨大...),解決方案就是該把變數該放到堆裡的放到堆裡的就放到堆裡。最後的效果是,再也不拋出這個0xC00000FD的異常了。

感想

其實上面用到的C++代碼,可以用GO直接重寫的,但是考慮到時間成本,最後還是放棄了。CALLBACK的問題我是暫時重寫了對應的API代碼來搞定。

GO在寫網路通訊,並發情境時比較嗨皮,但是這種跨語言的互動操作實在是太蛋疼。只能且行且蛋疼了

 

Update:

這個問題後來被我提交給GO的Team Dev了。具體詳情可以見這裡:https://github.com/golang/go/issues/9457

按照minux的說法就是需要在使用DLL時的,匯入runtime/cgo即可。是說編譯器編譯的時候,發現沒有使用CGO的話,就會把系統線程的棧大小設定為64KB,而且是不擴充的;但是如果使用 了CGO,就會變大預設棧,同時變為可擴充的。

最後還是得吐槽下,What the fuck!對於GO的這種使用方法真心不喜歡,實在是太蛋疼。CGO方式還要依賴GCC之類的玩意。WINDOWS下想愉快的玩耍根本不是太可能。改天再說下用GO來的感受。

http://www.cnblogs.com/concurrency/p/4170657.html

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.