c# - 进行托管到 native 互操作时操作系统加载器锁定

标签 c# c++ wpf multithreading interop

我正在使用 HwndHost 将 native 控件 (C++) 加载到 WPF 控件中. HwndHost定义如下:

class ControlHost : System.Windows.Interop.HwndHost
{
    public IntPtr Handle;
    protected override HandleRef BuildWindowCore(HandleRef hwndParent)
    {
        // instantiate the native control
        Handle = control.Handle;
        return new HandleRef(this, control.Handle);
    }
    ...
}

我的 WPF 项目有一个名为 ControlHostElement 的 System.Windows.Controls.Border。一般模式是获取 ControlHostElement 的句柄,实例化 native 控件并将其设置为 WPF 控件的子元素。此模式由 MSDN here 规定.我通过 WPF 页面上的按钮触发此操作:

private void btnHwndHost_OnClick(object sender, RoutedEventArgs e)
{
    myControlHost = new ControlHost();
    ControlHostElement.Child = myControlHost;
}

问题是,当我实例化我的 native 控件时,我在分配 Child 的行收到 OS Loader Lock 错误:

DLL 'my.dll' is attempting managed execution inside OS Loader lock. Do not attempt to run managed code inside a DllMain or image initialization function since doing so can cause the application to hang.

我不确定此时我是如何进入加载程序线程的,但我想我应该启动一个新线程来执行初始化和窗口句柄分配:

private void btnHwndHost_OnClick(object sender, RoutedEventArgs e)
{
    Thread loadControlHostThread = new Thread(
        new ThreadStart(this.loadControlHostThread_DoWork));
    loadControlHostThread.SetApartmentState(ApartmentState.STA);
    loadControlHostThread.Start();
}

void loadControlHostThread_DoWork()
{
    myControlHost = new ControlHost();
    ControlHostElement.Child = myControlHost;
}

没有骰子:

An unhandled exception of type 'System.InvalidOperationException' occurred in WindowsBase.dll

Additional information: The calling thread cannot access this object because a different thread owns it.

很公平。也许我应该尝试让 UI 线程来完成这项工作:

void loadControlHostThread_DoWork()
{
    this.Dispatcher.Invoke((Action)(() =>
        {
            myControlHost = new ControlHost();
            ControlHostElement.Child = myControlHost;
        }));
}

这会导致相同的 OS Loader Lock 错误。我初始化 native 控件的正确方法是什么?

最佳答案

I get an OS Loader Lock error

这不是错误,是警告。来自 MDA,托管调试器助手。它们是 Microsoft 插入 CLR 和调试器中的一小段代码,当它看起来您的程序有问题时会发出警告。不会产生异常但会使您的程序挂起或以非常难以诊断的方式失败的类型。

Loader 锁当然符合这种模式,它是隐藏在 Windows 内部的死锁。与加载器关联,操作系统的一部分负责加载 DLL 并调用它们的 DllMain() 入口点。它需要一个内部锁来确保一次调用一个 DllMain() 函数。它可以防止重入问题,与 Application.DoEvents() 导致的那种麻烦相当。该锁上的死锁很难调试,代码完全隐藏在操作系统以及您一无所知的神秘 DllMain() 函数中。一个真正的僵局很可能会让你把你的头发扯成一大团,而没有那种 MDA 的秃头几乎没有任何表现。

不幸的是,MDA 往往会产生错误警告。它并不总是意识到死锁实际上不会发生。它过于急切,这是它必须以 Crystal 球的方式预测它可能发生的副作用。如果没有其他方式能够将自身连接到操作系统内部以给你一个有保证的警告。 Microsoft 的 Windows 小组从来没有对容纳托管代码感到高兴,Longhorn 在很长一段时间内都是一个痛处。加载程序锁定是 .NET 1.0 中的一个大问题

在您的情况下,这几乎可以肯定是错误警告,您可以确定 CLR 已经加载,否则您的程序可能无法启动。

幸运的是,让它不再困扰您非常简单:Debug + Exceptions,打开 Managed Debugging Assistants 节点并取消选中“LoaderLock”复选框。很有可能它会让您从那里平静下来,让您专注于测试您的程序。

关于c# - 进行托管到 native 互操作时操作系统加载器锁定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23689521/

相关文章:

c# - 在列表的上限和下限之间找到一个值

c# - 为什么在串口上发送 SMS 有时会卡住硬件?

c# - 如何从运行 .NET 4.7.2 的 WPF 访问 gRPC 服务?

c# - Roslyn:从代码分析器访问部分类的 XAML

c# - MySQL根据日期时间插入

c# - DockPanel.Dock ="Right"不适用于最大化窗口上的单个控件?

c++ - 无法将偏移索引计算到 3D 数组中

c++ - 来自 QT 的 SQLite 最大列数配置

c++ - BST isBST() 解释

wpf - 如何使用触发器制作文本框 Visibility=Hidden