Dll制作与使用教程

前言:

被dll折腾了很长一段时间,上网查了很多资料,终于搞清楚了,现把dll的编写和使用方法进行梳理一下,分享给大家,也方便自己以后随时查询。另外把我看到的两篇有价值的参考文章贴出来,我的这篇文章是在它们的基础上,加上自己的实践总结出来的。

链接1:http://blog.chinaunix.net/uid-20255102-id-1713076.html

链接2:http://www.blogjava.net/wxb_nudt/archive/2007/09/11/144371.html

一. dll为什么会出现

我们在编写好一个系统软件后,有时需要更新该软件的某一部分代码,修改完成后我们需要再次编译该系统软件,这很不方便。有没有方法只修改该部分代码,而不用重新编译该系统软件呢?另外,当一段程序需要在其他语言中使用时,需要重写该段代码,这同样影响工作效率,有没有方法让我们可以直接使用该段代码呢?dll的出现可以很好的解决这些问题。

二. dll的优点

根据以上描述,可将优点总结如下:

1. dll可供多种语言直接调用。

2.当一个系统软件使用了dll文件,如果需要修改该dll,只需要将修改后的dll替换原dll,不需要重新编译整个系统软件。

3.节省内存。当一段代码以源代码的形式在不同程序间重用时,同时运行这些程序的exe,系统会在内存中重复加载这段代码。若使用dll来编写这段代码,则只会在内存中加载一次,所有使用该dll的进程会共享这段内存。

三. dll的制作

1.采用__declspec(dllexport)方式

使用VS2010制作dll时,默认生成的即是该方式。VS2010制作dll方式如下:

(1). 打开【VS2010】--->【新建项目】--->选择【win32控制台应用程序】,输入项目名称,选择保存路径后,点击【确定】。如下图所示:

                                                 

(2). 点击【确定】后,弹出【win32应用程序向导】界面;点击【下一步】,弹出【应用程序设置】界面,【应用程序类型】选择【DLL】,【附加选项】选择【导出符号】,点击【完成】。如下图所示:

                                                                                   

(3). 点击【完成】后生成一个如下图所示的工程文件:

                     

我们只需要关注DllTest.h、DllTest.cpp这两个文件。DllTest.h文件给出了导出函数的声明示例,参照该示例可以声明自定义的导出函数;函数定义写在DllTest.cpp文件中,如下图所示:

                     

该文件自动给出了导出函数的示例,我们可以参照该示例定义所需要的导出函数。

(4). 函数声明和定义完成后,点击【启动调试】,启动成功后,弹出如下图所示的界面:

此时,我们在该路径下即可看到已生成DllTest.dll文件和DllTest.lib文件。

在调用该方式制作的dll时,需使用DllTest.h文件、DllTest.dll文件和DllTest.lib文件。

2.采用def文件方式

