PowerShell 释放 COM 对象

12

我在一个PowerShell脚本中有一个小的实用函数:

function Unzip-File{
    param(
        [Parameter(Mandatory=$true)]
        [string]$ZipFile,
        [Parameter(Mandatory=$true)]
        [string]$Path
    )

    $shell=New-Object -ComObject shell.application

    $zip = $shell.namespace($ZipFile)
    $target = $shell.NameSpace($Path)
    $target.CopyHere($zip.Items())
}

我应该在脚本中清理COM对象吗?还是PowerShell聪明到足以自动清理COM对象垃圾?

我阅读了摆脱COM对象(一劳永逸)盲目地应用:

[System.Runtime.Interopservices.Marshal]::ReleaseComObject($zip)
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($target)
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($shell)

但我不确定这是否有必要。

在本地函数中使用COM对象的正确模式是什么?


1
我认为你找到的文章可能是正确的。进一步阅读关于Remove-Variable的注释,以释放对变量的任何其他引用,然后GC可能会处理其余部分。如果你的对象有一个Dispose()方法,你可能也想在释放之前调用它。 - alroc
2个回答

12

通常我使用这个函数:

function Release-Ref ($ref) {

[System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$ref) | out-null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()

}

因为我注意到我的COM对象总是存活的,所以我认为PowerShell 2.0无法删除不再使用的COM对象。

[System.Runtime.InteropServices.Marshal]::ReleaseComObject( $ref )
(见这里) 返回与对象关联的RCW的引用计数的新值。由于RCW仅保留对包装的COM对象的一个引用,而不管调用它的托管客户端数量,因此此值通常为零。我已经注意到对于shell.application,您需要调用它直到该值变为0。
要测试是否已释放所有引用,可以尝试在该值为0时读取$shell的值:它将返回一个错误。

有没有一种方法可以“监视”我的对象是否被正确清除? - Steve B
1
我相信 ReleaseComObject 是关键。我也认为另外两行不是必需的。使用 C# 时,我一直阅读到最好让 GC 在它决定时工作。我会在 PowerShell 中遵循这个建议。 - Steve B
@SteveB 我添加了一些新信息。 - CB.
我看到了0的值,而且它总是0。我猜这是因为我在我的函数中局部创建对象,因此控制了对象的短暂生命周期。 - Steve B
因此,您应该更新您的实用方法以包括 while([System.Runtime.Interopservices.Marshal]::ReleaseComObject($ref) -gt 0) { Write-Debug "Releasing an COM instance" } - Steve B
显示剩余5条评论

0

GC(垃圾回收器)只能处理.NET对象,而不能处理.NET封装的COM对象。您需要显式清理底层的COM对象。


4
不正确。垃圾回收器(GC)也可以处理.NET封装的COM对象。当GC检测到一个.NET COM对象不再被其他任何东西引用时,它会清理该对象。其中一部分清理工作是在底层接口上调用Release方法。因此,如果一个.NET对象不再引用一个COM对象,那么它将被清理。然而,在我看来,.NET和COM并不兼容。手动调用ReleaseComObject通常比等待GC完成其工作更容易。 - Joseph Willcoxson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接