装载器锁定错误

103

我正在使用C#编写代码来构建一个C++ dll。

我遇到了一个错误,提示:

检测到LoaderLock。消息:尝试在操作系统Loader锁中执行托管代码。不要尝试在DllMain或映像初始化函数内部运行托管代码,因为这样会导致应用程序挂起。

我尝试搜索这个错误的确切含义,但是找到的大多数文章都说这只是一个警告,我应该在Visual Studio中关闭它。其他解决方案似乎与iTunes有关,或者是在使用DirectX进行编程时出现的问题。我的问题与这两个都没有关系。

有人能够解释一下这到底是什么意思吗?


我理解你的感受,我也遇到了同样的问题,最让我惊讶的是:我的dll甚至不是托管代码,那么为什么/如何它应该在(不存在的)DllMain上使用托管代码呢? - Sam
我在调试模式下尝试查看数据集内容时收到了此警告。我正在使用C#,这发生在常规的Windows窗体中。 - Soenhay
由于您无法找出原因(如您在顶部答案中所评论的),我怀疑您正在加载某个DLL文件,该文件正在犯罪。 - John Thoits
9个回答

75
你需要转到菜单“调试(Debug) -> 异常(Exceptions)”,打开托管调试助手(Managed Debugging Assistants),找到LoaderLock并取消勾选。

http://goo.gl/TGAHV


23
是的,这是关闭警告的方法;但即使过了2年,我仍然没有完全弄清楚它为什么会发生。 - Devdatta Tengshe
遇到了同样的问题。在调试时没问题,但是安装应用程序后仍然出现错误... - David Brunelle
2
这种情况发生在我打开一个旧项目时使用VS 2012。 - 4imble
1
我和你一样,@Kohan。我也打开了一个旧项目并遇到了错误。我已经禁用了异常,但是想要了解如何防止这种情况发生。 - Pimenta
1
如果我以本地调试的方式运行项目,并将所有异常设置为默认值(重置所有内容),则调试窗口会显示以下内容:<mda:msg xmlns:mda =“http://schemas.microsoft.com/CLR/2004/10/mda”> <!-- Attempting managed execution inside OS Loader lock.... etc --> <mda:loaderLockMsg break =“true”/> </mda:msg>。然后VS在CTOR序列期间呈现多个断点。 关闭LoaderLock设置没有帮助。对于我来说,我必须选中顶部MDA选项(适用于所有MDA),然后取消选中顶级选项(无MDA),然后构建+运行。这对我的同事不起作用。 - GilesDMiddleton
19
在VS2015中,想要分享一个更新,现在需要前往“调试->窗口->异常设置”选项。其余步骤与“托管调试助手\LoaderLock”相同。 - jxramos

55

Loader Lock的一般概念是: 系统在锁内运行DllMain内的代码(如同步锁)。因此,在DllMain内运行非平凡代码就会“请求死锁”,如此处所述。

问题是,为什么要在DllMain内运行代码?这段代码必须在DllMain上下文中运行吗?还是可以生成一个新的线程并在其中运行代码,而不必等待代码在DllMain内完成执行?

我认为,特别是对于托管代码的问题,运行托管代码可能涉及加载CLR等类似操作,无法知道会发生什么导致死锁...如果我是你,不会听从“禁用此警告”的建议,因为大多数情况下你会发现你的应用程序在某些场景下意外挂起。


5
我正在开发一个Direct3D应用程序,这是一个可执行文件。但是,我仍然看到这个错误。有什么好的解决方法吗? - Agnel Kurian

23

.NET 4.0及更高版本的更新

这是一个早期提出的问题,当时在.Net 2.0时支持混合模式DLL的初始化问题严重,容易出现随机死锁。从.Net 4.0开始,混合模式DLL的初始化方式已经改变。现在有两个分离的初始化阶段:

  1. Native initialization,在DLL入口点调用,包括本机C++运行时设置和执行您的DllMain方法。
  2. Managed initialization,由系统加载器自动执行。

因为步骤#2是在加载程序锁之外执行的,所以不会出现死锁情况。详细信息请参见混合程序集的初始化

为确保您的混合模式程序集可以从本机可执行文件中加载,您唯一需要检查的是将DllMain方法声明为本地代码。#pragma unmanaged可以在这里帮助:

#pragma unmanaged

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
    )
{
    ... // your implementation here
}

同样重要的是,DllMain 可能直接或间接调用的任何代码也必须是非托管的。限制 DllMain 使用的功能类型是有意义的,这样可以跟踪所有从 DllMain 可达的代码,并确保它们都使用 #pragma unmanaged 编译。

如果编译器检测到 DllMain 没有声明为非托管,则会给出警告 C4747:

1>  Generating Code...
1>E:\src\mixedmodedll\dllmain.cpp : warning C4747: Calling managed 'DllMain': Managed code may not be run under loader lock, including the DLL entrypoint and calls reached from the DLL entrypoint

然而,如果DllMain间接调用其他托管函数,编译器将不会生成任何警告,因此您需要确保这永远不会发生,否则您的应用程序可能会随机死锁。


7

提醒那些使用VS2017的用户,在遇到“加载器锁定错误”时,需要禁用“异常助手”,而不是在(VS2017之前)禁用“异常助理”。设置路径为调试 -> 异常。我曾经遇到过这个问题,浪费了2小时寻找解决方案...


我在“调试”下没有“异常”。我使用的是VS2017社区版15.8.4。 - Alex
@Alex,请检查Debug-->Windows-->Exception Settings,或按Ctrl + Alt + E。 - mistika

6
按下Ctr+D+E,然后展开“Managed Debugging Assistants”选项卡。接着,取消勾选“LoaderLock”选项。
希望这能对您有所帮助。

3
这个快捷键实际上取决于你在第一次运行时指定的配置。C# 的快捷键布局是 (Ctrl+D, E)。(同时,你也可以在“选项”->“环境”->“键盘”中将任何键组合分配给此功能。) - Adam L. S.

4

最近在创建一个用原生代码编写的COM对象实例时,我遇到了这个错误:

m_ComObject = Activator.CreateInstance(Type.GetTypeFromProgID("Fancy.McDancy"));

这导致了所描述的错误。一个“检测到LoaderLock”异常被抛出。
我通过在一个额外的线程中创建对象实例来克服这个错误:
ThreadStart threadRef = new ThreadStart(delegate { m_ComObject = Activator.CreateInstance(Type.GetTypeFromProgID("Fancy.McDancy")); });
Thread myThread = new Thread(threadRef);

myThread.Start();
myThread.Join(); // for synchronization

错误可能会发生在可远程对象(MarshalByRefObject)中,这种解决方案对于它们无效。 - Matthieu

3
我正在构建一个C++ CLR DLL(MSVS2015),必须调用一个未管理的DLL并定义未管理的代码。我使用#pragma managed和#pragma unmanaged来控制代码在给定区域中的模式。
在我的情况下,我只需在我的DllMain()前面放置#pragma unmanaged,这解决了问题。它似乎认为我想要一个托管版本的DllMain()。

2

在我的Visual Studio 2017实例中,设置路径为Debug -> Windows -> Exception Settings。异常设置"窗口"显示在底部选项卡组中(而不是单独的窗口),我花了一段时间才注意到它。搜索"loader"。


1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接