当前位置:C++技术网 > 资讯 > dll动态链接库全面讲解:2 使用API函数直接使用动态链接库dll的函数

dll动态链接库全面讲解:2 使用API函数直接使用动态链接库dll的函数

更新时间:2015-10-25 16:36:50浏览次数:1+次

    在《dll动态链接库全面讲解1:通过预编译指令引用lib导入库来使用dll》一文中,我已经介绍了使用lib导入库使用dll的方法,这种方法非常的方便。如果有lib引入库的情况下,推荐使用lib引入库的方式使用dll。这样使用方便,也不容易出错。
    但是有时候你得到的只有一个dll和dll的头文件,这个时候,没有lib导入库,那么我们就只能直接使用dll了。
    不过也好,本文介绍的方法,逼格肯定比通过lib导入库使用dll的高很多倍哦。
    同样我们先把dll和dll的头文件放入创建好的项目中,然后添加头文件。下面就开始解释如何使用dll咯。
    我们要使用Windows提供的API函数来加载dll,然后再通过API来找到dll中函数的地址,这样就可以通过函数指针来调用函数了。
    加载dll的API函数为LoadLibrary。如果项目中设置了Unicode字符集,使用LoadLibraryW宽字符版本,如果使用的是多字节字符集,则使用LoadLibraryA版本。如果不想管这个,那么你就直接使用LoadLibrary中性版本,然后在传入字符串参数时使用_T宏即可。LoadLibrary返回值类型为HMODULE,是模块句柄类型。exe也是一个模块,一般叫做实例,句柄就是实例句柄。而dll一般都以模块或者组件的形式出现,所以都叫做模块,所以对应的句柄即使模块句柄。HMODULE的MODULE即module,就是模块的意思。
    那么我们的dll文件名为myDll.dll,所以使用方法如下:

HMODULE hDll = LoadLibrary(_T("myDll.dll"));// - 中性版本
HMODULE hDll = LoadLibraryA("myDll.dll");// - 多字节字符集版本
HMODULE hDll = LoadLibraryW(L"myDll.dll");// - Unicode字符集版本

     三个版本返回的结果都是一样的,区别在于传递参数的字符串是哪种形式而已。在控制台项目中,我就使用A版本咯,这样写字符串方便,和控制条程序字符串保持一致,看起来舒服。
    加载dll成功的话,hDll的值就是Dll的模块句柄,如果失败的话,则为NULL。而加载DLL失败是常有的事情,比如dll文件被删了或者重命名了。这都会导致加载失败。当然还有其他原因,反正就是说,这个失败概率还是蛮大的。所以一定要做检测,如果为NULL,不能继续执行,要提示。你自己提示的可以很人性化,否则最后被系统系统,那些错误提示,很多人都看不懂哦。看看下面的提示多温馨呀!
   
    还有,如果你不做检测,没有加载到dll,然后就去找函数,这不是找死吗!看看程序死成什么样了。
   
    这个问题就是没有加载成功dll,导致模块句柄为空,此时你在空句柄的情况下能找到什么鬼的函数呀,不死也是神经病了。你看到代码中确实做了条件判断,但是你发现了问题没有。还是继续执行了呀!此时已经走投无路了,还要往前冲,不就是撞墙而死么!所以如果加载失败,你应该停止前进的脚步,换条路走或者自杀算了。解决的代码如下:

HMODULE hDll = LoadLibraryA("myDll.dll");// - 加载dll
if (!hDll)
{
    MessageBoxA(NULL, "亲,怎么这么不小心呢,dll文件弄丢了哦。\r\n你再找不到,看我不收拾你!", "温馨提示", 0);
    return;// - 在main函数中return不就是自杀么,哈哈哈
}

     不过在这里,不自杀你也可以这样:

