由于您具体指的是注入 DLL,因此除了已经说过的内容之外,我还有一些意见供您参考。
首先,让我们确保线程、进程和模块的概念清晰。
线程基本上是代码运行的直接环境。诸如处理器寄存器和堆栈变量的当前状态(例如,在大多数情况下,函数中的局部变量,以及当前执行在代码中的位置)之类的东西属于线程。还有其他资源通常具有线程关联性,例如 windows。这在很大程度上取决于所讨论的资源是否以及它们具有什么样的线程亲和性。
假设您编写了一个简单的 hello world 程序。它将在一个线程中运行,该线程从头到尾遍历您的程序并打印“Hello World”。现在让我们假设您要编写一个程序,该程序缓慢地写入“Hello World”,每秒一个字符,但同时下载一个文件。然后你可以创建第二个线程,让一个线程缓慢输出“Hello World”,一个线程下载文件。这意味着执行可以并行发生,具有不同的本地状态 - 一个线程当前在您的 printHelloWorld 函数内,一个线程在 downloadFile 内。
进程基本上是一个或多个线程的容器。它将它们捆绑在一个使用相同虚拟内存的共享环境中(这意味着例如代码中的全局变量可以从所有线程访问,但这需要仔细同步以避免竞争条件)并共享资源,例如文件句柄进程中的线程创建。因此,您之前的 hello-world-and-download 程序将在 1 个进程中有 2 个线程,例如共享控制台,并在任务管理器中被视为一个实体。
模块是一个包含可执行代码(在大多数情况下)并被加载到进程中的文件。通常,在一个进程中有一个 EXE 文件和几个 DLL 文件作为模块加载。 DLL 文件和 EXE 文件在技术上几乎相同,但 EXE 文件是进程启动的基础,而 DLL 文件是导出某些功能的库,可供其他模块使用。由于我说模块被加载到进程中,这意味着一个模块可以被进程中的所有线程访问,并且它本身没有线程亲和性——在我们前面的例子中,当第二个线程下载文件时,它可能正在调用 HTTP 网络 DLL,然后其代码将在第二个线程中运行。 Windows 会自动将许多模块加载到每个进程中,而其他模块可能由编译器的某些功能加载。
好的,那么,回到你的问题:
是否需要在 dll 文件中创建线程[...]?
本质上,使用 DLL 与是否需要创建新线程无关。这取决于您想做什么 - 如果您需要与正在运行的任何其他代码并行执行一些耗时的任务,那么您需要为其创建一个新线程,否则就没有必要了。
[...]还是不行?
如前所述,您可以根据需要创建新线程(它会起作用),但这并不是使用 DLL 的必要条件。
根据我的理解,主线程将从主机进程中运行,对吗?
宿主进程的主线程当然会在宿主进程中。 (虽然技术上没有“主线程”,因为让进程中的第一个线程创建第二个线程然后终止是完全有效的,所以只有第二个线程会再运行,你通常会让第一个线程通过进程的整个生命周期,在这种情况下您可能可以将其称为“主线程”。)但是,当前运行的代码位于哪个模块中,将取决于线程当前正在做什么。
让我回到“注入”的问题:前面的答案似乎假设了一个更“正常”的环境,您的 DLL 只是链接到程序并打算由它加载。在这种情况下,您的 DLL 的初始化例程(在将模块加载到进程中时自动运行)将只在“主线程”中运行,可能在进程的实际工作开始之前。
但是,当您注入 DLL 时,情况会有所不同。这取决于您如何进行注射:
- 如果您通过修改宿主EXE 的导入表来注入DLL,那么您的DLL 将按照我刚才所说的“正常”方式加载。因此,您可以期望您的初始化例程在进程启动期间在主线程中运行。
- 如果您使用 AppInit_DLLs 注册表项注入 DLL,则相同。
- 如果您通过启动挂起的主机进程来注入 DLL,然后编写一个存根以将 DLL 加载到进程的内存中并使用
SetThreadContext 将指令指针指向它,那么同样的事情。
- 如果您通过远程调用目标进程内的
LoadLibrary 的方式注入DLL,使用CreateRemoteThread,那么,正如名称所暗示的那样,您正在进程内创建一个新线程 .在这个线程中,LoadLibrary 将加载您的 DLL 并调用您的初始化例程,因此在这种情况下,您的初始化例程确实会在“主线程”之外的新线程中运行。