Windows API 编程-----DLL编程之禁止加载本身

  和可执行文件同样,动态连接库也有本身的入口地址,若是系统或者当前进程的某个线程调用LoadLibrary函数加载或者使用FreeLibrary卸载该动态连接库的时候,会自动使用3个特定的堆栈参数跳转到该地址来运行。入口函数是为了完成动态连接库代码的初始化和蔼后工做,好比卸载后的资源释放。ios

  这三个参数具备特殊的含义。数据库

  BOOL APIENTRY DllMain(api

    HMODULE hModule,函数

    DWORD ul_reason_for_call,性能

    LPVOID lpReserved
  )
spa


  第一个参数是实例句柄,这个所谓的句柄实际上就是该加载进程在内存中的景象地址,注意进程句柄和动态连接库的模块句柄是不同的。好比,若是用户想使用hModule去加载该动态连接库中的一个位图资源,那将永远得不到结果。动态连接库的模块句柄依附于加载进程,脱离进程独立于系统空间中的动态连接库是不存在的,也没有意义。动态连接库老是要加载到进程中的某个地址,该地址就是动态连接库的模块句柄,这个句柄不一样于进程实例句柄,它是相对于进程入口地址的一个偏移量,只有经过模块句柄才能寻址到该动态连接库中嵌套的各类窗口或者其余资源。
线程

  ul_reason_for_call 参数表示该动态连接库是在什么条件下被加载的,即加载的缘由。当用户显式采用LoadLibrary(Ex)函数加载一个动态连接库或者由进程自己隐式加载该库时,该入口函数就会被调用,入口参数 ul_reason_for_call 这时就等于DLL_PROCESS_ATTACH,入口函数的返回值就是LoadLibrary函数的返回值,它们都是经过EAX寄存器来传递的。根据LoadLibrary(Ex)函数的说明,这个函数若是返回一个NULL值,表示加载失败。实际上在DllMain 函数中,只要返回FALSE,LoadLibrary(Ex)函数就会返回一个NULL值。若是动态连接库须要排他调用,能够在入口点函数中判断加载进程的文件名,或者履行其余合法性检查;若是不但愿被某个进程调用,能够直接在DllMain函数中返回FALSE。DLL_PROCESS_ATTACH分支每每用于实现系统初始化,好比创建数据库连接,建立钩子函数,分配系统资源,保存入口进程实例句柄等。code

  一样,当进程再也不使用该动态连接库,好比调用ExitProcess函数或者显式调用FreeLibrary函数时,系统又会使用DLL_PROCESS_DETACH参数执行相关分支。用于释放系统资源、断开数据库连接、卸载钩子函数、关闭文件、释放内存等。blog

 

  注意:DLL_PROCESS_DETACH分支的执行是有条件的,进程的意外终止,并不会执行DLL_PROCESS_DETACH分支语句,这样分支中的关闭资源、断开连接、关闭文件等善后语句就没法执行,这将会形成一些数据的潜在丢失。所以除非万不得已,不要轻易使用TerminateProcess函数终止进程执行。进程

  若是线程建立时DLL完成到进程空间的映射,这样系统会使用DLL_THREAD_ATTACH进入入口点。而系统执行ExitThread函数时会执行DLL_THREAD_DETACH分支。一样的规则适用于线程,用户不要轻易使用TerminateThread函数,这也会致使一些不可预知的内存泄漏或者资源没有释放和数据丢失。

  若是用户不在意DLL_THREAD_DETACH和DLL_THREAD_ATTACH通知,又但愿提升建立和撤销线程的性能,能够在收到DLL_THREAD_ATTACH通知时,调用DisableThreadLibraryCalls函数。

  注意:关于lpReserved参数----在静态(隐式)加载和调用LoadLibray函数实现动态(显式)加载动态库时,这两种状况是不同的,动态加载时这个值为0。若是用户但愿本身编写的的动态连接库只能被动态加载或者只能被静态加载,能够经过判断这个参数来实现。

代码示例:

dllmain.cpp(生成mydll.dll):

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h" #include<stdio.h> #include<Psapi.h> #include<Windows.h> typedef void(*pFnPtr)(char*); BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { char szName[MAX_PATH]; if (lpReserved != 0) { //只容许动态加载,静态加载将提示错误并退出
        MessageBox(NULL, "只容许动态加载", "Sorry", MB_OK); return FALSE; } GetModuleBaseName(GetCurrentProcess(),NULL ,szName,MAX_PATH);//获取当前主进程的base name
    switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: //故意将调用进程的名字改成test.exe,该动态连接库将没法加载。
        if (strcmp(szName, "test.exe") == 0) return FALSE; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } extern "C" { //主调进程将调用该函数,该函数再调用主调进程的ExeFn函数
    _declspec(dllexport) int fnImportingDLL() { MessageBox(NULL, "Dll Function called!", "mydll", MB_OK); pFnPtr fn = (pFnPtr)::GetProcAddress(GetModuleHandle(NULL), "ExeFn"); if (fn) fn("梦回吹角连营"); else { MessageBox(NULL, "It Did not work:", "From DLL", MB_OK); return -1; } } }

 

 

main.cpp(生成console.exe):

#include<iostream> #include<Windows.h>
#define _DYNAMIC_ #ifndef _DYNAMIC_ #pragma comment(lib,"mydll.lib")    //静态加载
extern "C" _declspec(dllexport) int fnImportingDLL(); #endif typedef int(*pfnImportingDLL)(); using namespace std; int main(int argc, char* argv[]) { #ifdef _DYNAMIC_ //动态加载
    pfnImportingDLL fnImportingDLL = NULL; HMODULE hModule=::LoadLibrary("mydll.dll"); if (hModule == NULL) { cout << "没法加载mydll.dll" << endl; return -1; } fnImportingDLL =(pfnImportingDLL) GetProcAddress(hModule, "fnImportingDLL"); if (fnImportingDLL == NULL) { cout << "找不到fnImportingDLL函数" << endl; return -2; } #endif cout << "I'm going...\n"; fnImportingDLL(); cout << "Game Over\n"; #ifdef _DYNAMIC_ ::FreeLibrary(hModule); #endif
    return 0; } extern "C" { //该函数将有动态连接库中的函数来调用
    _declspec(dllexport) void ExeFn(char* lpszMessage) { MessageBox(NULL, lpszMessage, "From Exe", MB_OK); } }

 

将生成的 console.exe文件和mydll.dll文件放在同一个文件夹下

注释掉 _DYNAMIC_ 宏将静态加载mydll.dll,这样会弹出对话框提示只能动态加载,由于在DllMain中检查了lpReserved的值并作了判断。

 

将console.exe的名字改成test.exe,而后在cmd中运行test.exe,将显示没法加载mydll.dll。

相关文章
相关标签/搜索