如何修复 VSIX 中意外的 COM 互操作问题
一段时间以来,我在 Visual Studio 2010 的 VSIX 包中观察到间歇性 COM 问题。尝试订阅 IDE 的基于 COM 的事件接收器之一会随机引发以下错误:
无法使用已与其底层 RCW 分离的 COM 对象
重现案例归结为以下代码(显然,必须在 VSIX 中使用):
using System;
using EnvDTE;
using EnvDTE80;
class Test
{
private readonly Events _events;
private readonly Events2 _events2;
private readonly BuildEvents _buildEvents;
private readonly ProjectItemsEvents _projectItemsEvents;
public Test(IServiceProvider provider)
{
var dte = (DTE)provider.GetService(typeof(DTE));
var dte2 = (DTE2)dte;
// Store all references in fields as a GC precaution.
_events = dte.Events;
_events2 = (Events2)dte2.Events;
_buildEvents = _events.BuildEvents;
_projectItemsEvents = _events2.ProjectItemsEvents;
// Proceed to subscribe to event sinks.
_buildEvents.OnBuildBegin += BuildBeginHandler; // BOOM!
_projectItemsEvents.ItemAdded += ItemAddedHandler;
}
private void ItemAddedHandler(ProjectItem projectItem) { }
private void BuildBeginHandler(vsBuildScope scope, vsBuildAction action) { }
}
我从可以找到的类似问题的大量描述中了解了可能的原因在网上。它基本上是 COM 互操作期间运行时可调用包装器和 GC 交互方式的副作用。这是 类似问题并附有解释。
我对这个解释很满意,特别是因为它提出了一个简单的解决方法 - 将事件接收器引用存储在一个字段中,以防止它过早被 GC 处理。确实,很多人似乎都是通过这种方式解决了他们的问题。
令我困扰的是它在我的情况下不起作用。我真的很困惑为什么。正如您可以清楚地看到的,作为预防措施,我已经将所有对象引用存储在字段中。然而错误仍然发生。我尝试在构造函数末尾使用 GC.KeepAlive() 调用更加明确,但无济于事。还有什么事情要做吗?
如果没有解决方案,我的 VSIX 会随机加载失败,给用户留下一个选项:重新启动 Visual Studio,并希望下次不会发生这种情况。
For some time now, I've observed an intermittent COM problem in my VSIX package for Visual Studio 2010. Attempting to subscribe to one of the IDE's COM-based event sinks randomly throws the following error:
COM object that has been separated from its underlying RCW cannot be used
A repro case boils down to this code (which must be used in VSIX, obviously):
using System;
using EnvDTE;
using EnvDTE80;
class Test
{
private readonly Events _events;
private readonly Events2 _events2;
private readonly BuildEvents _buildEvents;
private readonly ProjectItemsEvents _projectItemsEvents;
public Test(IServiceProvider provider)
{
var dte = (DTE)provider.GetService(typeof(DTE));
var dte2 = (DTE2)dte;
// Store all references in fields as a GC precaution.
_events = dte.Events;
_events2 = (Events2)dte2.Events;
_buildEvents = _events.BuildEvents;
_projectItemsEvents = _events2.ProjectItemsEvents;
// Proceed to subscribe to event sinks.
_buildEvents.OnBuildBegin += BuildBeginHandler; // BOOM!
_projectItemsEvents.ItemAdded += ItemAddedHandler;
}
private void ItemAddedHandler(ProjectItem projectItem) { }
private void BuildBeginHandler(vsBuildScope scope, vsBuildAction action) { }
}
I've learned about a possible cause from numerous descriptions of similar problems that can be found on the net. It's basically a side effect of the way Runtime Callable Wrappers and GC interact during COM interop. Here's a similar problem complete with explanation.
I'm fine with that explanation, especially because it suggests an easy workaround - storing the event sink reference in a field in order to prevent it from being prematurely GC'ed. Indeed, many people seem to have solved their problem this way.
What bothers me is that it doesn't work in my case. I'm really stumped as to why. As you can plainly see, I already store all object references in fields as a precaution. Yet the error still occurs. I tried being even more explicit using GC.KeepAlive()
calls at the end of the ctor, but to no avail. Is there anything else left to do?
Without a solution, my VSIX randomly fails to load, leaving the user with a single option: to restart Visual Studio and hope it doesn't happen the next time.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
好吧,我放弃了,只是做了我唯一想到的事情。我想,由于这显然是一个我无法以可预测的方式影响的竞赛条件,所以如果我输了,我也可能会重新参加比赛。
因此,我将订阅行移至
while
循环中,try
..catch
-es 它们并在一段Thread 后重试。睡眠()。当两个订阅都成功时或者当我连续输掉比赛超过 2 秒时,循环就会退出。
更重要的是,自从实施这一改变以来,我还没有输过一次比赛。一只真正的海森虫,如果我见过的话。
不管怎样,我会坚持下去,直到找到合适的解决方案或者错误再次出现。
Well, I gave up and simply did the only thing that crossed my mind. I figured that since this is obviously a race condition I can't affect in a predictable manner, I might as well reenter the race if I lose.
So I moved the subscription lines into a
while
loop thattry
..catch
-es them and retries after a bit ofThread.Sleep()
. The loop exits either when both subscriptions succeed or when I've been continuously losing the race for more than 2 seconds.The kicker is, I haven't lost the race once since I've implemented the change. A true Heisenbug, if I ever saw one.
Anyway, I'm going to stick with this until a proper solution occurs to me or the bug reappears.
我怀疑您的问题实际上是您试图过早连接事件处理程序。您通常需要在 Initialize 你的包/工具窗口/任何东西的方法 - 一般来说,如果你需要使用一个服务,你需要在调用Initialize方法之后执行此操作,绝对不要在你的包的构造函数中执行此操作。
(这只是一种预感 - 您的
Test
类没有实现任何 VSX 接口,因此我无法从您的示例中看到何时调用构造函数)I suspect that your problem is really that you are attempting to wire up your event handlers too soon. You normally need to be doing these sorts of things in the Initialize method of your package / toolwindow / whatever - generally speaking if you need to use a service you need to do if after the Initialize method has been called, definitely don't do this in the constructor of your Package.
(This is just a hunch - your
Test
class doesn't implement any VSX interfaces and so I can't see from your sample when the constructor is being called)