Linruin的专栏

DLL编写教程

备注:本来想自己写一篇关于dll编程的文章,苦于不知从何写起,在网上找了一下dll编写教程,还真给我找到了一篇好文章,里面大部分内容都写得很不错,但有些部分我的见解和原文作者的见解有所不同,这里将原文附上,在一些见解不同的地方做了修改,都有标注。如有不对的地方,请各位大虾批评指正,谢谢!

半年不能上网,最近网络终于通了,终于可以更新博客了,写点什么呢?决定最近写一个编程技术系列,其内容是一些通用的编程技术。例如DLL,COM,Socket,多线程等等。这些技术的特点就是使用广泛,但是误解很多;网上教程很多,但是几乎没有什么优质良品。我以近几个月来的编程经验发现,很有必要好好的总结一下这些编程技术了。一来对自己是总结提高,二来可以方便光顾我博客的朋友。

好了,废话少说,言归正传。第一篇就是《DLL编写教程》,为什么起这么土的名字呢?为什么不叫《轻轻松松写DLL》或者《DLL一日通》呢?或者更nb的《深入简出DLL》呢?呵呵,常常上网搜索资料的弟兄自然知道。

本文对通用的DLL技术做了一个总结,并提供了源代码打包下载,下载地址为:

 

DLL的优点

简单的说,dll有以下几个优点:

1) 节省内存。同一个软件模块,若是以源代码的形式重用,则会被编译到不同的可执行程序中,同时运行这些exe时这些模块的二进制码会被重复加载到内存中。如果使用dll,则只在内存中加载一次,所有使用该dll的进程会共享此块内存(当然,像dll中的全局变量这种东西是会被每个进程复制一份的)。

2) 不需编译的软件系统升级,若一个软件系统使用了dll,则该dll被改变(函数名不变)时,系统升级只需要更换此dll即可,不需要重新编译整个系统。事实上,很多软件都是以这种方式升级的。例如我们经常玩的星际、魔兽等游戏也是这样进行版本升级的。

3) Dll库可以供多种编程语言使用,例如用c编写的dll可以在vb中调用。这一点上DLL还做得很不够,因此在dll的基础上发明了COM技术,更好的解决了一系列问题。要注意:com虽然也是以dll(或exe)的形式存在的,但它的调用方式却不同于普通dll,随后我的博客会给出com的调用方法,请关注。

最简单的dll

开始写dll之前,你需要一个c/c++编译器和链接器,并关闭你的IDE。是的,把你的VC和C++ BUILDER之类的东东都关掉,并打开你以往只用来记电话的记事本程序。不这样做的话,你可能一辈子也不明白dll的真谛。我使用了VC自带的cl编译器和link链接器,它们一般都在vc的bin目录下。(若你没有在安装vc的时候选择注册环境变量,那么就立刻将它们的路径加入path吧)如果你还是因为离开了IDE而害怕到哭泣的话,你可以关闭这个页面并继续去看《VC++技术内幕》之类无聊的书了。

最简单的dll并不比c的helloworld难,只要一个DllMain函数即可,包含objbase.h头文件(支持COM技术的一个头文件)。若你觉得这个头文件名字难记,那么用windows.H也可以。源代码如下:dll_nolib.cpp

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_nolib_client.cpp

#include <windows.h>

#include <iostream.h>

int main(void)

{    //加载我们的dll    HINSTANCE hinst=::LoadLibrary("dll_nolib.dll");    if (NULL != hinst)    {        cout<<"dll loaded!"<<endl;    }    return 0;}

注意,调用dll使用LoadLibrary函数,它的参数就是dll的路径和名称,返回值是dll的句柄。 使用如下命令编译链接客户端:

Cl dll_nolib_client.cpp

并执行dll_nolib_client.exe,得到如下结果:

Dll is attached!

dll loaded!

Dll is detached!

以上结果表明dll已经被客户端加载过。但是这样仅仅能够将dll加载到内存,不能找到dll中的函数。

使用dumpbin命令查看DLL中的函数

Dumpbin命令可以查看一个dll中的输出函数符号名,键入如下命令:

Dumpbin –exports dll_nolib.dll

通过查看,发现dll_nolib.dll并没有输出任何函数。

如何在dll中定义输出函数

总体来说有两种方法,一种是添加一个def定义文件,在此文件中定义dll中要输出的函数;第二种是在源代码中待输出的函数前加上__declspec(dllexport)关键字。

Def文件

首先写一个带有输出函数的dll,源代码如下:dll_def.cpp

#include <objbase.h>

#include <iostream.h>

void FuncInDll (void)

{    cout<<"FuncInDll is called!"<<endl;}

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)

接受失败也等于给了自己从零开始的机会,

Linruin的专栏

相关文章:

你感兴趣的文章:

标签云: