c++ DLL编程之一

来源:互联网 发布:51单片机控制12v继电器 编辑:程序博客网 时间:2024/05/18 01:53

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

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

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

3)      Dll库可以供多种编程语言使用,例如用c编写的dll可以在vb中调用。这一点上DLL还做得很不够,因此在dll的基础上发明了COM技术,更好的解决了一系列问题。

最简单的dll
开始写dll之前,你需要一个c/c++编译器和链接器,并关闭你的IDE。是的,把你的VC和C++ BUILDER之类的东东都关掉,并打开你以往只用来记电话的记事本程序。不这样做的话,你可能一辈子也不明白dll的真谛。我使用了VC自带的cl编译器和link链接器,它们一般都在vc的bin目录下(若你没有在安装vc的时候选择注册环境变量,那么就立刻将它们的路径加入path吧)。【如果用VC的话步骤:新建项目-Windows控制台程序-DLL(注意下面有个“输出符号”要根据情况选择或不选)】

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

#include <objbase.h>
#include <iostream.h>
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中的函数【也可以用VS2005的工具Depends.exe查看】
Dumpbin命令可以查看一个dll中的输出函数符号名,键入如下命令:
Dumpbin –exports dll_nolib.dll
通过查看,发现dll_nolib.dll并没有输出任何函数。

如何在dll中定义输出函数
总体来说有两种方法,一种是添加一个def定义文件,在此文件中定义dll中要输出的函数;第二种是在源代码中待输出的函

数前加上__declspec(dllexport)关键字。

含Def文件的DLL
首先写一个带有输出函数的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)
{
    HANDLE g_hModule;
    switch(dwReason)
    {
    case DLL_PROCESS_ATTACH:
       g_hModule = (HINSTANCE)hModule;
       break;
    case DLL_PROCESS_DETACH:
        g_hModule=NULL;
        break;
    }
    return TRUE;
}

这个dll的def文件如下:dll_def.def


LIBRARY         dll_def.dll
DESCRIPTION     "c)2007-2009 Wang Xuebin"
EXPORTS
                FuncInDll @1 PRIVATE

你会发现def的语法很简单,首先是LIBRARY关键字,指定dll的名字;然后一个可选的关键字DESCRIPTION,后面写上版权等信息(不写也可以);最后是EXPORTS关键字,后面写上dll中所有要输出的函数名或变量名,然后接上@以及依次编号的数字(从1到N),最后接上修饰符。

用如下命令编译链接带有def文件的dll:

Cl /c dll_def.cpp
Link /dll dll_def.obj /def:dll_def.def

再调用dumpbin查看生成的dll_def.dll:

Dumpbin –exports dll_def.dll

得到如下结果:

Dump of file dll_def.dll
File Type: DLL
Section contains the following exports for dll_def.dll
           0 characteristics
    46E4EE98 time date stamp Mon Sep 10 15:13:28 2007
        0.00 version
           1 ordinal base
           1 number of functions
           1 number of names
    ordinal hint RVA      name
          1    0 00001000 FuncInDll
Summary
        2000 .data
        1000 .rdata
        1000 .reloc
        6000 .text

观察这一行
          1    0 00001000 FuncInDll

会发现该dll输出了函数FuncInDll。

显式调用DLL中的函数

写一个dll_def.dll的客户端程序:dll_def_client.cpp

#include <windows.h>
#include <iostream.h>
int main(void)
{
    //定义一个函数指针
    typedef void (* DLLWITHLIB )(void);
    //定义一个函数指针变量
    DLLWITHLIB pfFuncInDll = NULL;
    //加载我们的dll
    HINSTANCE hinst=::LoadLibrary("dll_def.dll");
    if (NULL != hinst)
    {
       cout<<"dll loaded!"<<endl;
    }
    //找到dll的FuncInDll函数
    pfFuncInDll = (DLLWITHLIB)GetProcAddress(hinst, "FuncInDll");
    //调用dll里的函数
    if (NULL != pfFuncInDll)
    {
       (*pfFuncInDll)();
    }
    return 0;
}

有两个地方值得注意,第一是函数指针的定义和使用,不懂的随便找本c++书看看;第二是GetProcAddress的使用,这个API是用来查找dll中的函数地址的,第一个参数是DLL的句柄,即LoadLibrary返回的句柄,第二个参数是dll中的函数名称,即dumpbin中输出的函数名(注意,这里的函数名称指的是编译后的函数名,不一定等于dll源代码中的函数名)。

编译链接这个客户端程序,并执行会得到:

dll loaded!

FuncInDll is called!

这表明客户端成功调用了dll中的函数FuncInDll。

原创粉丝点击