IHTMLChangeSink UnregisterForDirtyRange抛出ComException HRESULT E_FAIL

时间:2015-10-15 22:21:59

标签: c# wpf com

我们有一个WPF,其中一个标签控件是显示客户详细信息的主要UI,每个开放客户都有自己的标签。 在客户选项卡中,我们有另一个选项卡控件,允许在各种信息子集之间切换,其中2个使用带有IHTMLChangeSink功能的Webbrowser控件来监视隐藏的div,用于触发应用程序中的逻辑。

以前,当关闭Customer选项卡时,我们遇到了非常大的内存泄漏,其原因是RegisterForDirtyRange创建的事件处理程序。为了解决内存泄漏,Dispose方法被修改为调用UnregisterForDirtyRange,使用AutoIT快速打开和关闭客户选项卡,我们能够证明内存泄漏是固定的;这是在开发人员类机器上完成的。

一旦这个更改推出给测试人员,我们就开始看到应用程序崩溃了,在事件日志中我们看到对UnregisterForDirtyRange的调用抛出了一个带有HRESULT E_FAIL的ComException。由于我们从未在开发人员硬件和测试机器上看到这种情况,因此无法保证产生崩溃的方法我认为在不太强大的硬件上运行时会出现某种竞争条件。

鉴于此信息我的问题是关于取消注册调用的内部工作方式,有人可以想到可能导致此异常的原因吗?

我最初的想法是,Notify方法可能在处理时运行,所以我尝试在dispose和notify之间引入一个锁,但这并没有改变任何东西。

以下是包装Web浏览器的标签控件的精简版本:

public partial class BrowserTabWidget : BrowserWidget, IHTMLChangeSink
{
    private static Guid _markupContainer2Guid = typeof(IMarkupContainer2).GUID;
    private IMarkupContainer2 _container;
    private uint _cookie;

    public BrowserTabWidget()
    {
        InitializeComponent();
        if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
        {
            Loaded += OnLoaded;
        }
    }

    protected override void DisposeControls()
    {
        if (_container != null)
        {
            _container.UnRegisterForDirtyRange(_cookie);
            Marshal.ReleaseComObject(_container);
        }
        WebBrowser.LoadCompleted -= OnWebBrowserLoadCompleted;
        WebBrowser.Dispose();
    }

    public override string CurrentUri
    {
        get { return (string)GetValue(CurrentUriProperty); }
        set
        {
            NavigateTo(value);
            SetValue(CurrentUriProperty, value);
        }
    }

    private void NavigateTo(string value)
    {
        WebBrowser.Navigate(new Uri(value));
    }

    public static readonly DependencyProperty CurrentUriProperty = DependencyProperty.Register("CurrentUri", typeof(string), typeof(BrowserTabWidget), new FrameworkPropertyMetadata(CurrentUriChanged));
    public static void CurrentUriChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var widget = (BrowserTabWidget)d;
        d.Dispatcher.BeginInvoke(
            DispatcherPriority.Normal,
            new Action(() => widget.NavigateTo(e.NewValue.ToString())));
    }

    private void InitializeWebBrowser()
    {
        WebBrowser.LoadCompleted += OnWebBrowserLoadCompleted;
        WebBrowser.Navigate(new Uri(viewModel.InitialUrl));
    }

    void OnWebBrowserLoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
    {
        _container = GetMarkupContainer();
        _container.RegisterForDirtyRange(this, out _cookie);
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        Loaded -= OnLoaded;
        Load();
    }

    private void Load()
    {
        InitializeWebBrowser();
    }

    private IMarkupContainer2 GetMarkupContainer()
    {
        var oDocument = WebBrowser.Document as IHTMLDocument2;
        var pDocument = Marshal.GetIUnknownForObject(oDocument);
        IntPtr pMarkupContainer;
        Marshal.QueryInterface(pDocument, ref _markupContainer2Guid, out pMarkupContainer);
        var oMarkupContainer = Marshal.GetUniqueObjectForIUnknown(pMarkupContainer);
        Marshal.Release(pDocument);
        Marshal.Release(pMarkupContainer);
        return (IMarkupContainer2)oMarkupContainer;
    }

    public void Notify()
    {
        var document = WebBrowser.Document as HTMLDocument;
        if (document != null)
        {
            //Parse Dom for hidden elements and trigger appropriate event handler
        }
    }
}

1 个答案:

答案 0 :(得分:1)

Hmya,E_FAIL,COM的诅咒。没有任何东西可以诊断任何东西,它只是教师对错误报告质量的评分。我在Winforms中编写了相同的代码,以获得可测试的内容,无需重复。然而,迫使一个复制品很容易。鉴于该方法只需要一个参数,只有一件事可能出错:

    if (_container != null)
    {
        _container.UnRegisterForDirtyRange(_cookie);
        _container.UnRegisterForDirtyRange(_cookie);    // Kaboom!!!
        Marshal.ReleaseComObject(_container);
    }

错误的cookie。犯罪他们没有返回E_INVALIDARG btw。

我无法测试你的确切代码,它确实有问题。我看到的最严重的一个和repro案例是没有防止不止一次调用DisposeControls()的保护。在 general 中,多次配置对象永远不会出错,如果你的项目中存在真实的失败模式,我无法理解。否则很简单,以保护自己免受此事。包括catch-and-swallow代码:

protected override void DisposeControls()
{
    if (_container == null) return;
    try {
        _container.UnRegisterForDirtyRange(_cookie);
    }
    catch (System.Runtime.InteropServices.COMException ex) {
        if (ex.ErrorCode != unchecked((int)0x80004005)) throw;
        // Log mishap...
    }
    finally {
        Marshal.ReleaseComObject(_container);
        _container = null;
        _cookie = 0;
        WebBrowser.LoadCompleted -= OnWebBrowserLoadCompleted;
        WebBrowser.Dispose();
    }
}

我在您的代码测试版中注意到的另一件事是,您似乎没有任何保护措施来防止浏览器通过除WebBrowser.Navigate()之外的任何其他方式导航到另一个页面。或者LoadCompleted事件多次触发,例如,它对stackoverflow.com主页执行。或任何使用框架的网页。那是泄密。通过让OnWebBrowserLoadCompleted()事件处理程序也取消注册cookie(如果已设置),使其具有弹性。