(1). 采用def文件方式时,不需要DllTest.h文件。因此将上述工程中的头文件从工程中移除,(因为stdafx.h文件内包括window.h,所以将dllmain.cpp文件内的#include<stdafx.h>替换为#include<window.h>)。

(2). 在源文件中添加【新建项】,选择【模块定义文件(.def)】,输入好名称后,点击【添加】,如下图所示

                                                 

添加好后的工程如下图所示:

                     

此时就可以在DllTest.def文件内定义声明导出函数了(定义导出函数和第一种方式一样,在DllTest.cpp内定义)。具体格式链接1的文章中所讲,如下图所示:

                    

四、dll的调用

1.隐式调用

(1). 格式如下:#pragma comment(lib,"DllTest.lib");

在调用程序内必须写上#include"DllTest.h"

(2). 隐式调用时,调用该dll的程序只能是C或C++

采用隐式调用上面生成的dll文件的代码示例如下图所示:

                    


2. 显示调用:

显示调用需要先用Loadlibrary函数加载dll文件,然后用GetProcAddress找到该dll的地址,采用显示调用dll的示例代码如下图所示:

                   

从以上可知:制作dll有两种方式,调用dll也有两种方式。这样在使用时就有4种组合方式。


以下给出了这四种组合方式使用方法和示例代码。

上面讲到调用时有4种组合方式,下面具体来讲一讲这四种方式的使用方法(末尾给出代码示例链接):

方式一、__declspec(dllexport)制作方式和隐式调用相合

步骤一:首先将dll制作好后的头文件、dll文件、lib文件拷贝到调用该dll的工程中。

步骤二:用教程一中隐式调用的方法将dll加载到该工程内即可。

方式二、__declspec(dllexport)制作方式和显式调用相合

注意:这种方式必须在制作dll的头文件内,在导出函数前添加上extern "C"。因为如果不加的话,C++编译器考虑到函数的重载,会将dll中的函数名进行更改(可以使用dumpbin命令验证添加extern "C"前后是否发生变化)。这导致在使用dll过程中,使用GetProceAddress查找导出函数时必须使用更改后的函数名,很不方便。

步骤一:只需将dll制作好后的dll文件拷贝到调用该dll的工程中。

步骤二:用教程一中的显示调用的方法将dll加载到该工程即可。

方式三、def制作方式和隐式调用结合

步骤一:将制作好的dll文件和lib文件拷贝到调用该dll的工程中。

步骤二:由于def方式没有头文件,所以在调用程序中首先声明导出函数,再按照教程一中的隐式调用方法进行调用即可。

方式四、def制作方式和显式调用结合

注:该方式不会出现方式二中所说的函数名被更改的问题

步骤一:将dll制作好后的dll文件拷贝到调用该dll的工程中。

步骤二:用教程一中的显示调用的方法将dll加载到该工程即可。


dll制作和调用代码示例





  • 5
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
最简单的dll并不比c的helloworld难,只要一个DllMain函数即可,包含objbase.h头文件(支持COM技术的一个头文件)。若你觉得这个头文件名字难记,那么用windows.H也可以。源代码如下:dll_nolib.cpp #include #include BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved) { HANDLE g_hModule; switch(dwReason) { case DLL_PROCESS_ATTACH: cout<<"Dll is attached!"<<endl; g_hModule = (HINSTANCE)hModule; break; case DLL_PROCESS_DETACH: cout<<"Dll is detached!"<<endl; g_hModule=NULL; break; } return true; } 其中DllMain是每个dll的入口函数,如同c的main函数一样。DllMain带有三个参数,hModule表示本dll的实例句柄(听不懂就不理它,写过windows程序的自然懂),dwReason表示dll当前所处的状态,例如DLL_PROCESS_ATTACH表示dll刚刚被加载到一个进程中,DLL_PROCESS_DETACH表示dll刚刚从一个进程中卸载。当然还有表示加载到线程中和从线程中卸载的状态,这里省略。最后一个参数是一个保留参数(目前和dll的一些状态相关,但是很少使用)。 从上面的程序可以看出,当dll被加载到一个进程中时,dll打印"Dll is attached!"语句;当dll从进程中卸载时,打印"Dll is detached!"语句。 编译dll需要以下两条命令: cl /c dll_nolib.cpp 这条命令会将cpp编译为obj文件,若不使用/c参数则cl还会试图继续将obj链接为exe,但是这里是一个dll,没有main函数,因此会报错。不要紧,继续使用链接命令。 Link /dll dll_nolib.obj 这条命令会生成dll_nolib.dll。 注意,因为编译命令比较简单,所以本文不讨论nmake,有兴趣的可以使用nmake,或者写个bat批处理来编译链接dll。 加载DLL(显式调用)
远程注入DLL方法有很多种,也是很多木马病毒所使用的隐藏进程的方法,因为通过程序加载的DLL在进程管理器是没有显示的.这里介绍一种用 CreateRemoteThread 远程建立线程的方式注入DLL. 首先,我们要提升自己的权限,因为远程注入必不可免的要访问到目标进程的内存空间,如果没有足够的系统权限,将无法作任何事.下面是这个函数是用来提升我们想要的权限用的. function EnableDebugPriv: Boolean; var hToken: THandle; tp: TTokenPrivileges; rl: Cardinal; begin Result := false; //打开进程令牌环 OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken); //获得进程本地唯一ID if LookupPrivilegeValue(nil, 'SeDebugPrivilege', tp.Privileges[0].Luid) then begin tp.PrivilegeCount := 1; tp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED; //调整权限 Result := AdjustTokenPrivileges(hToken, false, tp, SizeOf(tp), nil, rl); end; end; 关于 OpenProcessToken() 和 AdjustTokenPrivileges() 两个 API 的简单介绍: OpenProcessToken():获得进程访问令牌的句柄. function OpenProcessToken( ProcessHandle: THandle; //要修改访问权限的进程句柄 DesiredAccess: DWORD; //指定你要进行的操作类型 var TokenHandle: THandle//返回的访问令牌指针 ): BOOL; AdjustTokenPrivileges() :调整进程的权限. function AdjustTokenPrivileges( TokenHandle: THandle; // 访问令牌的句柄 DisableAllPrivileges: BOOL; // 决定是进行权限修改还是除能(Disable)所有权限 const NewState: TTokenPrivileges; { 指明要修改的权限,是一个指向TOKEN_PRIVILEGES结构的指针,该结构包含一个数组, 数据组的每个项指明了权限的类型和要进行的操作; } BufferLength: DWORD; //结构PreviousState的长度,如果PreviousState为空,该参数应为 0 var PreviousState: TTokenPrivileges; // 指向TOKEN_PRIVILEGES结构的指针,存放修改前的访问权限的信息 var ReturnLength: DWORD //实际PreviousState结构返回的大小 ) : BOOL; 远程注入DLL其实是通过 CreateRemoteThread 建立一个远程线程调用 LoadLibrary 函数来加载我们指定的DLL,可是如何能让远程线程知道我要加载DLL呢,要知道在Win32系统下,每个进程都拥有自己的4G虚拟地址空间,各个进程之间都是相互独立的。所我们需要在远程进程的内存空间里申请一块内存空间,写入我们的需要注入的 DLL 的路径. 需要用到的 API 函数有: OpenProcess():打开目标进程,得到目标进程的操作权限,详细参看MSDN function OpenProcess( dwDesiredAccess: DWORD; // 希望获得的访问权限 bInheritHandle: BOOL; // 指明是否希望所获得的句柄可以继承 dwProcessId: DWORD // 要访问的进程ID ): THandle; VirtualAllocEx():用于在目标进程内存空间中申请内存空间以写入DLL的文件名 function VirtualAllocEx( hProcess: THandle; // 申请内存所在的进程句柄 lpAddress: Pointer; // 保留页面的内存地址;一般用nil自动分配 dwSize, // 欲分配的内存大小,字节单位;注意实际分 配的内存大小是页内存大小的整数倍 flAllocationType: DWORD; flProtect: DWORD ): Pointer; WriteProcessMemory():往申请到的空间中写入DLL的文件名 function WriteProcessMemory( hProcess: THandle; //要写入内存数据的目标进程句柄 const lpBaseAddress: Pointer; //要写入的目标进程的内存指针, 需以 VirtualAllocEx() 来申请 lpBuffer: Pointer; //要写入的数据 nSize: DWORD; //写入数据的大小 var lpNumberOfBytesWritten: DWORD //实际写入的大小 ): BOOL; 然后就可以调用 CreateRemoteThread 建立远程线程调用 LoadLibrary 函数来加载我们指定的DLL. CreateRemoteThread() //在一个远程进程中建立线程 function CreateRemoteThread( hProcess: THandle; //远程进程的句柄 lpThreadAttributes: Pointer; //线程安全描述字,指向SECURITY_ATTRIBUTES结构的指针 dwStackSize: DWORD; //线程栈大小,以字节表示 lpStartAddress: TFNThreadStartRoutine; // 一个TFNThreadStartRoutine类型的指针,指向在远程进程中执行的函数地址 lpParameter: Pointer; //传入参数的指针 dwCreationFlags: DWORD; //创建线程的其它标志 var lpThreadId: DWORD //线程身份标志,如果为0, 则不返回 ): THandle; 整个远程注入DLL的具体实现代码如下: function InjectDll(const DllFullPath: string; const dwRemoteProcessId: Cardinal): Boolean; var hRemoteProcess, hRemoteThread: THandle; pszLibFileRemote: Pointer; pszLibAFilename: PwideChar; pfnStartAddr: TFNThreadStartRoutine; memSize, WriteSize, lpThreadId: Cardinal; begin Result := false; // 调整权限,使程序可以访问其他进程的内存空间 if EnableDebugPriv then begin //打开远程线程 PROCESS_ALL_ACCESS 参数表示打开所有的权限 hRemoteProcess := OpenProcess(PROCESS_ALL_ACCESS, false, dwRemoteProcessId); try // 为注入的dll文件路径分配内存大小,由于为WideChar,故要乘2 GetMem(pszLibAFilename, Length(DllFullPath) * 2 + 1); // 之所以要转换成 WideChar, 是因为当DLL位于有中文字符的路径下时不会出错 StringToWideChar(DllFullPath, pszLibAFilename, Length(DllFullPath) * 2 + 1); // 计算 pszLibAFilename 的长度,注意,是以字节为单元的长度 memSize := (1 + lstrlenW(pszLibAFilename)) * SizeOf(WCHAR); //使用VirtualAllocEx函数在远程进程的内存地址空间分配DLL文件名空间 pszLibFileRemote := VirtualAllocEx(hRemoteProcess, nil, memSize, MEM_COMMIT, PAGE_READWRITE); if Assigned(pszLibFileRemote) then begin //使用WriteProcessMemory函数将DLL的路径名写入到远程进程的内存空间 if WriteProcessMemory(hRemoteProcess, pszLibFileRemote, pszLibAFilename, memSize, WriteSize) and (WriteSize = memSize) then begin lpThreadId := 0; // 计算LoadLibraryW的入口地址 pfnStartAddr := GetProcAddress(LoadLibrary('Kernel32.dll'), 'LoadLibraryW'); // 启动远程线程LoadLbraryW,通过远程线程调用创建新的线程 hRemoteThread := CreateRemoteThread(hRemoteProcess, nil, 0, pfnStartAddr, pszLibFileRemote, 0, lpThreadId); // 如果执行成功返回 True; if (hRemoteThread 0) then Result := true; // 释放句柄 CloseHandle(hRemoteThread); end; end; finally // 释放句柄 CloseHandle(hRemoteProcess); end; end; end; 接下来要说的是如何卸载注入目标进程中的DLL,其实原理和注入DLL是完全相同的,只是远程调用调用的函数不同而已,这里要调用的是FreeLibrary,代码如下: function UnInjectDll(const DllFullPath: string; const dwRemoteProcessId: Cardinal): Boolean; // 进程注入和取消注入其实都差不多,只是运行的函数不同而已 var hRemoteProcess, hRemoteThread: THandle; pszLibFileRemote: PChar; pszLibAFilename: PwideChar; pfnStartAddr: TFNThreadStartRoutine; memSize, WriteSize, lpThreadId, dwHandle: Cardinal; begin Result := false; // 调整权限,使程序可以访问其他进程的内存空间 if EnableDebugPriv then begin //打开远程线程 PROCESS_ALL_ACCESS 参数表示打开所有的权限 hRemoteProcess := OpenProcess(PROCESS_ALL_ACCESS, false, dwRemoteProcessId); try // 为注入的dll文件路径分配内存大小,由于为WideChar,故要乘2 GetMem(pszLibAFilename, Length(DllFullPath) * 2 + 1); // 之所以要转换成 WideChar, 是因为当DLL位于有中文字符的路径下时不会出错 StringToWideChar(DllFullPath, pszLibAFilename, Length(DllFullPath) * 2 + 1); // 计算 pszLibAFilename 的长度,注意,是以字节为单元的长度 memSize := (1 + lstrlenW(pszLibAFilename)) * SizeOf(WCHAR); //使用VirtualAllocEx函数在远程进程的内存地址空间分配DLL文件名空间 pszLibFileRemote := VirtualAllocEx(hRemoteProcess, nil, memSize, MEM_COMMIT, PAGE_READWRITE); if Assigned(pszLibFileRemote) then begin //使用WriteProcessMemory函数将DLL的路径名写入到远程进程的内存空间 if WriteProcessMemory(hRemoteProcess, pszLibFileRemote, pszLibAFilename, memSize, WriteSize) and (WriteSize = memSize) then begin // 计算GetModuleHandleW的入口地址 pfnStartAddr := GetProcAddress(LoadLibrary('Kernel32.dll'), 'GetModuleHandleW'); //使目标进程调用GetModuleHandleW,获得DLL在目标进程中的句柄 hRemoteThread := CreateRemoteThread(hRemoteProcess, nil, 0, pfnStartAddr, pszLibFileRemote, 0, lpThreadId); // 等待GetModuleHandle运行完毕 WaitForSingleObject(hRemoteThread, INFINITE); // 获得GetModuleHandle的返回值,存在dwHandle变量中 GetExitCodeThread(hRemoteThread, dwHandle); // 计算FreeLibrary的入口地址 pfnStartAddr := GetProcAddress(LoadLibrary('Kernel32.dll'), 'FreeLibrary'); // 使目标进程调用FreeLibrary,卸载DLL hRemoteThread := CreateRemoteThread(hRemoteProcess, nil, 0, pfnStartAddr, Pointer(dwHandle), 0, lpThreadId); // 等待FreeLibrary卸载完毕 WaitForSingleObject(hRemoteThread, INFINITE); // 如果执行成功返回 True; if hRemoteProcess 0 then Result := true; // 释放目标进程中申请的空间 VirtualFreeEx(hRemoteProcess, pszLibFileRemote, Length(DllFullPath) + 1, MEM_DECOMMIT); // 释放句柄 CloseHandle(hRemoteThread); end; end; finally // 释放句柄 CloseHandle(hRemoteProcess); end; end; end;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值