您的位置:首页 > 其它

DLL之多线程

2015-08-03 10:44 204 查看
动态连接库有其自身的优点,如节省内存(例如,多个应用程序可以共享一个DLL文件,真正实现了资源"共享",大大缩小了应用程序的执行代码,有效地利用了内存,而且DLL文件作为一个单独的程序模块,封装性、独立性好,有利于提高软件开发和维护的效率。)、支持多语种等功能,而且,当DLL中的函数改变后,只要不是参数的改变,调用函数并不需要重新编译。

Non-MFC DLL:指的是不用MFC的类库结构,直接用C语言写的DLL,其输出的函数一般用的是标准C接口,并能被非MFC或MFC编写的应用程序所调用。Regular DLL:和下述的Extension Dlls一样,是用MFC类库编写的。明显的特点是在源文件里有一个继承CWinApp的类。其又可细分成静态连接到MFC和动态连接的.静态连接到 MFC的动态连接库只被VC的专业般和企业版所支持。Extension
DLL:用来实现从MFC所继承下来的类的重新利用,也就是说,用这种类型的动态连接库,可以用来输出一个从MFC所继承下来的类。Extension DLL使用MFC的动态接版本所创建的,并且它只被用MFC类库所编写的应用程序所调用。

当响应下列事件时,动态连接库(DLLMain)被调用:

DLL_PROCESS_ATTACH: 进程被调用

DLL_THREAD_ATTACH: 线程被调用

DLL_PROCESS_DETACH: 进程被停止

DLL_THREAD_DETACH: 线程被停止

当进程被创建时,系统也为该进程创建了一个互斥对象。每个进程都有它自己的互斥对象。进程互斥对象的一个作用是,序列化在需要调用DllMain的 4种情况下DllMain的执行。

Non-MFC DLLs的通用编写方法:

BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)

{

switch( ul_reason_for_call ) {

case DLL_PROCESS_ATTACH:

.......

case DLL_THREAD_ATTACH:

.......

case DLL_THREAD_DETACH:

.......

case DLL_PROCESS_DETACH:

.......

}

return TRUE;

}

在应用文件中加入你所想要输出的函数或变量或c++类:

加入一个新的输出函数了:

void _declspec(dllexport) JustSoSo()

{

MessageBox(NULL,"It's so easy!","Hahaha......",MB_OK);

}

要输出一个类也可以,如下:

class _declspec(dllexport) Easy

{

//add your class definition...

};

对DLL的调用分为两种,一种是显式的调用,一种是隐式的调用。所谓显式的调用,是指在应用程序中用LoadLibrary或MFC提供的AfxLoadLibrary显式的将自己所做的动态连接库调近来,动态连接库的文件名即是上两函数的参数,再用GetProcAddress()获取想要引入的函数。自此,你就可以象使用如同本应用程序自定义的函数一样来调用此引入函数了。在应用程序退出之前,应该用FreeLibrary或MFC提供的AfxLoadLibrary释放动态连接库。隐式的调用则需要把产生动态连接库时产生的.LIB文件加入到应用程序的工程中,想使用DLL中的函数时,只须说明一下,如上:说明输出函数void
JustSoSo();隐式调用不需要调用LoadLibrary()和FreeLibrary()。

隐式说明调用的方法比较简单。显式调用,当DLL改变后,应用程序须从新编译。并且,所调用的DLL在应用程序加载的同时被加载到内存中。当应用程序调用的DLL比较多时,装入的过程十分慢。隐式的调用,当应用程序不知道所要装入的DLL或隐式调用不成功时,允许用户指定所要加载的动态连接库,比较灵活。

为DllMain换名

一个Dll的入口函数名是可以自己定义的。

打开VC++菜单Project/Settings/Link tab/ Output in the Category box.首先要说明一点,虽然DllMain可以换成其他函数名,但函数的参数和返回值必须和DllMain一样。而且这个函数要为__stdcall类型(DllMain本身也是__stdcall类型)。

按OK后,如果马上编译的话会出现如下错误:
LIBCMTD.lib(crt0.obj) : error LNK2001: unresolved external symbol _main
Debug/Dll.dll : fatal error LNK1120: 1 unresolved externals
打开VC++菜单Project/Settings/C/C++选项卡,如下图,在Project Options:末尾的地方添加”/D”(图中蓝色高亮的地方),要注意位置.

死锁

在一个多线程程序中,某个DLL被加载进程地址空间时,该DLL的DllMain启动了一个线程,然后立即调用一个应答事件对象的WaitForSingleObject函数,以确认在继续进行其余的DllMain处理之前,新产生的线程能够正确地执行一些操作。

/----------------------start------------

HANDLE g_thread_handle =NULL; // 该DLL内部线程的句柄

DWORD g_thread_id =0; // 该DLL内部线程的ID

HANDLEg_hEvent=NULL;// 应答事件的句柄

DWORDWINAPIInSideDll_ThreadProc( LPVOIDp )

