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