DLL 导出函数 _stdcall 和 _cdecl调用约定

 —— 关于 DLL 在 VB 中调用的一些细节

         (VS 2005 编译器)
 
                                                                                                                                                                                      黄  彪
                                                                                                                                                                              2012-04-06
 
        由于DLL通常是为 C 程序准备的,因此一个C/C++程序员在为C/C++程序编写DLL时可以不必关心导出函数的调用约定。在你不关心这个问题的时候,编译器通常会为导出函数指定默认的调用约定——那就是_cdecl调用约定. _cdecl 调用约定规定了参数的出栈工作由调用者来完成。如果查看编译器生成的汇编指令的话,对于一个有参数的函数调用,可以看到在 CALL 指令前,一定会有 PUSH 指令,而在CALL 之后一定会有 POP 指令。
 
      ·山穷水尽
 
        一个完美的编译器可以使程序员们轻松很多,但再完美的编译器也是由不完美的人开发的,因此总是会有一些意料之外的事情发生。当你试着用 VC 为 VB 去开发一个DLL时, 这种事情就会发生。
 
        首先,VB 使用 _stdcall 调用约定,因此 DLL 中的导出函数必须全部声明成 _stdcall,VB 才有可能正常调用,类似于下面这样:
 
               
                extern "C" _declspec(dllexport) int _stdcall MyFunction (int aParam, int bParam){
                    return aParam + bParam;
                }

 
        对于一个曾经无数次为 C/C++ 程序写过 DLL 的程序员来说,仅仅是添加了一个 _stdcall 修饰符而已,其余一切都是外甥打灯笼——照旧,很简单,不是吗?
 
        然后我们分别建立一个 VC 和 VB 的工程去测试我们的 DLL 。
 
       ******************************************************************************************************
 
        首先在 VC 中测试。
 
                 typedef int(*)(int,int) MYPROC;
                 HMODULE hMod = LoadLibrary (_T("MyDll.dll"));
                 MYPROC pFun   = (MYPROC)GetProcAddress(hMod, "MyFunction");
                 int nRet               = pFun ( 2, 3);

 
        在第三行中 GetProcAddress  返回的总是 NULL,  GetLastError 返回的错误码是 127. 看来这样在VC中是行不通的。
 
       ******************************************************************************************************
 
        然后在 VB 中测试。
 
                 '在 form1 模块中添加声明。
                 
Private Declare Function MyFunction Lib "MyDll.dll" (ByValaParam As Long,ByVal bParam As Long) As Long

 
                 '在事件中调用
                 Dim iRet As Long
                 iRet = MyFunction ( 2, 3)

                
        结果会如我们所设计的那样 iRet = 5 吗?非也,程序在执行到 iRet = MyFunction( 2, 3) 这行时会产生异常,并且提示“找不到 MyFunction 的入口点 in MyDll.dll”。 看这样在VB中也是行不通的。
 
        ******************************************************************************************************
 
        经过一翻努力,我们究竟做了些什么? 答案是:我们做了一个即不能在 VC 中使用,也不能在 VB 中使用的 DLL 怪胎。没有碰到过此类问题的读者会问:“有这么严重吗? 这些代码一点都不能用吗?”。“呃,在我看来,我想大概的确就是这么严重!”
 
      ·柳暗花明
 
        我们可以使用 DEF 文件去规避上述的这些问题,对于那些对 DEF 文件不了解的程序员,我有一个好消息:DEF 文件的内容简单得连猴子都可以弄明白。因此,我认为使用 DEF 文件来定义 DLL 的导出函数是最经济有效的解决办法。当我们使用 DEF 文件导出我们的函数时,就没有必要在源文件中再加入那些导出修饰符了,但是源代码中的调用约定修饰符是不能省略的。
 
        全部的做法分为三部:
 
        一, 改变 MyFunction 函数声明,象下面这样:
       
                int _stdcall MyFunction (intaParam, int bParam) {
                    return aParam + bParam;
                }

 
        二,编辑DEF 文件,象下面这样:
                LIBRARY MyDll
                EXPORTS
                        MyFunction @1
 
        三,修改编译器开关,使得编译器使用 DEF 文件来定义导出函数。
 
        经过以上三部修改,编译后的 DLL 在 VB 中是完全可以直接使用的。可是在VC中调用 LoadLibrary 加载使用的话,还是会有一个问题:因为 _stdcall 调用约定规定了参数是由函数自已进行出栈处理的。但调用者会认为它是一个使用 _cdecl 调用约定的函数。在函数返回后,调用者会对函数的参数执行出栈指令——这将会引发严重错误。
 
       解决这个问题,我们可以自已使用汇编指令来调用 _stdcall 约定的函数,而不是由编译器去生成汇编指令。
       类似下面这样:
 
        int nRet = 0;
        _asm{
                PUSH 3
                PUSH 2
                CALL [pFun]       // pFun 是通过 GetProcAddress 从 dll 中取得的导出函数地址。
                MOV nRet, eax
        }

        
        nRet 便包含了 MyFunction 函数的返回值。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值