同事報案,在 Visual Studio 從私有 NuGet 伺服器安裝我寫的共用元件,該元件參照了 Managed ODP.NET 但沒在 NuGet Package 宣告相依性,理論上不會一併安裝 Managed ODP.NET NuGet Package,但同事發現建置後 bin 目錄卻神奇地出現 Oracle.ManagedDataAccess.dll。試著在我的電腦演練相同操作,bin 目錄並不會出現 Oracle.ManagedDataAccess.dll!很明顯這又是我不了解的「魔法」,啟動調查,試著找出 Visual Studio 自動帶出相關 DLL 的原理。
起初我懐疑有某個聰明的外掛套件從中幫忙,自動補齊該共用元件需要的第三方程式庫,Visual Studio 改用安全模式也是同樣結果,不在場證明 GET,無保請回。
接著我懐疑跟註冊 GAC 有關,推測同事的 Managed ODP.NET 有註冊 GAC,Visual Studio 能成功找到 DLL 寫入 bin,我的沒註冊 GAC 不知去哪裡找 DLL,補不了檔案。
不料,檢查 GAC C:\Windows\assembly\GAC_MSIL\Oracle.ManagedDataAccess 資料夾的結果出乎意料,我的機器有同事沒有,意味我有註冊 GAC 而同事沒有,跟前面的推論完全相反啊啊啊~(啪!我聽到清脆的打臉聲,Orz)
別怕,拎杯機靈勝過尚書大人,立刻找到合理新解釋:因為我的 Managed ODP.NET 已註冊 GAC,所以不需要複製到 bin!(咳,「事前信心十足大膽預測,事後又能娓娓道來為何失準」是「偽專家」的必要技能,我真不愧是見過大風大浪的老屁股呀)
但這裡有個問題:如果同事沒有註冊 Managed ODP.NET,Visual Studio 是怎麼找到 Oracle.ManagedDataAccess.dll 放進 bin 裡?
這類疑難雜症,交給茶包一哥 Process Monitor就對了!
開啟 Process Monitor 監控專案建置過程的 Registry 與檔案存取,我學到一件事:建置作業由 MSBuild.exe 負責,它會先搜尋共用元件所在目錄看 Oracle.ManagedDataAccess.dll 有沒有跟它放在一起(在本例為 NuGet Packages 目錄,但我沒有包進去),若沒找到 MSBuild 會接著尋找 HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319\AssemblyFoldersEx\ Regitry,同事在該機碼下有個 Oracle.ManagedDataAccess 指向 Oracle.ManagedDataAccess.dll,MSBuild 就靠著它找到 DLL 並複製到 bin 目錄下。
[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319\AssemblyFoldersEx\Oracle.ManagedDataAccess]
@="E:\\Oracle\\odp.net\\managed\\common\\Oracle.ManagedDataAccess.dll"
(我的電腦在 AssemblyFoldersEx 也有機碼指向 Managed ODP.NET,但名稱不同,叫 odp.net.managed,如下圖)
補充:AssemblyFoldersEx 是 Visual Studio/MSBuild 尋找第三方元件的依據,參考:如何在32/64bit環境讓Visual Studio加入參考時可在.NET頁籤瀏覽自己的元件。
AssemblyFoldersEx 機碼可解釋為何同事沒註冊 GAC 也可以找到 Managed ODP.NET DLL,下一步是驗證 GAC 是否與 DLL 會不會複製到 bin 有關?用 gacutil /u Oracle.ManagedDataAccess.dll 將其從 GAC 移除,再測試我的電腦 bin 還是沒出現 Oracle.ManagedDataAccess.dll,代表「有註冊 GAC 所以不用複製到 bin」的推測又錯了…(啪!啪!)
最後,老老實實比對同事跟我的 Process Monitor 記錄,同事的狀況是從 AssemblyFoldersEx 查到路徑找到檔案就複製到 bin 下(如下圖),我則是 AssemblyFoldersEx 找到路徑尋獲 DLL 檔,又繼續去 GAC \Windows\assembly\GAC_MSIL\Oracle.ManagedDataAccess 尋找 DLL 檔,重點是都有找到檔案,但就是沒將檔案複製到 bin。
這讓我有個想法:莫非問題出在版本不符?檢查共用元件參照的 Managed ODP.NET 版本為 4.121.2.0,同事 AssemblyFoldersEx\Oracle.ManagedDataAccess Registry 指向的版本也是 4.121.2.0,
而我的 AssemblyFoldersEx 與 GAC 指向的版本則是 4.121.1.0,並不是共用元件要求的版本。
BINGO!
將 AssemblyFoldersEx 指向位置的版本換成 4.121.2.0,我的電腦編譯後 bin 就出現 Oracle.ManagedDataAccess.dll 了,全案宣告偵破!
歸納本案調查心得:
- Visual Studio 建置背後靠 MSBuild 完成
- MSBuild 與 Visual Studio 會藉由 SOFTWARE\Microsoft\.NETFramework\v4.0.30319\AssemblyFoldersEx\ Registry 尋找第三方程式庫
- 若發現某 DLL 參照其他程式庫,MSBuild 會試著依 DLL 所在目錄、AssemblyFoldersEx、GAC 順序找尋所參照 DLL 並複製到 bin 目錄下
- 找尋 DLL 時版本必須完全一致,否則視同沒找到,既不複製也不會有錯誤訊息
【後話】
MSBuild 解析參照組件過程感覺有很多學問,想找篇深入探討文件解惑。在 MSDN 部落格上看到一篇序文如獲至寶,作者提到計劃寫完一系列共六篇文章深入剖析 MSBuild 及 Visual Studio 如何解析尋找 Assembly,讓我滿心期待一探究竟,很遺憾,作者 2010 年 5 月發願後就沒了下文,徒留幾篇敲碗留言… Orz