使用IVSInvisibleEditor和IVSPersistDocData但如何发布它们?

使用IVSInvisibleEditor和IVSPersistDocData但如何发布它们?,editor,visual-studio-extensions,vspackage,Editor,Visual Studio Extensions,Vspackage,我在自定义工具窗口中使用IVsInvisibleEditor将t4文件加载到托管vs编辑器中。我调用IVsInvisibleEditorManager.RegisterInvisibleEditor方法,像它们一样传入t4文件。然后,我使用GetDocData方法获取文件内容,然后将其设置为编辑器的缓冲区。我通过将getdocdata的结果强制转换到IVsPersistDocData实例并调用save方法来保存编辑器中的更改。在关闭工具窗口时,我尝试通过调用IVsPersistDocData实例

我在自定义工具窗口中使用IVsInvisibleEditor将t4文件加载到托管vs编辑器中。我调用IVsInvisibleEditorManager.RegisterInvisibleEditor方法,像它们一样传入t4文件。然后,我使用GetDocData方法获取文件内容,然后将其设置为编辑器的缓冲区。我通过将getdocdata的结果强制转换到IVsPersistDocData实例并调用save方法来保存编辑器中的更改。在关闭工具窗口时,我尝试通过调用IVsPersistDocData实例上的close来清理资源。当我尝试再次打开同一文件的工具窗口时,在尝试再次调用不可见编辑器上的getdocdata时会出现异常。如果我不在IVsPersistDocData上关闭它,它就会工作。如何正确关闭所有这些资源(IVsInvisibleEditor、IVsInvisibleEditorManager、IVsPersistDocData),以便在再次尝试使用它们时不会出现异常?

IVsInvisibleEditor
没有关闭方法,因为它只使用COM引用计数:当对象获得对IUnknown.Release()的最终调用时,它使用它作为关闭底层文件的提示。如果你在C++中写扩展,那么这很简单:只要确保你能发布它,你就可以了。但我猜您是在托管代码中编写的,这要困难得多。CLR使处理这样的对象变得很痛苦。我假设您不是COM编组专家,因此我为长时间的讨论道歉,但了解这一切是如何工作的很重要

背景:每当您尝试使用托管代码中的COM对象时,CLR都会创建一个称为“运行时可调用包装器”或RCW的对象。这是一个小型托管对象,是本机对象的包装器。在内部,它持有IUnknown指针,并“拥有”该对象的AddRef/Release。其思想是,当托管代码不再使用RCW时,RCW会被垃圾收集,当这种情况发生时,CLR会对底层对象调用Release()

调用
IVsInvisibleEditorManager.RegisterInvisibleEditor
时,VS中的本机代码会将指向该对象的指针返回到托管代码。然后CLR将对象包装在RCW中,这意味着除非我们使用
封送.ReleaseComObject
采取特殊步骤,否则不可见编辑器将四处浮动,直到GC确定RCW已消失,并且是时候释放它了。不是你需要的

所以,只需调用Marshal.ReleaseComObject,就可以完成了,对吗?错了!一般来说,因为CLR还有另一个棘手的行为。假设您要打开一个文件的不可见编辑器,当您打开它时,Visual Studio中的另一个组件也会打开同一个文件,并且管理器返回了相同的
IVsInvisibleEditor
本机实例。您已经拥有本机对象实例的RCW。对于另一个组件,CLR将“哈哈!其他人已经拥有该对象”,并将与您相同的RCW交给他们。如果他们调用了
Marshal.ReleaseComObject
并销毁了COM对象,那就意味着你手中的对象已经变成了僵尸。这就是为什么
ReleaseComObject
是危险的:只有当您知道自己是唯一持有RCW的人时,才可以调用它,但默认情况下,CLR在需要RCW的任何人之间共享RCW

这就是为什么您无法从托管代码中正确使用
IVsInvisibleEditorManager
:当您调用
RegisterInvisibleEditor
时,您会得到一个共享的RCW。您不能在它上调用'ReleaseComObject'而不可能破坏其他人。没有简单的方法可以选择获得一个独特的RCW

正确解决此问题的第一步是为
IVsInivisibleEditorManager
定义我们自己的接口。这是我们在托管VS代码的某些部分中定义它的方式:

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("14439CDE-B6CF-4DD6-9615-67E8B3DF380D")]
internal interface IIntPtrReturningVsInvisibleEditorManager
{
    int RegisterInvisibleEditor(
        [MarshalAs(UnmanagedType.LPWStr)] string pszMkDocument,
        IVsProject pProject,
        uint dwFlags,
        IVsSimpleDocFactory pFactory,
        out IntPtr ppEditor);
}
这一点你可以坚持你的组装,没有问题。这就是它在Microsoft.VisualStudio.Shell interop程序集中的定义方式,但有一个关键区别:pEditor参数不是一个COM对象(这将为我们获得一个共享RCW),我们只是为该对象返回一个IntPtr。CLR将保持不变,这是关键:我们需要控制如何将其转换为RCW。然后,首先获取
IVsInvisibleEditorManager
接口,然后将其转换到您自己的接口。这是因为将RCW转换为COM接口很神奇:只要底层对象说它支持接口(通过指定的GUID查找),CLR就会伪造它,并说RCW可以转换为接口——即使是您自己定义的接口。然后,您可以调用RegisterInvisibleEditor并获取一个IntPtr,然后为它创建一个唯一的RCW。以下是获取文档数据的代码:

var invisibleEditorManager = (IIntPtrReturningVsInvisibleEditorManager)serviceProvider.GetService(typeof(SVsInvisibleEditorManager));
var invisibleEditorPtr = IntPtr.Zero;
Marshal.ThrowExceptionForHR(invisibleEditorManager.RegisterInvisibleEditor(filePath, null, 0, null, out invisibleEditorPtr));

try
{
    this.invisibleEditor = (IVsInvisibleEditor)Marshal.GetUniqueObjectForIUnknown(invisibleEditorPtr);

    var docDataPtr = IntPtr.Zero;
    Marshal.ThrowExceptionForHR(invisibleEditor.GetDocData(fEnsureWritable: 0, riid: typeof(IVsTextLines).GUID, ppDocData: out docDataPtr));

    try
    {
        var docData = Marshal.GetObjectForIUnknown(docDataPtr);

        // use docData how you want, probably by getting the text of it               
    }
    finally
    {
        Marshal.Release(docDataPtr);
    }
}
finally
{
    // Since we have a unique RCW holding onto the object, we must release our direct pointer as well
    Marshal.Release(invisibleEditorPtr);
}
最后这些块是关键的:当我们得到一个表示COM对象的IntPtr时,该对象已经为我们添加了REF。创建RCW时,本机对象将获得另一个AddRef()。如果我们不在本机指针上调用Release,那么我们也会泄漏它。但是在上面的代码之后,this.invisibleEditor保存了我们以后可以使用的唯一RCW。一旦你准备好结束这一切,你所要做的就是打电话:

Marshal.ReleaseComObject(this.invisibleEditor)

而底层COM对象将立即被销毁。

您是否面临实际问题,还是只是出于礼貌:-)?IVSpersistDocData.Close关闭文档。如果你真的想关闭文档,你应该调用这个。否则,如果您在.NET上运行,您不需要做任何特殊的事情(如果您在接口上保留引用,因为如果您有IntPtr,那么您希望释放它们),我确实遇到了一个异常,因为我认为IVsInvisibleEditorManager.RegisterInvisibleEditor正在将我打开的文件添加到正在运行的文档表中,而当我仅对IVSPersistDocData使用close方法时,我是