{

/* 表示一些操作。

如果“---- operations.----”被打印到Output窗口中了,

说明本线程函数在被执行了。 */

OutputDebugString(“---- operations.---- \n”);

/*InSideDll_ThreadProc的操作完成后,

通知在g_hEvent处等待的线程,可以继续运行了。*/

SetEvent(g_hEvent);

return1;

}

BOOLAPIENTRYDllMain( HANDLEhModule,

DWORDul_reason_for_call, LPVOIDlpReserved )

{

switch (ul_reason_for_call)

{

caseDLL_PROCESS_ATTACH:

//DLL正在映射到进程地址空间中

{

// 禁止线程库调用,

DisableThreadLibraryCalls((HINSTANCE)hModule);

// 创建DLL内线程使用的事件对象

g_hEvent = ::CreateEvent( NULL, FALSE, FALSE, _T("hello11" ));

//创建DLL内线程对象

g_thread_handle = ::CreateThread(NULL,0,

InSideDll_ThreadProc,(LPVOID)0,0, &( g_thread_id) ) ;

// 等待刚创建的线程完成相关操作

::WaitForSingleObject( g_hEvent, INFINITE );

// 清除资源

::CloseHandle(g_thread_handle);

g_thread_id = 0 ;

g_thread_handle = NULL ;

::CloseHandle(g_hEvent);

g_hEvent=NULL;

}

break;

caseDLL_PROCESS_DETACH:

// DLL正在从进程地址空间中卸载

break;

}

returnTRUE;

}

//----------------------end------------

如果对这样的程序进行调试,通过Call Stack窗口可以看到该程序正在等待DllMain内部的线程处理,而Output窗口中也没有打印出“---- operations.---- ”语句。可见线程函数InSideDll_ThreadProc根本就没有得到运行的机会。

在程序运行过程中,第一个线程对LoadLibrary的调用引起操作系统获取进程互斥对象并以DLL_PROCESS_ATTACH值调用该DLL的DllMain。该DLL的DllMain函数产生第二个线程。无论何时当进程产生一个新线程时,操作系统将获取进程互斥对象,以便于它可以为DLL_THREAD_ATTACH值调用每个加载的DLL的DllMain函数。在这个特定的程序中,第二个线程阻塞,因为第一个线程还保持着进程互斥对象。不幸的是,第一个线程然后调用WaitForSingleObject确认第二个线程能够正确地完成一些操作。因为第二个线程被阻塞在进程互斥对象上,这个进程互斥对象还被第一个线程所持有,而第一个线程要等待第二个线程从而也被阻塞,结果就导致了死锁。

装载DLL过程中的多线程死锁是因为DllMain的顺序调用规则,但是很少人了解卸载DLL过程中的多线程死锁也是由于同样的原因。

例如,如果一个DLL的DllMain的代码写成下面的形式,且进程中有至少一个DLL的DllMain没有调用DisableThreadLibraryCalls函数的话,那么卸载该DLL过程中就会因为DllMain的顺序操作特性带来DLL内部线程没有完全退出的错误。

//----------------------start ------------

HANDLE g_thread_handle =NULL; // 该DLL内部线程的句柄

DWORD g_thread_id =0; // 该DLL内部线程的ID

HANDLE g_hEvent=NULL;// 应答事件的句柄

DWORD WINAPI InSideDll_ThreadProc( LPVOID p )

{

while(1){

// 收到通知就退出线程函数

DWORD ret = ::WaitForSingleObject( g_hEvent, INFINITE );

if(WAIT_TIMEOUT = =ret|| WAIT_OBJECT_0 = =ret) break;

}

return true ;

}

BOOL APIENTRY DllMain( HANDLE hModule,

DWORD ul_reason_for_call,

LPVOID lpReserved

)

{

switch (ul_reason_for_call)

{

case DLL_PROCESS_ATTACH:

//线程正在映射到进程地址空间中

{

// 创建DLL内的线程使用的事件对象

g_hEvent = ::CreateEvent( NULL, FALSE, FALSE, _T("hello11" ));

//创建DLL内的线程对象

g_thread_handle = ::CreateThread(NULL,0,

InSideDll_ThreadProc,(LPVOID)0,0, &( g_thread_id) ) ;

// 禁止线程库调用,

DisableThreadLibraryCalls((HINSTANCE)hModule);

}

break;

case DLL_PROCESS_DETACH:

// DLL正在从进程地址空间中卸载

{

// 通知内部的线程g_thread_handle 退出

::SetEvent(g_hEvent);

// 等待内部的线程g_thread_handle 退出

::WaitForSingleObject(g_thread_handle, INFINITE ) ;

// 清除资源

::CloseHandle(g_thread_handle);

g_thread_id = 0 ;

g_thread_handle = NULL ;

::CloseHandle(g_hEvent);

g_hEvent=NULL;

}

break;

}

return TRUE;

}

//----------------------end ------------

创建一个DLL工程时,编译环境自动生成一个工程名.cpp文件和一个StdAfx.h头文件,而不会生成工程名.h头文件。而且StdAfx.h头文件不是必须的,况且会给编译带来麻烦,因此一般把它去掉。在"Progecd->Settings->C/C++选项卡->Category:Precompiled
Headers"中,选中"Not using precompiled headers"
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: