首先,我想说清楚这不是关于如何在.Net下发布COM对象的问题。这是一个关于尝试理解可能由于正常的COM行为导致的意外COM对象释放的问题,但是我无法找到对观察到的行为的明确解释,并且希望确认我基于某些做出的推断报价稍后提出。
我注意到,当通过COM-Interop使用Excel时,Excel实例将完全终止,正如人们所希望的那样,当在Apartmentstate设置为ApartmentState.STA的辅助线程中创建互操作引用时。通过使用Marshal.ReleaseCOMObject显式调用它们的释放或通过调用垃圾收集器(GC)来按顺序清理对象,不会采取任何操作来清除运行时可调用包装器(RCW' s)上的引用计数。用于Excel完全关闭。对于那些不熟悉使用Excel Interop的人,请注意,众所周知,在被告知退出所有.Net COM参考文件后,它不会关闭。
我的第一个想法是GC在线程完成时自动运行。为了确定这是否属实,我使用了Visual Studio"性能和诊断"监视内存使用情况的工具。
上图,我首先运行在UI线程上与Excel交互然后在MTA线程上交互的方法。可以观察到,在运行GC以释放COM引用之前,Excel进程不会终止。请注意,GC标记位于分析图表上。然后我在STA线程上运行该方法两次。可以观察到Excel进程终止而不需要任何其他操作,并且性能分析图表指示在Excel启动的线程退出后GC没有运行。此外,如果我尝试在终止后访问在STA线程中创建的引用,则不能使用已与其基础RCW分离的" COM对象。"抛出异常。
此时我认为Excel进程的发布在某种程度上与用于创建对象的线程的回收有关。我在STA线程上运行了两次执行Excel方法的程序并记录下面显示的结果。可以看出,在测试的整个生命周期中,所有线程实例以及COM对象都被列为活动。
在研究COM对象的生命周期中,我在Larry Osterman的博客文章中发现了以下声明"这些“Threading Models” and why do I care?"似乎解释了为什么.Net RCW&# 39; s与底层COM对象断开连接。
COM对象的生命周期仅限于创建对象的公寓的生命周期。因此,如果您在STA中创建对象,然后销毁公寓(通过调用CoUninitialize),则将销毁在此公寓中创建的所有对象。
这句话暗示STA COM公寓是控制机制。但是,我发现的唯一表明公寓生命周期对.Net对象的影响的是Chris Brumme博客文章"Apartments and Pumping in the CLR"中的以下引文。
我们的COM Interop层确保我们几乎只在正确的公寓和上下文中调用COM对象。我们违反COM规则的地方是COM对象的公寓或上下文被拆除的时候。在这种情况下,我们仍然会在pUnk上调用IUnknown :: Release来尝试恢复其资源,即使这是严格违法的。
最后我的问题是:我观察到的是在线程执行结束时为线程被破坏而创建的STA公寓的结果,从而允许Excel进程终止,因为不再有任何对象持有对它的引用?
我最初声明这不是关于如何在.Net中发布COM对象的问题。但是,我很欣赏使用这种技术可能产生负面影响的任何见解。它一直有效,但是当文档技术很容易实现时,我对使用它犹豫不决。
下面介绍的代码是我用来调查此行为的代码。
Imports System
Imports Excel = Microsoft.Office.Interop.Excel
Imports System.Threading
Imports System.Runtime.InteropServices
Imports System.Windows.Forms
Imports System.Diagnostics
Public Class frmComRelease : Inherits Form
Private launchedExcelProcesses As New System.Collections.Concurrent.ConcurrentDictionary(Of Process, ApartmentState) ' requires Proj Ref: System.ServiceModel.dll
Private btnRunUI As Button
Private btnRunMTA As Button
Private btnRunSTA As Button
Private btnRunGC As Button
Private btnTryToAccessExcelReference As Button
Private excelReference As Object
Private processStatus As TextBox
Private chkBxGrabReference As CheckBox
Private grabReference As Boolean
Private key As New Object
Public Sub New()
MyBase.New()
Font = New Drawing.Font(Font.FontFamily, 12, Font.Style, Drawing.GraphicsUnit.Pixel)
Width = 400 : Height = 350
btnRunUI = AddButton("Run Excel On UI Thead", Nothing, AddressOf btnRunUI_Click)
btnRunMTA = AddButton("Run Excel On MTA Thead", btnRunUI, AddressOf btnRunMTA_Click)
btnRunSTA = AddButton("Run Excel On STA Thead", btnRunMTA, AddressOf btnRunSTA_Click)
btnTryToAccessExcelReference = AddButton("Access Last Excel Reference", btnRunSTA, AddressOf btnTryToAccessExcelReference_Click)
btnRunGC = AddButton("Run GC to free UI or MTA started Excel Process", btnTryToAccessExcelReference, AddressOf btnRunGC_Click)
processStatus = New TextBox With {.Multiline = True, .Location = New System.Drawing.Point(5, btnRunGC.Bottom + 10), .Width = Me.ClientSize.Width - 10, .Anchor = AnchorStyles.Bottom Or AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Top, .ReadOnly = True, .ScrollBars = ScrollBars.Vertical}
processStatus.Height = ClientSize.Height - processStatus.Top - 5
Controls.Add(processStatus)
chkBxGrabReference = New CheckBox() With {.Text = "Hold Excel Reference", .AutoCheck = True, .Location = New System.Drawing.Point(10 + btnRunMTA.Width, 5), .TextAlign = System.Drawing.ContentAlignment.MiddleLeft, .AutoSize = True}
AddHandler chkBxGrabReference.CheckedChanged, AddressOf chkBxGrabReference_CheckedChanged
Controls.Add(chkBxGrabReference)
StartPosition = FormStartPosition.Manual
Location = New Drawing.Point(500, 100)
End Sub
Private Sub chkBxGrabReference_CheckedChanged(sender As Object, e As EventArgs)
SyncLock key
grabReference = chkBxGrabReference.Checked
End SyncLock
End Sub
Private Function AddButton(text As String, relativeTo As Control, clickHandler As EventHandler) As Button
Dim btn As New Button() With {.Text = text, .Location = New System.Drawing.Point(5, If(relativeTo Is Nothing, 5, relativeTo.Bottom + 5)), .TextAlign = System.Drawing.ContentAlignment.MiddleLeft, .AutoSize = True}
AddHandler btn.Click, clickHandler
Controls.Add(btn)
Return btn
End Function
Protected Overrides Sub OnClosed(e As EventArgs)
MyBase.OnClosed(e)
For Each p As Process In Me.launchedExcelProcesses.Keys
p.Dispose()
Next
End Sub
Private Sub btnTryToAccessExcelReference_Click(sender As Object, e As EventArgs)
SyncLock key
If excelReference IsNot Nothing Then
Dim ptr As IntPtr
Dim msg As String
Try
ptr = Marshal.GetIUnknownForObject(excelReference)
Marshal.Release(ptr)
msg = "Sucessfully accessed reference"
Catch ex As Exception
msg = ex.Message
End Try
excelReference = Nothing
MessageBox.Show(msg)
End If
End SyncLock
End Sub
Private Sub btnRunUI_Click(sender As Object, e As EventArgs)
ExcelWork()
End Sub
Private Sub btnRunMTA_Click(sender As Object, e As EventArgs)
Dim t As New Thread(AddressOf ExcelWork)
t.SetApartmentState(ApartmentState.MTA)
t.Start()
End Sub
Private Sub btnRunSTA_Click(sender As Object, e As EventArgs)
Dim t As New Thread(AddressOf ExcelWork)
t.SetApartmentState(ApartmentState.STA)
t.Start()
End Sub
Private Sub btnRunGC_Click(sender As Object, e As EventArgs)
excelReference = Nothing
Do
GC.Collect()
GC.WaitForPendingFinalizers()
Loop While System.Runtime.InteropServices.Marshal.AreComObjectsAvailableForCleanup
End Sub
Private Sub ExcelWork()
Dim app As Excel.Application = New Excel.Application()
app.Visible = True
PositionExcel(app)
SyncLock key
If grabReference Then excelReference = app
End SyncLock
Dim processId As Int32
Dim threadID As Int32 = GetWindowThreadProcessId(app.Hwnd, processId)
Dim proc As Process = Process.GetProcessById(processId)
proc.EnableRaisingEvents = True
Dim state As ApartmentState = Thread.CurrentThread.GetApartmentState()
launchedExcelProcesses.TryAdd(proc, state)
UpdateStatus(GetProcessStatusMessage(proc))
AddHandler proc.Exited, AddressOf Process_Exited
Dim wb As Excel.Workbook = app.Workbooks.Add()
For Each cell As Excel.Range In DirectCast(wb.Worksheets.Item(1), Excel.Worksheet).Range("A1:H10")
cell.Value2 = 10
Next
wb.Close(False)
app.Quit()
UpdateStatus(String.Format("Exiting {0} thread of Excel process [{1}]", state, proc.Id))
End Sub
Private Sub PositionExcel(app As Excel.Application)
Dim r As System.Drawing.Rectangle = Me.Bounds
' Excel position/size measured in pts
Dim pxTopt As Double
Using g As Drawing.Graphics = CreateGraphics()
pxTopt = 72.0 / g.DpiX
End Using
app.WindowState = Excel.XlWindowState.xlNormal
app.Top = r.Top * pxTopt
app.Left = (r.Right) * pxTopt
app.Width = r.Width * pxTopt
app.Height = r.Height * pxTopt
End Sub
Private Function GetProcessStatusMessage(process As Process) As String
Dim state As ApartmentState
launchedExcelProcesses.TryGetValue(process, state)
Return String.Format("{3} - Excel process [{0}] {1} at {2}", process.Id, If(process.HasExited, "ended", "started"), If(process.HasExited, process.ExitTime, process.StartTime), state)
End Function
Private Sub UpdateStatus(msg As String)
Invoke(New Action(Of String)(AddressOf processStatus.AppendText), msg & Environment.NewLine)
End Sub
Private Sub Process_Exited(sender As Object, e As EventArgs)
Dim proc As Process = DirectCast(sender, Process)
UpdateStatus(GetProcessStatusMessage(proc))
Dim state As ApartmentState
launchedExcelProcesses.TryRemove(proc, state)
proc.Dispose()
proc = Nothing
End Sub
<DllImport("user32.dll", SetLastError:=True)>
Private Shared Function GetWindowThreadProcessId(ByVal hwnd As Int32, ByRef lpdwProcessId As Int32) As Int32
End Function
End Class
修改:可能相关的其他信息:
Don Box; May 1997, Microsoft Systems Journal, Q&A ActiveX/COM
......就其本质而言,所有对象都存在于一个过程中。为 进程外服务器,此进程由动态创建 服务控制管理器(SCM),基于服务器的实现 主/ WinMain函数。对于outofproc服务器,服务器实现者在 完全控制过程何时关闭。标准 服务器的WinMain的实现是拥有主线程的 进程等待,直到没有对象有未完成的客户端 服务。这保证了对象的主页&#34;将继续活着 只要有需要。
答案 0 :(得分:1)
我无法找到一个官方消息来源称,由.NET调用CoUninitialize。但是,我发现了一些东西。下面是一些&#34;堆栈跟踪&#34;来自.NET Core源代码。我无法找到相应的.NET Framework源代码,但我认为它与此没有太大区别。这些并不是通过此代码的唯一途径,并且这些并非所有COM初始化和未初始化的情况,但这足以证明CLR旨在隐式管理COM框架。 / p>
这里有一些值得注意的事情。 Thread:PrepareApartmentAndContext也registers an IInitializeSpy object。该对象watches for the apartment to be shut down并调用ReleaseRCWsInCaches。也可以从其他一些地方调用该方法。在这些兔子洞的某处,您可以找到您寻找的所有信息。