获取代码运行所在模块的句柄值

最近看代码,遇到一个很有趣的问题:如何获取一个静态库代码链接进模块的句柄? 模块句柄值实际上是这个模块加载内存中的基地址。我们暂且放下这个问题,先看看动态库是如何获取句柄值的。

动态库获取句柄值

正常情况下一个动态库dll获取它的句柄,可以通过GetModuleHandle或者GetModuleHandleEx这个API来获取。

HMODULE WINAPI GetModuleHandle(
  _In_opt_ LPCTSTR lpModuleName
);

BOOL WINAPI GetModuleHandleEx(
  _In_     DWORD   dwFlags,
  _In_opt_ LPCTSTR lpModuleName,
  _Out_    HMODULE *phModule
);

这个两个API都需要把dll模块名传进去才能获取句柄值。但是dll可能被人重命名,所以这个方法不一定百分百可靠。

另外一种办法是在DLLMain入口函数里保存hinstDLL值。但是不一定所有的Dll都有Dll,所以这个方法也是不一定百分百可靠。

BOOL WINAPI DllMain(
  _In_ HINSTANCE hinstDLL,
  _In_ DWORD     fdwReason,
  _In_ LPVOID    lpvReserved
);

还有种方法是调用VirtualQuery接口获取MEMORY_BASIC_INFORMATION值:

typedef struct _MEMORY_BASIC_INFORMATION {
  PVOID  BaseAddress;
  PVOID  AllocationBase;
  DWORD  AllocationProtect;
  SIZE_T RegionSize;
  DWORD  State;
  DWORD  Protect;
  DWORD  Type;
} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;

MEMORY_BASIC_INFORMATION结构体里面的MEMORY_BASIC_INFORMATION.AllocationBase就是基地址,可以转换成模块的句柄。这个算是获取Dll句柄的终极办法了。但是这个办法毕竟有些hack,不到万不得已,就别用。

从http://www.codeguru.com/Cpp/W-P/dll/tips/article.php/c3635/ 学到一个办法,这里有段代码是专门用来解决这个问题的:

//#if _MSC_VER >= 1300    // for VC 7.0
//// from ATL 7.0 sources
//#ifndef _delayimp_h
//extern "C" IMAGE_DOS_HEADER __ImageBase;
//#endif
//#endif

//HMODULE GetCurrentModule()
//{
//#if _MSC_VER < 1300    // earlier than .NET compiler (VC 6.0)
//
//  // Here's a trick that will get you the handle of the module
//  // you're running in without any a-priori knowledge:
//  // http://www.dotnet247.com/247reference/msgs/13/65259.aspx
//
//  MEMORY_BASIC_INFORMATION mbi;
//  static int dummy;
//  VirtualQuery( &dummy, &mbi, sizeof(mbi) );
//
//  return reinterpret_cast<HMODULE>(mbi.AllocationBase);
//
//#else    // VC 7.0
//
//  // from ATL 7.0 sources
//
//  return reinterpret_cast<HMODULE>(&__ImageBase);
//#endif
//}

这段代码里面还有另外一种获取模块句柄的方法:__ImageBase。 __ImageBase是Microsoft编译器生成的伪变量,__ImageBase代表着模块的DOS头,也就是模块的起始地址。换言之,也就是这个模块的基地址,这个模块的句柄。

静态库获取句柄值

因为你不知道静态库会被哪个模块链接,所以无法使用GetModuleHandle这个API来获取句柄。它没有DLLMain函数,也无法使用VirtualQuery。幸好静态库同样可以使用__ImageBase。详见https://blogs.msdn.microsoft.com/oldnewthing/20041025-00/?p=37483/ 介绍。