zoukankan      html  css  js  c++  java
  • windows dll使用

    如何生成一个DLL 参考 

    Windows 动态链接库 DLL 浅析

    在VC++6.0开发环境下,打开File\New\Project选项,可以选择Win32 Dynamic-Link Library或MFC AppWizard【dll】来以不同的方式创建Non-MFC DLL、Regular DLL、Extension DLL等不同种类的动态链接库。下面以选择Win32 Dynamic-Link Library方式来创建一个DLL(实现加法运算):

    1、创建一个Win32 Dynamic-Link Library方式的空工程,取名为myDLL

    2、分别添加头文件(.h)和源文件(.cpp)

    [cpp] view plain copy
     
     print?
    1. // mydll.h file  
    2. extern "C" _declspec(dllexport) int add(int a, int b);  
    3.   
    4. //mydll.cpp file  
    5. #include "mydll.h"  
    6. int add(int a, int b) //该DLL需要导出的函数功能:加法  
    7. {  
    8.     return a + b;  
    9. }  
    (extern "C" 和_declspec缺一不可)

    说明:

    (1)前面的 extern “C” 告诉编译器函数可以在本模块或其他模块中使用,其中“C”表明需按照C语言方式编译和连接它,因为C++编译时,会对函数名进行修饰,用于实现函数重载,而C里面没有这个功能,所以需要用extern "C"在头文件进行声明的时候加以区分,以便链接时能进行正确地函数名查找。

    (2)_declspec(dllexport)为导出函数关键字,意为需从DLL中导出该函数,以便使用。

    3、编译连接

    在进行编译连接后会在Debug目录下找到DLL文件和对应的lib文件

    六、如何调用一个DLL

    下面实现两种调用方式:单独.dll 和.h + .lib + .dll结合

    需把对应的 .dll 文件以及.lib 文件和.h文件(结合方式时)拷贝至调用的程序目录下

    (1)单纯使用.dll

    [cpp] view plain copy
     
     print?
    1. #include<wtypes.h>   
    2. #include <winbase.h>   
    3. #include <iostream>  
    4. #include<tchar.h>
    5. _declspec(dllimport) int Add(int a, int b); //导入声明,亦可以不加,如果加上可加快程序运行  
    6.   
    7. typedef int(*pAdd)(int a,int b);  
    8.   
    9. int main()  
    10. {  
    11.   
    12.     HINSTANCE hDLL;  
    13.     pAdd Add;  
    14.     hDLL=LoadLibrary(_T("mydll.dll"));  //加载 DLL文件  
    15.     if(hDLL == NULL)std::cout<<"Error!!!\n";  
    16.     Add=(pAdd)GetProcAddress(hDLL,"add");  //取DLL中的函数地址,以备调用  
    17.   
    18.     int a =Add(5,8);  
    19.     std::cout<<"a: "<<a<<std::endl;  
    20.       
    21.     FreeLibrary(hDLL);  
    22.     return 0;  
    23. }   



    (2).h + .lib + .dll 结合方式

    [cpp] view plain copy
     
     print?
    1. #include<wtypes.h>   
    2. #include <winbase.h>   
    3. #include <iostream>  
    4. #include "mydll.h"  
    5. #pragma comment(lib,"mydll.lib")  //将mydll.lib库文件连接到目标文件中(即本工程)  
    6. extern "C"_declspec(dllimport) int add(int a,int b);  
    7. int main()  
    8. {  
    9.   
    10.     int a =add(5,8);  
    11.     std::cout<<"a: "<<a<<std::endl;  
    12.   
    13.     return 0;  
    14. }   

    #

    余下看:Windows 动态链接库 DLL 浅析

    理解 __declspec(dllexport)和__declspec(dllimport)

    1、解决的问题:

      考虑下面的需求,使用一个方法,一个是提供者,一个是使用者,二者之间的接口是头文件。头文件中声明了方法,在提供者那里方法应该被声明为__declspec(dllexport),在使用者那里,方法应该被声明为__declspec(dllimport)。二者使用同一个头文件,作为接口,怎么办呢?

    2、解决办法:

      使用条件编译:定义一个变量,针对提供者和使用者,设置不同的值。

    复制代码
     1 #ifndef DLL_H_
     2 #define DLL_H_
     3 
     4 #ifdef DLLProvider
     5 #define DLL_EXPORT_IMPORT __declspec(dllexport)
     6 #else
     7 #define DLL_EXPORT_IMPORT __declspec(dllimport)
     8 #endif
     9 
    10 DLL_EXPORT_IMPORT int add(int ,int);
    11 
    12 #endif
    复制代码

    首先新建一个dll工程。里面有2个文件,如下:

    dll1.h

    #ifdef DLL1_API
    #else
    #define DLL1_API _declspec(dllimport)
    #endif
    DLL1_API int add(int a,int b);
    DLL1_API  int subtract(int a,int b);
    
    /*
    _declspec(dllimport) int add(int a,int b);
    _declspec(dllimport) int subtract(int a,int b);
    */
    class DLL1_API Point //把整个类导出
    {
    public:
        void output(int x,int y);
    };

    如何只导出某个类的成员函数.

    DLL1_API void output(int x,int y);
    导出某个函数,在外部还是可以使用这个类,Point x;只是只可以使用这个类导出的函数,不能用其他的函数。

    dll1.cpp

    #define DLL_API  _declspec(dllexport)
    #include"dll1.h"
    #include<windows.h>
    #include<stdio.h>
    
    int add(int a,int b)
    {
        return a+b;
    }
     
    int subtract(int a,int b)
    {
        return a-b;
    }
    
    void Point::output(int x,int y)
    {
        HWND hwnd=GetForegroundWindow();
        HDC hdc=GetDC(hwnd);
        char buf[20];
        memset(buf,0,20);
        sprintf(buf,"x=%d,y=%d",x,y);
        TextOut(hdc,0,0,buf,strlen(buf));
        ReleaseDC(hwnd,hdc);
    
    }

    mfc对话框里面有3个按钮,按钮相应程序如下:

    应该包含头文件#include "dll1.h"

    void CDll1TestDlg::OnBtnAdd() 
    {
        // TODO: Add your control notification handler code here
        CString str;
        str.Format("5+3=%d",add(5,3));
        MessageBox(str);
        
    }
     
    void CDll1TestDlg::OnBtnSub() 
    {
        // TODO: Add your control notification handler code here
            CString str;
        str.Format("5-3=%d",subtract(5,3));
        MessageBox(str);
    
    }
     
    
    void CDll1TestDlg::OnBtnOutput() 
    {
        // TODO: Add your control notification handler code here
        Point pt;
        pt.output(5,3);
        
    }

    解决名字改编问题:

       c++编译器在生成一个dll时,会对导出的函数进行名字改编,并且不同的编译器使用的改编规则不一样,因此改编后的名字是不一样的。这样,如果利用不同的编译器分别生成和访问该dll的客户端程序的话,后者在访问该dll的导出函数时就会出现问题。

     把头文件类Point注释起来。

    在dll1.h:

    #ifdef DLL1_API
    #else
    #define DLL1_API extern "C" _declspec(dllimport)
    #endif
    DLL1_API int add(int a,int b);
    DLL1_API int subtract(int a,int b);

    在dll1.cpp前面:

    #define DLL_API  _declspec(dllexport) 加上 extern "C"

    #define DLL_API extern "C"  _declspec(dllexport)

    利用限定符:extern "C" 可以解决c++和C语言之间相互调用时函数命名的问题。但是这个方法有一个缺陷,就是不能用于导出一个类的成员函数,只能用于导出全局函数这种情况。这就是我吗为什么要将Point类的代码注释起来的原因了。

     另外,如果导出函数的调用约定发生了改变,那么即使使用了限定符:”extern C,那么函数的名字仍会发生改编。为了说明这种情况,在dll.h的add,subtract函数前加上_stdcall 关键字:

    DLL1_API int  _stdcall add(int a,int b);
    DLL1_API int  _stdcall subtract(int a,int b);

    在dll1.cpp源文件中,add函数前也要加上_stdcall关键字:

    #define DLL_API extern "C"   _declspec(dllexport)
    #include"dll1.h"
    #include<windows.h>
    #include<stdio.h>
    
    int  _stdcalll add(int a,int b)
    {
        return a+b;
    }
     
    int _stdcall subtract(int a,int b)
    {
        return a-b;
    }

    如果没有添加_stdcall关键字,那么函数调用约定就是C调用约定,标准调用约定就是WINAPI调用约定,也就是pascal调用约定,这个约定与C调用约定不一样。

     也就是说,如果函数的调用约定发生了变化,即使在声明这些函数时使用了extern "C"限定符,他们的名字仍然会发生改编,我们知道,c和delphi调用约定是不一样的,后者使用的是pascal调用约定,即标准调用约定:_stdcall.如果现在需要利用C语言编写一个dll,然后delphi编写的客户端程序访问的话,那么在导出函数时,应指定其使用标准的函数调用约定。但是,这仍会出现问题,因为这时函数名称会发生改编。这种情况下,可以通过一个称为模块定义问题(DEF)的方式来解决名字改编问题。

    新建一个dll2工程。

    dll2.cpp

    int add(int a,int b)
    {
    return a+b;
    }

    int subtract(int a,int b)
    {
    return a-b;
    }

    增加一个文本文件dll2.def

    里面内容如下:

    LIBRARY dll2

    EXPORTS
    add
    subtract

    用dumpbin执行时函数名字还是原来定义的名字。

    显示加载问题

     上面的例子都是通过隐式链接加载方式来实现对动态链接库的访问,下面将采用动态加载方式来访问动态链接库。

    将最新的dll2.dll文件复制到DLLTEst目录下(不需要dll2.lib),然后在DLLTEst工程中,将dlltestdlg.cpp 文件中包含dll1.h文件的那行代码注释起来,并在该工程设置对话框的link选项卡上,删除对dll1.lib文件的链接。

    HMODULE WINAPI LoadLibrary(
      _In_  LPCTSTR lpFileName
    );
    

     Loads the specified module into the address space of the calling process. The specified module may cause other modules to be loaded.

    LoadLibrary函数不仅可以加载dll,还可以加载可执行模块(.exe).一般来说,当加载可执行模块时,主要是为了访问该模块内的一些资源,例如对话框资源、位图资源或图标资源等。函数返回类型是HMODULE,该类型和HINSTANCE类型可以通用。

    GetProcAddress

    Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL).

    FARPROC WINAPI GetProcAddress(
      _In_  HMODULE hModule,
      _In_  LPCSTR lpProcName
    );

    void CDll1TestDlg::OnBtnAdd() 
    {
        
        HINSTANCE hInst;
        hInst=LoadLibrary("dll2.dll");
        typedef int (*ADDPROC)(int a,int b);//定义函数指针类型
        ADDPROC add=(ADDPROC)GetProcAddress(hInst,"add");
        if(!add)
        {
            MessageBox("获取函数地址失败");
            return;
        }
        CString str;
        str.Format("5+3=%d",add(5,3));
        MessageBox(str);
    
    }

    完成后程序可以正常执行。通过本例可以看出,动态加载dll时,客户端不再需要包含导出函数的头文件和引入库文件,只需要.dll文件即可
    (隐式链接和动态连接各有其优缺点,实际上,采用隐式方式访问dll时,在程序启动时也是通过调用LoadLibrary函数加载该进程需要的动态连接库的。)

  • 相关阅读:
    课程设计之第二次冲刺----第十天
    课程设计之第二次冲刺----第九天
    课程设计之第二次冲刺----第八天
    课程设计之第二次冲刺----第七天
    课程设计之第二次冲刺----第6天
    课程设计之第二次冲刺----第五天
    课程设计之第二次冲刺----第四天
    课程设计之第二次冲刺----第三天
    课程设计之第二次冲刺----第二天
    第一个sprint与第二个sprint阶段总结
  • 原文地址:https://www.cnblogs.com/youxin/p/2829170.html
Copyright © 2011-2022 走看看