gpt4 book ai didi

editor - 使用IVSInvisibleEditor和IVSPersistDocData,但如何释放它们?

转载 作者:行者123 更新时间:2023-12-01 08:14:46 25 4
gpt4 key购买 nike

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

最佳答案

IVsInvisibleEditor上没有Close方法,因为它仅使用COM引用计数:当对象获取它时,它是对IUnknown.Release()的最终调用,它使用该提示来关闭基础文件。如果您使用C++编写扩展程序,那么这很容易:只要确保将其发布就可以了。但是我猜您是用托管代码编写的,这要困难得多。 CLR使处理此类对象变得很痛苦。我将假设您不是COM编组专家,因此我为冗长的讨论深表歉意,但是了解所有这些工作原理非常重要。

背景:每当您尝试使用托管代码中的COM对象时,CLR都会创建所谓的“运行时可调用包装程序”或RCW。这是一个小的托管对象,是本地对象的包装。在内部,它保留IUnknown指针,并“拥有”该对象的AddRef / Release。这个想法是当托管代码不再使用RCW时,RCW会收集垃圾,当这种情况发生时,CLR然后在基础对象上调用Release()。

当您调用IVsInvisibleEditorManager.RegisterInvisibleEditor时,VS中的本机代码会将指向该对象的指针移回托管代码。然后,CLR将对象包装在RCW中,这意味着,除非我们对Marshal.ReleaseComObject采取特殊步骤,否则不可见的编辑器将漂浮在周围,直到GC确定RCW消失了,是时候释放它了。不是您所需要的。

因此,只需调用Marshal.ReleaseComObject,然后完成,对吗?错误!通常,使用Marshal.ReleaseComObject should be considered dangerous是因为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互操作程序集中定义的方式,但是有一个关键的区别:代替ppEditor参数作为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时,该对象已经为我们添加了AddRef。当我们创建RCW时,本机对象将获得另一个AddRef()。如果我们不对本机指针调用Release,那么我们也会泄漏它。但是在上面的代码之后,this.invisibleEditor拥有我们以后可以使用的唯一RCW。一旦准备好关闭整个过程,您只需调用:
Marshal.ReleaseComObject(this.invisibleEditor)

并且基础COM对象将立即被销毁。

关于editor - 使用IVSInvisibleEditor和IVSPersistDocData,但如何释放它们?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25406780/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com