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函数加载该进程需要的动态连接库的。)

原文地址:https://www.cnblogs.com/youxin/p/2829170.html