HMODULE hDll = LoadLibraryA("myDll.dll");// - 加载dll
if (!hDll)
{
    MessageBoxA(NULL, "亲,怎么这么不小心呢,dll文件弄丢了哦。\r\n你再找不到,看我不收拾你!", "温馨提示", 0);
}
if(hDll!=NULL)
{
    printf("前方已经没有路了,是自杀还是自杀?请选择!");
}

     当然在这里,我们没有在第一步自杀,不过我们往前走了一步,这已经是成功的选择了一条新的路线,表明了你的坚持和勇气。只是最后还是殊途同归了,哈哈哈。当然,后面看你自己怎么做了,不用相信命运的安排,我相信你会有更好的选择的。
    好了,上面出现加载失败的原因,是我故意把dll文件名改了,所以,你懂得。有点小坏,不要太介意哈。
    如果一切正常,我们就要找dll中的函数了。这里使用的是API函数GetProcAddress。Proc表示的是Process,即过程的意思,也就是一个函数代码块咯。Address就是地址的意思咯,整个函数的意思就是获取函数的地址咯。是的,我们就是需要得到函数的地址,就可以调用函数了。函数名其实就是函数地址咯。调用函数的底层反应就是跳转指令,将执行的位置调到函数代码所在的入口,然后开始执行函数代码。
    GetProcAddress函数的第一个参数就是模块的句柄,上面得到的hDll就是了。第二个参数就是函数名称。我们从dll头文件中知道有show和showme两个函数,那么我们将函数名称传一个进去就可以了。
    既然是要获取函数的地址,那么我们肯定要用函数指针来接受这个地址,然后再通过函数指针就可以方便的调用函数咯。当然,你可以直接获取地函数地址后直接调用,如下所示:

GetProcAddress(hDll, "show")();

     函数返回值就是一个函数地址,就相当于一个函数名,不过这个是匿名的函数咯,因为没有函数名称咯。然后在后面加一个()操作符就表示了函数调用,在括号中写参数就可以了。不过这样有一个缺点,就是使用不方便。每次使用都得去获取一次函数地址,真心麻烦,不过这个是一个有效的方法,有时候用得上,所以在此告诉大家了,这个秘密一般人都不告诉他哦。
    那么我们常用的方法就是要用函数指针来接受地址,然后通过函数指针调用。函数指针变量将函数地址存起来了,以后可以随便调用。函数类型函数地址和函数指针相关的知识,请阅读《函数名、函数类型、函数地址和函数指针》。
    那么我们使用typedef重定义一个函数类型,这样方便定义函数指针。重定义的代码如下:

typedef void (PFUN)();// - 重定义函数类型

     那么这样定义之后,PFUN就是无参数返回值类型为void的函数类型咯。那么我们用PFUN类型来定义一个函数指针变量,然后接受获得得到的函数地址。不过,GetProcAddress返回的类型是FARPROC类型,与我们的函数类型不一样,所以要做强制转换,这样就可以正常使用了,否则会提示:

1>c:\users\wdx\documents\visual studio 2015\projects\usedll2\usedll2\m.cpp(13): error C2440: “初始化”: 无法从“FARPROC”转换为“PFUN (__cdecl *)”
1>  c:\users\wdx\documents\visual studio 2015\projects\usedll2\usedll2\m.cpp(13): note: 该转换要求 reinterpret_cast、C 样式转换或函数类型转换

     这个错误提示就是表明函数类型不一致。做个强制类型转换即可。那么我们将返回的地址赋值给函数指针,然后直接调用函数,代码如下:

PFUN *pShow = GetProcAddress(hDll, "show");// - 获取函数地址
pShow();// - 调用函数

     pShow是一个函数地址指针变量,所以就相当于是函数名了。只不过这个变量的值是可以改变的。函数名则是一个地址常量。这与数组名和指向数组的指针一样的意思。
    那么执行的效果图如下:

   

    对于GetProcAddress的返回值,最好也做一个判断,因为可能输入函数名错误或者函数并没有导出,都会导致获取失败。失败后就不能调用了。获取失败,得到的函数地址为NULL。所以检查一下,如果函数地址为NULL,则不再继续调用函数了。代码如下:


PFUN *pShow = (PFUN*)GetProcAddress(hDll, "show");// - 获取函数地址
if(!pShow)
{
    MessageBoxA(NULL, "函数获取失败,可能是dll中此函数未导出,也可能不存在此函数。!", "温馨提示", 0);
    return;
}
pShow();// - 调用函数

    形成一个好的编码习惯是必要的,这样可以大大降低Bug的出现几率,提高工作效率。