『飞秋』在.NET 4中调用GDAL库时遇到的问题及解决方法
『飛秋』在.NET 4中調用GDAL庫時遇到的問題及解決方法
最近需要在.NET 4的環境中調用GDAL庫。GDAL本身是一套非托管類庫,不過還好提供了用SWIG做的托管的Wrapper。
可以在FWTools的安裝包中找到這些Wrapper的編譯好的dll文件,不過FWTools中附帶的版本依賴于gdal_fw.dll(gdal_fw.dll是GDAL核心類庫的修改版),而gdal_fw.dll依賴的其他非托管程序集太多了,加起來有18M左右。所以還是自己下載代碼編譯的好。
這篇文章介紹了1.4版本的下載和編譯方法,該方法同樣適用于現在最新的1.7版本。
編譯好之后引用、調用、Debug都沒問題,一切正常,但是如果用Release編譯并在VS之外運行的話則會報出AccessViolationException,異常信息提示說訪問了受保護的內存。我的第一反應就是托管的Wrapper中用P/Invoke調用了非托管程序集,而非托管程序集導致了這個問題。但是這個猜測并不能解釋為什么只有在.NET 4+Release+IDE外運行的情況下才會出錯的現象。
猜來猜去,找來找去找到了問題的所在:
GDAL的托管Wrapper中有一個叫做SWIGStringHelper的類型,該類型的靜態構造方法中執行了一些比較重要的初始化操作。另外一個叫做OsrPINVOKE的類中聲明了一個SWIGStringHelper類型的私有靜態字段,并在聲明時就new了該字段,而且OsrPINVOKE中沒有顯式聲明的靜態構造。
把這兩個類型的代碼簡化一下的話,大概是這樣的:
view sourceprint?01 class OsrPINVOKE?
02???? {?
03???????? private static SWIGStringHelper helper = newSWIGStringHelper();?
04????
05???????? public static void DoSomething()?
06???????? {?
07??????????? Console.WriteLine("static method of OsrPINVOKE");?
08???????? }?
09???? }?
10????
11???? class SWIGStringHelper?
12???? {?
13???????? static SWIGStringHelper()?
14???????? {?
15??????????? //這里做了一些重要的初始化?
16??????????? Console.WriteLine("SWIGStringHelper static constructor");?
17???????? }??????????????????????????????????????????
18 }
如果有代碼調用DoSomething,這段代碼執行順序估計是這樣的:
OsrPINVOKE的靜態構造方法(里面初始化helper這個靜態字段);
SWIGStringHelper的靜態構造方法(輸出字符串);
SWIGStringHelper的實例構造方法(里面啥也沒有做);
DoSomething方法(輸出字符串)。
所以應該是先輸出SWIGStringHelperstatic constructor而后輸出static method ofOsrPINVOKE。
試著用下面的代碼調用一下:
view sourceprint?1 static void Main(string[] args)?
2???????? {?
3???????????? OsrPINVOKE.DoSomething();?
4???????????? Console.ReadLine();?
5???????? }
卻發現如果用的target framework是.net4,用release編譯并且在VS外運行的話,就會只輸出static method of OsrPINVOKE,感覺好像SWIGStringHelper的靜態構造方法沒有執行。而如果用的是.net 2.0、3.5,或者是用Debug編譯或是在VS里面運行的話輸出結果都和預期的一致。
難道是靜態字段的初始化在.NET 4中變成Lazy的了?
事實證明真的是這樣:
如果一個類型提供了顯式聲明的靜態構造的話,那么這個靜態構造方法會在創建該類型實例或者訪問該類型的任何靜態成員之前被執行。
如果一個類型沒有提供顯式聲明的靜態構造的話,編譯器會自動給該類型一個默認的靜態構造,并把靜態字段的初始化都放到該默認靜態構造中去,而這個默認的靜態構造只會在靜態字段被訪問時才執行,也就是說創建實例、調用實例方法、調用靜態方法時都不會觸發靜態構造的執行(當然前提是它們沒有訪問靜態字段)。
不過CLR在加載一個類型時怎么知道其中包含的靜態構造方法是編譯器加上的還是原C#代碼中顯式提供的呢?事實上這是beforefieldinit的作用。
根據上面的原則再來分析一下:OsrPINVOKE中沒有顯式聲明的靜態構造,所以編譯器會生成一個默認的靜態構造并把helper的實例創建放入其中。而這個默認的靜態構造只會在helper這個唯一的靜態字段被訪問時才會執行。而代碼中沒有任何地方訪問了helper,所以OsrPINVOKE的靜態構造根本就沒有執行,helper根本就沒有被new出來,SWIGStringHelper的靜態構造自然也就沒有執行。
所以要解決這個問題的話只要在OsrPINVOKE里面顯式聲明一個靜態構造方法,把new SWIGStringHelper();這一句放到里面,或者僅僅是顯式聲明一個靜態構造并把它留空。然后重新編譯一下GDAL的Wrapper就可以了。
如果您也在.NET 4中調用GDAL時遇到了類似的問題,不妨試一下這種解決方法。
其實不僅是GDAL,其他由SWIG制作的托管Wrapper估計都會受到影響。
參考:飛秋官網:http://www.freeeim.com/
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的『飞秋』在.NET 4中调用GDAL库时遇到的问题及解决方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 3DSlicer3:模块管理(一)颜色、
- 下一篇: 飞鸽传书不能传送文件