从非托管C++动态加载混合模式C++/CLI .dll(及其依赖项)

5
我有一个托管的C++程序集,通过标准的LoadLibrary()调用在非托管的C++应用程序中动态加载。托管的C++程序集依赖于其他几个托管的(C#)程序集。一切都很正常,直到我将所有托管的程序集移动到非托管应用程序的子目录中。例如:
- 托管的C++ .dll (MyCoolDll.dll) - 依赖于DotNetDll1.dll - 依赖于DotNetDll2.dll - 非托管的C++应用程序(MyCoolApp.exe) - 通过LoadLibrary("MyCoolDll.dll")加载MyCoolDll.dll
这一切都很正常,直到我将MyCoolDll.dll、DotNetDll1.dll和DotNetDll2.dll移动到/someSubDirectory下(同时更新了MyCoolApp.exe中的代码以LoadLibrary("someSubDirectory/MyCooldll.dll"))。
我猜当加载MyCoolDll.dll时,它试图在工作目录中查找DotNetDll1.dll和DotNetDll2.dll,而不是它所在的目录。
那么,我该如何告诉MyCoolDll.dll它的依赖项在子目录中呢?它是在非托管应用程序内运行的库,因此我认为我不能在app.config或其他任何地方指定这个?

1
哇,汉斯,太棒了!我真的很怀疑,因为MyCoolApp.exe只是一个普通的Win32应用程序(不是.NET),所以我认为为它添加一个应用程序配置文件是没有帮助的。谢谢!你想把这个写成答案而不是评论吗?我会标记它为已接受的。 - Jordan0Day
3个回答

8
我觉得你需要的是一个自定义程序集解析器。我也曾经使用过它,来达到你想要的目的——我想在不在未托管的DLL树中加载托管代码的某些DLL文件夹中寻找一些DLL文件。
第1步是创建一个函数,你可以调用它来设置解析器:
void PrepareManagedCode()
{
    // Set up our resolver for assembly loading
    AppDomain^ currentDomain = AppDomain::CurrentDomain;
    currentDomain->AssemblyResolve += gcnew ResolveEventHandler(currentDomain_AssemblyResolve);
}  // PrepareManagedCode()

接下来是解析器。这个例子有一个名为ourFinalPath的全局变量,而在您的情况下,它将是您使用的额外文件夹:

/// <summary>
/// This handler is called only when the CLR tries to bind to the assembly and fails
/// </summary>
/// <param name="sender">Event originator</param>
/// <param name="args">Event data</param>
/// <returns>The loaded assembly</returns>
Assembly^ currentDomain_AssemblyResolve(Object^ sender, ResolveEventArgs^ args)
{
    sender;

    // If this is an mscorlib, do a bare load
    if (args->Name->Length >= 8 && args->Name->Substring(0, 8) == L"mscorlib")
    {
        return Assembly::Load(args->Name->Substring(0, args->Name->IndexOf(L",")) + L".dll");
    }

    // Load the assembly from the specified path
    String^ finalPath = nullptr;
    try
    {
        finalPath = gcnew String(ourAssemblyPath) + args->Name->Substring(0, args->Name->IndexOf(",")) + ".dll";
        Assembly^ retval = Assembly::LoadFrom(finalPath);
        return retval;
    }
    catch (...)
    {
    }

    return nullptr;
}

这是我认为必须走的路线——问题似乎在于我仍然需要将MyCoolDll.dll的依赖项放在主应用程序目录中,因为在加载MyCoolDll.dll之前,.net不会在子文件夹中搜索它们(因此装配解析器不会首先设置)。令我惊讶的是,汉斯上面的建议起作用了(即使MyCoolApp.exe不是托管可执行文件)。 - Jordan0Day
您的非托管 DLL 将在需要程序集解析之前加载。 然后,您可以自己加载混合模式 DLL,并调用程序集解析设置。 对于复杂场景,您将需要它; 但是,如果简单配置文件适用于您的情况,那就太好了! - Ed Bayiates
谢谢,这个可行,但我有一些注释/问题:
  • 你为什么要对mscorlib做特殊处理?似乎不做任何处理也能工作。
  • 使用AssemblyName来解析args->Name会更好。
  • 要小心返回实际请求的程序集,而不是总是相同的程序集。我忽略了这一点,结果出现了糟糕的情况 :)
- Melvyn
@Yaurthek 这是一个工作代码示例,展示了您可以选择在此调用中从哪里加载。在我们的情况下,我们希望从不同的位置加载这些库。 - Ed Bayiates
@EdBayiates 为什么您在程序集解析器中使用try catch而不是if文件存在呢? - Code Name Jack
显示剩余3条评论

6
在这种情况下,CLR通过编译本机导出项__declspec(dllexport)时编译器注入的thunk进行加载。 这样做是可以的,但并不特别快。
CLR将寻找.config文件以初始化主AppDomain,并将查找MyCoolApp.exe.config,尽管这根本不是托管可执行文件。 您可以使用<probing>元素来添加子目录以搜索程序集。

0

我曾经遇到过这个问题,但是有一个进退两难的情况,我的托管C++ DLL会立即崩溃,因为它直接引用了找不到的.NET DLL。我甚至无法调用任何.NET代码,所以Ed的解决方案最初对我没有起作用。

诀窍在于创建第二个托管C++ DLL,不引用任何特殊的.NET DLL。没有这些引用,它可以正常加载。在第二个DLL中运行Ed的代码来设置程序集解析器。然后像以前一样加载有问题的托管C++ DLL,就可以正常工作了。


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