我在另一个线程内部创建线程时遇到问题。通常我可以这样做,但这个问题的原因是因为启动这些线程的DLL Incremented Reference Count
。我需要在这个DLL中启动多个线程。如何解决这个问题,并且能够在我的项目中需要时发出多个CreateThread()
,而不会因为我的DLL中的Incremented Reference Count
而遇到问题?
以下是我在DLL文件中写入增量引用计数的函数:
BOOL IncrementReference( HMODULE hModule )
{
if ( hModule == NULL )
return FALSE;
TCHAR ModulePath[ MAX_PATH + 1 ];
if ( GetModuleFileName( hModule , ModulePath , MAX_PATH ) == 0 )
return FALSE;
if ( LoadLibrary( ModulePath ) == NULL )
return FALSE;
return TRUE;
}
根据要求,这是一个重建我面临的问题的PoC程序。我真的希望这会帮助你们指出我的解决方案。另外,请注意,由于我所针对的应用程序中的条件(已在该应用程序中设置的挂钩),DLL正在卸载,因此我的线程首先需要递增引用计数。
另外,我不能在主线程中运行多个操作,因为它有自己的功能需要处理,另外需要另一个线程来处理其他事情。它们也必须同时运行,因此我需要修复在增量DLL中创建多个线程的问题。
// dllmain.cpp : Defines the entry point for the DLL application.
#pragma comment( linker , "/Entry:DllMain" )
#include <Windows.h>
#include <process.h>
UINT CALLBACK SecondThread( PVOID pParam )
{
MessageBox( NULL , __FUNCTION__ , "Which Thread?" , 0 );
return 0;
}
UINT CALLBACK FirstThread( PVOID pParam )
{
MessageBox( NULL , __FUNCTION__ , "Which Thread?" , 0 );
_beginthreadex(0, 0, &SecondThread, 0, 0, 0);
return 0;
}
BOOL IncrementReference( HMODULE hModule )
{
if ( hModule == NULL )
return FALSE;
TCHAR ModulePath[ MAX_PATH + 1 ];
if ( GetModuleFileName( hModule , ModulePath , MAX_PATH ) == 0 )
return FALSE;
if ( LoadLibrary( ModulePath ) == NULL )
return FALSE;
return TRUE;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
if (IncrementReference(0))
_beginthreadex(0, 0, &FirstThread, 0, 0, 0);
}
break;
}
return TRUE;
}
如您所见,代码从不执行SecondThread
函数。问题是,为什么?还有什么可以解决它?
答案 0 :(得分:2)
#pragma comment( linker , "/Entry:DllMain" )
这是一个非常糟糕的主意,DLL的正确入口点实际上是而不是 DllMain()。你必须记住,WinMain和DllMain只是占位符名称。 Microsoft记录可执行文件入口点相关性的一种方法。按照惯例,你在程序中使用相同的名字,每个人都会理解他们的所作所为。
但是在C或C ++程序中有一个非常重要的附加细节,需要首先初始化CRT(C运行时库)。 在之前,您可以运行任何可能进行CRT函数调用的代码。与_beginthreadex()
一样。
换句话说,默认/ ENTRY链接器选项是不是 DllMain()。 DLL的真正入口点是_DllMainCRTStartup()
。 CRT内部的一个函数负责所需的初始化,然后调用DllMain()。如果你在程序中写了一个,那就是那个运行的程序。如果你没有,那么CRT中的虚拟链接就会被链接。
当您进行CRT功能调用且CRT未初始化时,所有投注均已关闭。您必须删除该#pragma,以便链接器使用正确的入口点。
答案 1 :(得分:0)
根据MSDN,您既不会在DllMain中调用LoadLibrary也不会调用CreateThread - 您的代码同时执行这两项操作!
答案 2 :(得分:0)
发布的MCVE有三个问题:
第一个是一个简单的错误,您正在调用IncrementReference(0)
而不是IncrementReference(hModule)
。
第二个是rundll32
没有使用的入口点;入口点参数是必需的,或者rundll32
不会工作(我不认为它甚至加载了DLL)。
第三个是汉斯指出的#pragma
。
修复IncrementReference()调用后,删除#pragma
并添加一个入口点:
extern "C" __declspec(dllexport) void __stdcall EntryPoint(HWND, HINSTANCE, LPSTR, INT)
{
MessageBoxA( NULL , __FUNCTION__ , "Which Thread?" , 0 );
}
然后您可以像这样运行DLL:
rundll32 testdll.dll,_EntryPoint@16
这适用于我的机器; EntryPoint,FirstThread和SecondThread都生成消息框。确保你没有过早地从EntryPoint中删除消息框,因为这将导致应用程序退出,并使用其他线程。
对LoadLibrary的调用仍然不合适,但在这种情况下似乎没有任何副作用(可能是因为保证已经加载了相关库)。
(上一页)答案:
只需将对DllMain的IncrementReference调用移至FirstThread即可修复MCVE。这是解决问题的唯一安全和正确的方法。
附录:正如汉斯指出的那样,您还需要删除/Entry
pragma。
(冗余?)评论:
如果正在加载DLL的应用程序在FirstThread可以运行之前卸载DLL的程度不正常,并且为了论证而假设您无法修复它,唯一可行的选择是解决问题 - 例如,DllMain可以暂停进程中的所有其他线程,以便它们无法卸载DLL,并在调用IncrementReference后从FirstThread恢复它们。
或者您可以尝试挂钩FreeLibrary,或者对加载器进行逆向工程并直接处理引用计数,或删除应用程序放置的挂钩,或者在DllMain中手动加载DLL的单独副本(使用您自己的DLL加载器)而不是一个Windows提供的)或开始一个单独的过程并在那里工作,或者哦,毫无疑问还有其他任何可能性,但在那一点上,我担心这个问题确实太宽泛了Stack Overflow,特别是因为您无法向我们提供应用程序正在执行的操作的真实细节。