在.NET 4中调用GDAL库时遇到的问题及解决方法

最近需要在 .NET 4 的环境中调用 GDAL 库。 GDAL 本身是一套非托管类库,不过还好提供了托管的 Wrapper 。

这些托管的程序集被包含在了 FWTools 的安装包中, FWTools 中带的版本依赖于 gdal_fw.dll, gdal_fw.dll
是 GDAL 核心类库的修改版,而它依赖的其他非托管程序集太多了,加起来有 18M 左右。所以还是自己下载代码编译的好。

这篇文章
介绍了 1.4 版本的编译方法,该方法同样适用于现在的 1.7 版本。

编译好之后引用、调用、 Debug 都没问题,一切正常,但是如果用 Release 编译并在 VS 之外运行的话则会报出
AccessViolationException ,异常信息提示说访问了受保护的内存。我的第一反应就是托管的 Wrapper 中用 P/Invoke
调用了非托管程序集,而非托管程序集导致了这个问题。但是这个猜测并不能解释为什么只有在 .NET 4+Release+IDE
外运行的情况下才会出错的现象。

猜来猜去,找来找去找到了问题的所在:

GDAL 的托管 Wrapper 中有一个叫做 SWIGStringHelper
的类型,该类型的静态构造方法中执行了一些比较重要的初始化操作。另外一个叫做 OsrPINVOKE 的类中声明了一个 SWIGStringHelper
类型的私有静态字段,并在声明时就初始化了该字段,而且 OsrPINVOKE 中没有显式声明的静态构造。

把代码简化一下的话,大概是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class OsrPINVOKE {
private static SWIGStringHelper helper = new SWIGStringHelper();

public static void DoSomething() {
Console.WriteLine("static method of OsrPINVOKE");
}
}

class SWIGStringHelper {
static SWIGStringHelper() {
//这里做了一些重要的初始化
Console.WriteLine("SWIGStringHelper static constructor ");
}
}

如果有代码调用 DoSomething ,我对这段代码执行顺序的估计是这样的:

OsrPINVOKE 的静态构造方法(里面初始化 helper 这个静态字段);

SWIGStringHelper 的静态构造方法(输出字符串);

SWIGStringHelper 的实例构造方法(里面啥也没有做);

DoSomething 方法(输出字符串)。

所以应该是先输出 SWIGStringHelper static constructor 而后输出 static method of OsrPINVOKE 。

试着这样调用一下:

1
2
3
4
static void Main(string[] args) {
OsrPINVOKE.DoSomething();
Console.ReadLine();
}

却发现如果用的 target framework 是 .net 4 ,用 release 编译并且在 VS 外运行的话,就会只输出
static method of OsrPINVOKE ,感觉好像 SWIGStringHelper 的静态构造方法没有执行。而如果用的是 .net
2.0 、 3.5 ,或者是用 Debug 编译或是在 VS 里面运行的话都不会有问题。

难道是静态字段的初始化在 .NET 4 中变成 Lazy 的了?

事实证明真的是这样。所以要解决这个问题的话只要在 OsrPINVOKE 里面显示声明一个静态构造方法,把 new SWIGStringHelper
(); 这一句放到里执行就 OK 了。

如果您在 .NET 4 中调用 GDAL 时遇到了类似的问题,不妨试一下。

参考:

http://social.microsoft.com/Forums/zh-CN/visualcshartzhchs/thread/2106ea8e-4889-45bf-82fd-55ab4b3e9aad

http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/d3fb3454-b982-4357-bb6b-63f7eceee69b/#96f5cc6b-a31b-4ae2-b2f4-40a99e7581af

感谢:

韦恩卑鄙

Nishant Sivakumar