c++ - 从dll载入dll?

标签 c++ multithreading loadlibrary dll

从DLL加载DLL的最佳方法是什么?

我的问题是我无法在process_attach上加载dll,也无法从主程序加载dll,因为我无法控制主程序源。因此,我也不能调用非dllmain函数。

最佳答案

在评论中进行了所有辩论之后,我认为最好以“真实”的答案概括我的立场。
首先,仍然不清楚为什么需要使用LoadLibrary在DllMain中加载dll。这绝对不是一个好主意,因为您的DllMain在另一个对LoadLibrary的调用中运行,该调用持有加载程序锁定,如documentation of DllMain所述:

During initial process startup or after a call to LoadLibrary, the system scans the list of loaded DLLs for the process. For each DLL that has not already been called with the DLL_PROCESS_ATTACH value, the system calls the DLL's entry-point function. This call is made in the context of the thread that caused the process address space to change, such as the primary thread of the process or the thread that called LoadLibrary. Access to the entry point is serialized by the system on a process-wide basis. Threads in DllMain hold the loader lock so no additional DLLs can be dynamically loaded or initialized.

The entry-point function should perform only simple initialization or termination tasks. It must not call the LoadLibrary or LoadLibraryEx function (or a function that calls these functions), because this may create dependency loops in the DLL load order. This can result in a DLL being used before the system has executed its initialization code. Similarly, the entry-point function must not call the FreeLibrary function (or a function that calls FreeLibrary) during process termination, because this can result in a DLL being used after the system has executed its termination code.

(添加了重点)
因此,这就是为什么它被禁止的原因;有关更清晰,更深入的说明,请参见thisthis,有关其他示例,如果您不遵守DllMain中的这些规则会发生什么,请参见Raymond Chen's blog中的一些帖子。
现在,关于拉基斯的答案。
正如我已经重复了好几次一样,您认为的是DllMain,不是dll的真正DllMain。相反,它只是由dll的实际入口点调用的函数。反过来,CRT会自动执行此任务以执行其附加的初始化/清除任务,其中包括构造全局对象和类的静态字段(实际上,从编译器的角度来看,所有这些几乎都是相同的)事物)。在完成清理任务之后(或之前,清理任务),它将调用您的DllMain。
它是这样的(显然我没有写所有的错误检查逻辑,只是为了展示它是如何工作的):
/* This is actually the function that the linker marks as entrypoint for the dll */
BOOL WINAPI CRTDllMain(
  __in  HINSTANCE hinstDLL,
  __in  DWORD fdwReason,
  __in  LPVOID lpvReserved
)
{
    BOOL ret=FALSE;
    switch(fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            /* Init the global CRT structures */
            init_CRT();
            /* Construct global objects and static fields */
            construct_globals();
            /* Call user-supplied DllMain and get from it the return code */
            ret = DllMain(hinstDLL, fdwReason, lpvReserved);
            break;
        case DLL_PROCESS_DETACH:
            /* Call user-supplied DllMain and get from it the return code */
            ret = DllMain(hinstDLL, fdwReason, lpvReserved);
            /* Destruct global objects and static fields */
            destruct_globals();
            /* Destruct the global CRT structures */
            cleanup_CRT();
            break;
        case DLL_THREAD_ATTACH:
            /* Init the CRT thread-local structures */
            init_TLS_CRT();
            /* The same as before, but for thread-local objects */
            construct_TLS_globals();
            /* Call user-supplied DllMain and get from it the return code */
            ret = DllMain(hinstDLL, fdwReason, lpvReserved);
            break;
        case DLL_THREAD_DETACH:
            /* Call user-supplied DllMain and get from it the return code */
            ret = DllMain(hinstDLL, fdwReason, lpvReserved);
            /* Destruct thread-local objects and static fields */
            destruct_TLS_globals();
            /* Destruct the thread-local CRT structures */
            cleanup_TLS_CRT();
            break;
        default:
            /* ?!? */
            /* Call user-supplied DllMain and get from it the return code */
            ret = DllMain(hinstDLL, fdwReason, lpvReserved);
    }
    return ret;
}
这没有什么特别的:它也发生在普通的可执行文件中,您的主文件由真正的入口点调用,该入口点由CRT保留,用于完全相同的目的。
现在,从中可以清楚地知道Rakis解决方案为何行不通:全局对象的构造函数由真正的DllMain调用(即dll的实际入口点,这是DllMain上有关MSDN页面的那个入口点)讨论),因此从那里调用LoadLibrary与从假DllMain调用具有完全相同的效果。
因此,遵循该建议,您将获得在DllMain中直接调用LoadLibrary的相同负面影响,并且还将问题隐藏在一个看似无关的位置,这将使下一个维护人员很难找到此错误的位置。位于。
至于delayload:这可能是个主意,但您必须非常小心,不要在DllMain中调用引用的dll的任何函数:实际上,如果这样做,则会触发对LoadLibrary的隐藏调用,该调用将具有相同的功能。直接调用它的负面影响。
无论如何,我认为,如果您需要在dll中引用某些函数,最好的选择是针对其导入库进行静态链接,因此加载程序将自动加载它而不会给您带来任何问题,并且它将自动解决任何奇怪的依赖关系可能出现的链条。
即使在这种情况下,也不能在DllMain中调用此dll的任何函数,因为不能保证它已经被加载;实际上,在DllMain中,您只能依靠被加载的kernel32,并且也许可以完全依靠dll来确保在发出要加载dll的LoadLibrary之前,调用者已经被加载(但是仍然不应该依赖于此,因为您的dll也可能由不符合这些假设的应用程序加载,而只是想要加载,例如load a resource of your dll without calling your code)。
正如我之前链接的文章所指出的那样,
The thing is, as far as your binary is concerned, DllMain gets called at a truly unique moment. By that time OS loader has found, mapped and bound the file from disk, but - depending on the circumstances - in some sense your binary may not have been "fully born". Things can be tricky.

