释放 COM 引用
释放 COM 引用
概述
本主题提供有关 COM 和 .NET 的互操作性的信息,包括如何在这两个不同模型中管理内存。
AOUninitialize.Shutdown
当独立应用程序(stand-alone application)尝试关闭时,可能会发生意外的错误。 例如,当您从 ArcGIS Engine 应用程序退出时(该应用程序托管了带有已加载地图文档的 MapControl ) ,您可能会收到类似于以下内容的错误(此类错误超出了【代码中任何错误处理语句的】范围):
- The instruction x references memory at x. The memory could not be read(指令 x 引用 x 处的内存。 无法读取内存)。
当 COM 对象在内存中的停留时间超过预期时,可能会发生这些错误,从而在进程关闭时,阻止从进程上正确卸载掉 COM 库。 为了帮助防止这些错误,已将静态 Shutdown 函数添加到 ESRI.ArcGIS.ADF.Local 程序集中。 此函数会在进程关闭之前卸载掉未使用的 COM 引用,以确保避免发生这些错误。
以下代码示例,显示了如何在窗口的 Disposed 方法中使用此函数:
[C#]
private void Form1_Disposed(object sender, System.EventArgs e)
{
ESRI.ArcGIS.ADF.COMSupport.AOUninitialize.Shutdown();
}
[VB.NET]
Private Sub Form1_Disposed(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Disposed
ESRI.ArcGIS.ADF.COMSupport.AOUninitialize.Shutdown()
End Sub
此函数仅在卸载【尚不存在 COM 对象的】库时有帮助。 在处理完任何 COM 对象后,应用此功能,更为恰当。 例如,在具有启动窗口的 ArcGIS Engine for Windows 应用程序中,把对 AOUninitialize.Shutdown 的调用,置于 Form Disposed 事件的处理程序中。
ComReleaser 类
AOUninitialize.Shutdown 函数处理独立应用程序( stand-alone applications)中的许多关闭问题,特别是与包含引擎控件(Engine controls)的应用程序相关的问题。 但是,您仍然会遇到【COM 对象保留在应用程序的内存中】并且【需要从内存中显式释放】的问题。
为确保 COM 对象能在超出作用范围时被释放,请使用 ComReleaser 类。 ComReleaser 类位于 ESRI.ArcGIS.ADF.Connection.Local.dll 程序集中。 您可以使用此类,来管理 COM 对象。因为在您的代码终止时,它有助于确保释放您的 COM 对象引用。 有关使用此类的更多信息,请参阅 ESRI.ArcGIS.ADF.Connection.Local。
在内部,此类(ComReleaser)使用了 System.Runtime.InteropServices.Marshal 类上的 ReleaseCOMObject 方法,来确保应用程序【对 COM 对象引用】的释放。 需要明白的是,这仅在【托管代码不再需要的】对象上调用。 有关详细信息,请参阅本主题中的 Marshal.ReleaseComObject 部分,和 MSDN 网站上 的 Marshal.ReleaseComObject方法 。
Marshal.ReleaseComObject
在 .NET 代码中,由运行时 RCW 来保存【对 COM 对象的引用】。RCW 是一个托管对象,作为代理对象,来充当底层 COM 对象。 .NET 垃圾收集器,不定时地从内存中清除托管对象。在 COM 对象持有系统资源(文件句柄、数据库连接等)的情况下,您可能需要显式释放这些 COM 对象,以释放 COM 对象持有的资源。
根据具体情况,这些问题可能会导致不同类型的错误。例如,针对个人地理数据库,重复打开地理数据库游标,而未释放最后一个游标,可能会导致【无法打开更多表的错误】(no more tables can be opened)。又例如,在某些情况下,应用程序关闭时可能会出现错误消息,因为对象引用仍保留在内存中。 StyleGallery 就是这样的类,如果您不显式释放它,可能会导致退出错误。
要从内存中完全释放 RCW 下的 COM 对象,可以在 Marshal 类上使用 ReleaseComObject 方法。该类是 .NET Framework 中 System.Runtime.InteropServices 命名空间的一部分。 调用 ReleaseComObject 方法会减少 RCW 上的引用计数。 一旦 RCW 上的引用计数达到零(这可能需要重复调用 ReleaseComObject 方法),RCW 就会被标记为垃圾回收。 如果此时没有其他 COM 对象持有该 COM 对象的引用,那么,COM 运行时会清除该 COM 对象。
调用 ReleaseComObject 会影响当前进程中所有对【这一 COM 对象】的托管引用。 因此,调用此方法时,要特别小心。 在可能被另一个托管组件引用的情况下,不要对 COM 对象调用 ReleaseComObject 方法。
例如,如果您存储了对 MxDocument 的引用,并在该引用上调用 ReleaseComObject 方法,则(在相同托管进程内)任何其他组件都无法访问这个 MxDocument 对象。 如果您正在创建一个独立的应用程序,并具有控制该应用程序中所有托管代码的优势,那么您可以更准确地确定何时可以释放 COM 对象。
下面的代码示例,展示了如何调用 ReleaseComObject 方法来释放 StyleGallery 对象。 代码递归调用 ReleaseComObject 方法, 直到返回值为零。 这表明,不再有任何对 StyleGallery 的托管引用。请在当您确定没有其他托管代码需要进一步访问该对象(StyleGallery 对象)时,再使用这样的代码。
[C#]
private void MyFunction()
{
ESRI.ArcGIS.Display.IStyleGallery styCls=new
ESRI.ArcGIS.Framework.StyleGalleryClass() as
ESRI.ArcGIS.Display.IStyleGallery;
// Use the StyleGalleryClass here.
int refsLeft=0;
do
{
refsLeft=Marshal.ReleaseComObject(styCls);
}
while (refsLeft > 0);
}
[VB.NET]
Sub Main()
Dim styCls As ESRI.ArcGIS.Display.IStyleGallery=New ESRI.ArcGIS.Framework.StyleGalleryClass
' Use the StyleGalleryClass here.
Dim refsLeft As Integer=0
Do
refsLeft=System.Runtime.InteropServices.Marshal.ReleaseComObject(styCls)
Loop While (refsLeft > 0)
End Sub
为了确保及时释放对象,您还可以在循环体中,在创建的 COM 对象上,调用 ReleaseComObject,而不是等待垃圾收集机制来执行清理。 这在处理光标对象和其他包含资源的地理数据库对象,以及样式库枚举器和项目对象时很有用。 有关详细信息,请参阅本主题中的 释放地理数据库游标 部分。
有关垃圾收集过程的详细信息,请参阅 MSDN 网站上的 Garbage Collection 。
释放地理数据库游标
某些 COM 对象仅在其自身的析构函数中释放资源。
例如,在 Esri 库中,一个地理数据库游标(geodatabase cursor),可以在基于文件的”要素类或表“上,获取【共享模式锁(shared schema lock )】;或者它(这个游标)保留(hold on) SDE 流(SDE,Spatial Database Engine,空间数据库引擎)。
当【共享模式锁】到位时,其他应用程序虽然可以继续查询或更新”要素类或表“中的行,但已不能删除”要素类或表“,已不能修改它们的模式(schema)。
对于基于文件的数据源(例如 shapefile),更新游标(update cursors)会在文件上获取到一个【具有排他性的】写锁。这会阻止其他应用程序读写文件(连读取文件也不可以了)。 这些锁的作用是,在所有引用(游标对象上的)都被释放之前,其他应用程序可能无法使用数据。
在 SDE 数据源的情况下,游标保留在 SDE 流上。如果应用程序有多个客户端,每个客户端都可以获取并保留在 SDE 流上,直至最终用尽最大允许量。 SDE 流数超过最大值的影响是,其他客户端无法再打开游标而查询数据库。在某些情况下,服务器可能会耗尽其可用的内存。
在 .NET 中,在垃圾回收发生之前,您对游标(或任何其他 COM 对象)的引用不会被释放;只有到了垃圾回收之时,资源才会被释放。因此,当您使用那些会锁定数据库资源的对象来执行大量操作时,您可能就会发现,这些数据库资源可以累积到【数据源的最大允许限制】。此时,错误可能因数据源的不同而有所不同。例如,访问个人地理数据库表,仅允许 255 个表连接,超过时会提示缺少资源。在其他情况下,异常可能与缺乏资源没有明显关系。
虽然调用 GC.Collect(启动垃圾收集的 .NET 方法),可用于清除任何挂起的对象。 但是,不要进行强制垃圾收集,因为调用可能很慢,并且强制收集也会干扰垃【圾收集器进行最佳的操作】。
正确的做法是使用 Marshal.ReleaseComObject 释放那些持有资源的对象。 通常,您应该始终以这种方式释放游标对象(例如,实现 IFeatureCursor 的对象)。 您还应释放 【实现 ISet 接口的对象】以及地理数据库枚举器。 再次声明,任何需要从 .NET 代码中的其他位置访问的对象,不应被释放。例如,不是您在代码中创建的【需要长期存在的】对象。
在为多个并发会话和请求提供服务的 Web 应用程序或 Web 服务中,依靠垃圾收集来释放【对 COM 对象的引用】,会导致游标及其持有的资源无法得到及时释放的错误。 同样,在 ArcGIS for Desktop 或 ArcGIS Engine 应用程序中,依靠垃圾收集来释放这些引用,可能会导致您的应用程序或其他应用程序接收错误。
ArcGIS Desktop and ArcGIS Engine
如果您在代码中使用地理数据库游标,则可以在不再需要游标对象时,调用 Marshal.ReleaseComObject ,以确保及时释放相应的地理数据库资源。 以下代码示例演示了这种模式:
[C#]
for (int i=1; i < 2500; i++)
{
IQueryFilter qu=New QueryFilterClass();
qu.WhereClause=@"Area=" + i.ToString();
IFeatureCursor featCursor=featClass.Search(qu, true);
// Use the feature cursor as required.
System.Runtime.InteropServices.Marshal.ReleaseComObject(featCursor);
}
[VB.NET]
For i As Integer=1 To 2500
Dim qu As IQueryFilter=New QueryFilterClass
qu.WhereClause="Area=" & i.ToString()
Dim featCursor As IFeatureCursor=featClass.Search(qu, True)
' Use the feature cursor as required.
System.Runtime.InteropServices.Marshal.ReleaseComObject(featCursor)
Next i
ArcGIS Engine
ComReleaser 类在处理 ArcGIS Engine 中的单例时起着重要作用。如前所述,COM 对象在使用后长时间留在内存中,可能会导致关机时出现问题和错误。单例是这样一种 COM 对象,即每个进程只有一个它的实例。如果单例被挂起,您可能会发现您的代码导致了错误。
在用到单例的情况下,不管使用的是什么样的 API,都必须始终释放它的引用。对于非 .NET API,通过将成员变量设置为空来释放 COM 对象。对于 .NET API,使用前面所述的 ComReleaser 类来释放单例 COM 对象。有关 ArcGIS Engine 中使用的特定单例的详细信息,请参阅 Using the control commands 中的“单例对象”部分.
ArcGIS for Server
为确保 COM 对象在超出范围时被释放,WebControls 程序集包含一个名为 WebObject 的帮助器对象。 使用 ManageLifetime 方法,将您的 COM 对象添加到一个对象集合中,在 WebObject 被释放时,显式释放对象集合中的 COM 对象。 您必须在 using 块内限定 WebObject 的使用范围,以便您使用 ManageLifetime 方法。那些添加到 WebObject 的任何对象(包括您的光标),都将在 using 块的末尾处,被显式释放。
以下代码示例,演示了这种模式:
[C#]
private void doSomthing_Click(object sender, System.EventArgs e)
{
using(WebObject webobj=new WebObject())
{
ServerConnection serverConn=new ServerConnection("doug", true);
IServerObjectManager som=serverConn.ServerObjectManager;
IServerContext ctx=som.CreateServerContext("Yellowstone", "MapServer");
IMapServer mapsrv=ctx.ServerObject as IMapServer;
IMapServerObjects mapo=mapsrv as IMapServerObjects;
IMap map=mapo.get_Map(mapsrv.DefaultMapName);
IFeatureLayer flayer=map.get_Layer(0)as IFeatureLayer;
IFeatureClass fclass=flayer.FeatureClass;
IFeatureCursor fcursor=fclass.Search(null, true);
webobj.ManageLifetime(fcursor);
IFeature f=null;
while ((f=fcursor.NextFeature()) != null)
{
// Do something with the feature.
}
ctx.ReleaseContext();
}
}
[VB.NET]
Private SubSystem.Object doSomething_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles doSomething.Click
Dim webobj As WebObject=New WebObject
Dim ctx As IServerContext=Nothing
Try
Dim serverConn As ServerConnection=New ServerConnection("doug", True)
Dim som As IServerObjectManager=serverConn.ServerObjectManager
ctx=som.CreateServerContext("Yellowstone", "MapServer")
Dim mapsrv As IMapServer=ctx.ServerObject
Dim mapo As IMapServerObjects=mapsrv
Dim map As IMap=mapo.Map(mapsrv.DefaultMapName)
Dim flayer As IFeatureLayer=map.Layer(0)
Dim fClass As IFeatureClass=flayer.FeatureClass
Dim fcursor As IFeatureCursor=fClass.Search(Nothing, True)
webobj.ManageLifetime(fcursor)
Dim f As IFeature=fcursor.NextFeature()
Do Until f Is Nothing
' Do something with the feature.
f=fcursor.NextFeature()
Loop
Finally
ctx.ReleaseContext()
webobj.Dispose()
End Try
End Sub
WebMap、WebGeocode 和 WebPageLayout 对象,也有一个 ManageLifetime 方法。 例如,如果您使用 WebMap 并将您的代码限定在 using 块中,则可以依靠这些对象,显式释放 通过 ManageLifetime 方法而添加的对象。