1. Vc下生成,如果只是cpp使用,只需要在函数前加 _declspec(dllexport)就可导出,可以通过vc工具dumpbin exports查看。
2. Vc6下可以建立一个空的dll,里面什么文件都没有,这时只要新建一个cpp文件,里面放一个函数,然后函数头上加入标示:_declspec(dllexport)就可以生产带有函数输出的dll了。
3. 外部使用需求:
l extern 引用函数(extern在这里可以被-declspec(dllimport) 替代),extern可以用在多个场和,但-declspec(dllimport)只能用在dll中,但是效率更高。
l 把dll文件copy到debug目录下,其实只要是vc的寻找目录下都可以
l 把lib文件copy到项目目录下(不要搞错了,否则找不到)
l 在工程-》设置-》link的对象/库模块下 添加.lib文件名称。
就是这么简单。
备注:输入库lib并没有提供代码,而是用来为链接程序提供信息,以便建立重定位表。也可以通过dumpbin –imports 来查看exe的输入信息。
4. 可以通过vc6下的工具dependency walker来查看可执行模块或者动态链接库的依赖。此工具如何查看exe的依赖dll,如果是自己写的dll,最好放到debug目录下,否则dependency会显示找不到这个dll。
5. 为了方便别人查看自己的dll,一般dll编写者都会提供一个头文件,来包含自己导出函数的声明。别人只需要引用这个头文件就可。而且头文件里最基础的只需要添加这个函数的声明就可以,但是要加标头,如:
-declspec(dllimport) int add(int a, int b); 这时,如果使用端copy此.h文件过去,然后文中的add的声明就可以去掉。
6. 如何让一个头文件既可以为客户端服务,又同时为自己dll服务(此时dll编写者的cpp文件中也可以把声明去掉),需要条件编译:
那么就需要使用条件编译。如:
#ifdef qiaoDLL_API #else #define qiaoDLL_API _declspec(dllimport) #endif |
原来的cpp文件中:_declspec(dllexport)需要define宏:(这个#define要放在#include之前)
#define qiaoDLL_API _declspec(dllexport) |
分析:由于vc中:头文件不参与编译,源文件单独编译。
因此:编译cpp时,由与已经定义了qiaoDLL_API,所以包含头文件时,就不会再定义,此时的qiaoDLL_API指的就是_declspec(dllexport)。而如果客户使用,那么只要他们不定义qiaoDLL_API,因为他们引入了.h文件,那么根据条件编译的结果,他们使用的就是qiaoDLL_API 就指代_declspec(dllimport),从而达到引入目的。
7. Vc6中是可以通过dll导出cpp的类的。导出类要求:同样要在.h文件中声明这个类,如:
Class qiaoDLL_API myClass { Void Output(int x, int y); } 把qiaoDLL_API标识符加在类前面就表示把整个类导出,如果只想导出类中的一部分函数,那么就需要去掉这个标识,然后把标识加在里面的函数头前。 |
8. 在类中实现一个方法:这里方法把结果显示在调用者的窗口上。如何实现:
使用windows的API函数来实现:
在void 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); |
还要包含两个头文件:
1. API对应 <windows.h>
2. 标准输入输出文件 <stdio.h>
客户端使用很简单:
MyClass mc;
mc.Output(10,20); 就可以了。
9. 由于 CPP考虑到重载,故将函数名在dll输出时发生变化,这时如果用c语言编写的客户端访问,可能就会出错。怎么办呢?
这时就需要在声明前加入 extern “C” 其中C必须大写。
#define qiaoDLL_API extern “C ” _declspec(dllimport)
#define qiaoDLL_API extern “C ” _declspec(dllexport)
此时用dumpbin查看就会发现,导出的函数名字和原来的名字没有发生改变。
缺陷:如果使用了extern “C” 就不能使用类的成员函数,只能导出全局函数,让全局函数不发生名字改变。另外,如果导出的函数调用约定发生改变,即使用了extern “C” ,名字也会发生改变。如:
在函数前加入 _stdcall (也就是winapi、也就是pascal),(不加这个标识,默认是c调用方式)。此时,即使你使用了extern “C”, 通过dumpbin查看发现了 函数名字也发生了改变。
【小知识:dephi采用的就是这种_stdcall(pascal)调用方式。】
但是名字发生了改变,该如何兼容c和dephi呢?
模块定义文件,工程名.def。加入工程后。编辑:
LIBRARY 工程名 EXPORTS add (也可以用newname=add来指定新的名字) |
此处的add就表示如果搜索cpp中有和add匹配的,那么就用此处的add作为输出函数名,不管是什么调用方式。【有意思的,如果使用新的名字,那么在vc++使用此dll时,两个名字都可以作为函数名使用,不过要修改.h文件】
10. 前面的方式是隐式链接,当链接的dll比较多时,建议采用动态链接,示例:
HINSTANCE hInst; hInst=LoadLibrary(“dll.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比较多,每个引用的函数又比较少,那么建议采用显示隐藏。因为隐式加载过多的dll,程序启动时加载资源过多,启动缓慢。
需要注意的是,如果前面的约定为_stdcall,那么在动态调用时也必须加上这个约定。即:
typedef int(_stdcall *Addproc)(int a,int b); //加上这个约定 |
否则就会报错。
10. 动态加载时,用dumpbin –imports 查看exe就会发现没有这个dll的信息。
11. 动态加载时,如果原来的没有采用extern “C” 或者def,此时动态链接库就会找不到函数,因为函数名称已经发生了变化(隐式加载时时可以找到的)。Oh,god,原来我的问题是出在这里啊。
12. 动态加载时,也可以通过序号访问函数,但是由于要将数值转化为LPCTSTR,可以用一个宏 MAKEINTRESOURCE(1);
13. 关于DllMain函数,可有可无,可以传递当前dll的句柄。第二个参数是关于加载原因,可以case使用。形式:
这里附上一个vc6自动生成的含有函数、类、全局变量的带DllMain的DLL:
// qiaoDll.cpp : Defines the entry point for the DLL application. //
#include "stdafx.h" #include "qiaoDll.h"
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
// This is an example of an exported variable QIAODLL_API int nQiaoDll=0;
// This is an example of an exported function. QIAODLL_API int fnQiaoDll(void) { return 42; }
// This is the constructor of a class that has been exported. // see qiaoDll.h for the class definition CQiaoDll::CQiaoDll() { return; }
|
// The following ifdef block is the standard way of creating macros which make exporting // from a DLL simpler. All files within this DLL are compiled with the QIAODLL_EXPORTS // symbol defined on the command line. this symbol should not be defined on any project // that uses this DLL. This way any other project whose source files include this file see // QIAODLL_API functions as being imported from a DLL, wheras this DLL sees symbols // defined with this macro as being exported. #ifdef QIAODLL_EXPORTS #define QIAODLL_API __declspec(dllexport) #else #define QIAODLL_API __declspec(dllimport) #endif
// This class is exported from the qiaoDll.dll class QIAODLL_API CQiaoDll { public: CQiaoDll(void); // TODO: add your methods here. };
extern QIAODLL_API int nQiaoDll;
QIAODLL_API int fnQiaoDll(void); |
14. 有的时候,报没有编译头的错,是因为需要StdAfx.h,可以填上或者到设置中把link中把编译头设置为自动或者不需要。
15. 也可以直接用mfc设计dll
参考了孙鑫老师的讲课,特感谢。