一个COM调用时出现的错误及解决办法
这两天一直在用COM里的复合文档来做个东东。我通过C#的平台调用方法使用IStorage和IStream这些接口的,并写了一个流类来包装IStream。在“调试”模式下,运行得很好,但是在“Release”下,则报错了!
症状为:
在长时间读写一个复合文档时,开始是可以正常运行的,但是一段时间后,就会报错
说“试图使用一个不存在的对象异常来自HRESULT 0x800030100 STG_E_REVERTED ” 。
而且问题是在Debug模式下,根本不出现这个问题,问题只出现在Release模式下。
开始我以为是和COM平台调用时的问题,看遍了MSDN和Google上能找到的相关资料,都没有提到类似的错误。没办法,我只能一段代码一段代码来查找原因。考虑到错误是引用了一个不存在COM对象,那么有可能是COM中对象计数器的问题,我想是不是什么的代码导致释放了引用COM对象的变量。
整整花了近六个小时,我才发现我犯了一个基本错误。我在包装类里写一个析构函数
~StorageStream()
{
this.Dispose();
}
而我的主程序源代码比较长,不贴出来了,大概如下面代码
1 Storage storage = Storage.CreateStorageFile(...);
2 StorageStream streamA = storage.CreateStream(...);
3 StorageStream streamB = storage.CreateStream(...);
4 for(int i =0;i < VeryLargeNum;i++)
5 {
6 streamA.Dosomething();
7 streamB.Dosomething();
8 }
9 streamA.Close();
10 streamB.Close(); // 这一句没写
11 storage.Close();
其中第8句没有写上去,编译时很正常,在“Debug”模式下也很正常,因为GC没有对streamB进行回收,而“Release”模式下,GC工作了,把streamB回收了,因此就导致引用了不存在的对象。
找到问题就好办了,加上Close(); 删掉析构函数,并在Dispose函数里加上一句
GC.KeepAlive(_stream);
一切搞定。
总结: GC的垃圾收集模式在“Debug”和“Release”模式下是不同的,因此常常可以看到很多人发贴子说“为什么我调试下可以运行,发布状态下则报错”,这个十有八九是GC搞的鬼。另外GC在判断是否可以收集一个对象时,它是根据后面还有没有代码引用这个对象,如果有的话它就不会收集,否则它会将其进行收集。为什么要加一个GC.KeepAlive是因为GC对于COM对象等非托管对象是不知情的,因此如果你将对象暴露给COM使用,就需要用到它。