In a nutshell, when DllMain is called, OS loader is in a rather fragile state. First off, it has applied a lock on its structures to prevent internal corruption while inside that call, and secondly, some of your dependencies may not be in a fully loaded state. Before a binary gets loaded, OS Loader looks at its static dependencies. If those require additional dependencies, it looks at them as well. As a result of this analysis, it comes up with a sequence in which DllMains of those binaries need to be called. It's pretty smart about things and in most cases you can even get away with not following most of the rules described in MSDN - but not always.

The thing is, the loading order is unknown to you, but more importantly, it's built based on the static import information. If some dynamic loading occurs in your DllMain during DLL_PROCESS_ATTACH and you're making an outbound call, all bets are off. There is no guarantee that DllMain of that binary will be called and therefore if you then attempt to GetProcAddress into a function inside that binary, results are completely unpredictable as global variables may not have been initialized. Most likely you will get an AV.

(再次强调)
顺便说一下,关于Linux vs Windows的问题:我不是Linux系统编程专家,但是我认为这方面并没有什么不同。
仍然有DllMain的等效项(_init和_fini函数),它们是-巧合! -由CRT自动获取,由CRT自动从_init调用全局对象的所有构造函数以及标有__attribute__构造函数的函数(在某种程度上等效于Win32中提供给程序员的“假” DllMain)。 _fini中的析构函数进行类似的处理。
由于在DLL加载仍在进行时也会调用_init(dlopen尚未返回),因此我认为您在此处可以执行的操作受到类似的限制。不过,在我看来,在Linux上,问题的困扰不大,因为(1)您必须明确选择加入类似DllMain的函数,因此您不会立即被滥用;以及(2)Linux应用程序,据我所见,往往使用较少的动态加载dll。
简而言之
没有“正确”的方法将允许您引用DllMain中的kernel32.dll以外的任何dll。
因此,不要对DllMain做任何重要的事情,也不要直接(即在CRT调用的“您的” DllMain中)也不要间接地(在全局类/静态字段构造函数中),尤其不要再次加载其他dll,也不要直接(通过LoadLibrary)都不是间接的(调用延迟加载的dll中的函数,这会触发LoadLibrary调用)。
将另一个dll作为依赖项加载的正确方法是-doh! -将其标记为静态依赖项。只需链接到其静态导入库,并至少引用其功能之一:链接器会将其添加到可执行镜像的依赖表中,加载器将自动加载它(在调用DllMain之前或之后初始化它,不需要知道它,因为您不必从DllMain调用它。
如果由于某种原因这是不可行的,那么仍然有delayload选项(具有我之前说过的限制)。
如果您仍然出于某种未知原因仍然需要在DllMain中调用LoadLibrary,那么,继续前进,在您的脚下射击,就在您的系中。但是不要告诉我我没有警告过你。

我忘记了:关于该主题的另一个基本信息来源是Microsoft的[创建DLL最佳实践] [6]文档,该文档实际上几乎只讨论了加载器,DllMain,加载器锁及其相互作用。请查看有关该主题的其他信息。

附录
No, not really an answer to my question. All it says is: "It's not possible with dynamic linking, you must link statically", and "you musn't call from dllmain".

这是对问题的答案:在强加的条件下,您将无法做自己想做的事情。简而言之,在DllMain中,您不能调用*除kernel32函数之外的任何函数*。时期。
Although in detail, but I'm not really interested in why it doesn't work,

相反,您应该这样做,因为了解以这种方式制定规则的原因可以避免重大错误。
fact is, the loader is not resolving dependenies correctly and the loading process is improperly threaded from Microsoft's part.

不,亲爱的,加载器正确完成了它的工作,因为**之后* LoadLibrary返回,所有依赖项都已加载,并且一切准备就绪。加载程序尝试按依赖关系顺序调用DllMain(以避免依赖于DllMain中其他dll的损坏的dll出现问题),但是在某些情况下,这根本是不可能的。
例如,可能有两个相互依赖的dll(例如A.dll和B.dll):现在,谁的DllMain首先调用?如果加载程序首先初始化A.dll,然后在其DllMain中将其初始化为B.dll中的函数,则可能会发生任何事情,因为B.dll尚未初始化(尚未调用其DllMain)。如果我们扭转这种情况,同样适用。
在其他情况下可能会出现类似的问题,因此简单的规则是:不要在DllMain中调用任何外部函数,DllMain仅用于初始化dll的内部状态。
The problem is there is no other way then doing it on dll_attach, and all the nice talk about not doing anything there is superfluous, because there is no alternative, at least not in my case.

讨论的过程是这样的:您说“我想在实域上求解x ^ 2 + 1 = 0的方程”。每个人都说你不可能您说这不是答案,并且要怪数学。
有人告诉您:嘿,您可以,这是一个窍门,解决方案只是+/- sqrt(-1);每个人都对这个答案不满意(因为您的问题是错误的,我们不在实际范围内),您要怪罪谁不赞成。我将根据您的问题向您解释为什么该解决方案不正确,以及为什么无法在实际领域中解决此问题。您说您不关心为什么不能完成它,您只能在实际领域中做到这一点,并且再一次指责数学。
现在,由于经过一百万次的解释和重申,在您的条件下您的答案没有解决方案,您可以向我们解释为什么在地球上您“不得不”做这样的蠢事,例如在DllMain 中加载dll?经常会出现“不可能”的问题,因为我们选择了一条奇怪的途径来解决另一个问题,这使我们陷入僵局。如果您解释了大局,我们可能会建议一个更好的解决方案,该解决方案不涉及在DllMain中加载dll。
PS: If I statically link DLL2 (ole32.dll, Vista x64) against DLL1 (mydll), which version of the dll will the linker require on older operating systems?

存在的那个(显然我假设您正在编译32位);如果找到的dll中没有应用程序所需的导出功能,则不会加载您的dll(LoadLibrary失败)。

附录(2)
Positive on injection, with CreateRemoteThread if you wanna know. Only on Linux and Mac the dll/shared library is loaded by the loader.

将dll添加为静态依赖项(从一开始就提出了建议),使得它完全可以像Linux/Mac一样由加载程序加载,但是问题仍然存在,因为正如我所解释的那样,在DllMain中您仍然无法依赖除了kernel32.dll以外的任何东西(即使加载器通常足够智能,可以首先初始化依赖项)。
仍然可以解决问题。使用CreateRemoteThread创建线程(实际上调用LoadLibrary来加载您的dll);在DllMain中,使用某些IPC方法(例如,名为共享内存,其句柄将保存在init函数中要关闭的位置),将dll将提供的“真实” init函数的地址传递给注入(inject)程序。 DllMain然后将退出而无需执行任何其他操作。相反,注入(inject)器应用程序将使用CreateRemoteThread提供的句柄通过WaitForSingleObject等待远程线程的结束。然后,在结束远程线程之后(因此LoadLibrary将完成,并且所有依赖项都将被初始化),注入(inject)程序将从DllMain创建的命名共享内存中读取远程进程中init函数的地址,并启动它与CreateRemoteThread。
问题:在Windows 2000上,禁止使用DllMain中的命名对象,因为
In Windows 2000, named objects are provided by the Terminal Services DLL. If this DLL is not initialized, calls to the DLL can cause the process to crash.

因此,该地址可能必须以另一种方式传递。一个非常干净的解决方案是在dll中创建一个共享数据段,将其加载到注入(inject)器应用程序和目标应用程序中,然后将其放入所需的地址中。显然,该dll必须首先加载到注入(inject)器中,然后再加载到目标中,因为否则,“正确”地址将被覆盖。
可以做的另一种非常有趣的方法是在其他进程内存中编写一个小的函数(直接在程序集中),该函数调用LoadLibrary并返回我们的init函数的地址。由于我们在此处编写了它,因此我们也可以使用CreateRemoteThread对其进行调用,因为我们知道它在哪里。
我认为,这是最好的方法,也是最简单的方法,因为已经在此nice article中编写了代码。看看它,这很有趣,它可能会解决您的问题。

关于c++ - 从dll载入dll?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2674736/

相关文章:

c++ - C++ 上的用户输入结束循环

C# : Show dialog on UI thread from another thread

java - 如何从 JFrame 中删除 JPanel?

c - 多线程 - 使用线程执行将文件内容存储在 char **array 中的函数?

java - 如何在 Java 中反转 System.loadLibrary

c++ - 指针 vector ,其元素用新的 : what happens when assigning a new value? 初始化

c++ - 在未初始化的对象上使用 std::bind 是否安全?

c++ - 初始化一个字符指针 C++

c++ - 如何消除 LoadLibrary() 函数中变量类型不兼容的错误?

c++ - 获取 LoadLibrary 无法加载 DLL 的原因