使用VS2005创建自己的动态链接库,这样就不用看到DLL文件有抵触情感了。
一、打开VS2005软件,创建DLL工程。起名ceshi1.
1)
2) 点击下一步,应用程序类型为DLL,空工程。
3) 完成,一个新的DLL工程设置完毕,接下来编辑源码
添加头文件,命名为ceshi1.h,编辑内容:
#ifndef CESHI1_H
#define CESHI1_H //防止重复定义
#include <iostream> //头文件引用
#if defined DLL_EXPORT //源文件需引用下一函数
#define DECLDIR __declspec(dllexport)//就是这个
#else
#define DECLDIR __declspec(dllimport)
#endif
extern "C"
{
DECLDIR int add(int a, int b); //函数声明
DECLDIR void function(void);
}
#endif
解释:VC中有两种方式导出DLL文件中的函数
1) 使用 __declspec关键字。
2) 创建模板定义文件(Module_Definithion File)即.def文件。
在此使用第一种方法:
__declspec(dllexport)作用是导出函数符号到DLL的一个存储类中。在头文件中定义#define DECLDIR __declspec(dllexport)宏来运行这个函数。
使用条件编译,在源文件中宏定义 #define DLL_EXPORT 来运行这个函数。
//******************************************************************************
创建源文件 ceshi1.cpp
#include <iostream>
#define DLL_EXPORT
#include "ceshi1.h"
extern "C"
{
//定义Dll中的所有函数
DECLDIR int add(int a, int b)
{
return a+b;
}
DECLDIR void function(void)
{
std::cout<<"DLL called"<<std::endl;
}
}
编译工程,生成ceshi1.dll文件和ceshi1.lib文件(注:找到工程目录,这两个文件在外面的debug文件夹下)。
二、使用创建好的DLL库文件
创建一个控制台应用程序
1) 隐式链接:
连接到.lib文件,首先将ceshi1.h头文件,ceshi1.dll动态库文件,ceshi1.lib静态库文件放在控制台应用程序工程的目录中,即源文件所在的文件夹里。
添加源文件ceshi2.cpp,编码:
#pragma comment(lib,"ceshi1.lib")//链接.lib文件
#include "ceshi1.h" //包含dll的头文件
#include <iostream>
int main()
{
function();
std::cout<<add(25,25)<<std::endl;
getchar();
return 0;
}
//******************************************************************************
2) 显式链接
只加载.dll文件,但需用到函数指针和Windows 函数,不需要.lib文件和.h头文件。
#include <iostream>
#include <windows.h>
typedef int (*AddFunc)(int, int);
typedef void (*FunctionFunc)(void);//函数指针
int main()
{
AddFunc _AddFunc;
FunctionFunc _FunctionFunc;
//HINSTANCE实例句柄(即一个历程的入口地址)
HINSTANCE hInstLibrary = LoadLibraryA("ceshi1.dll");
//注意,此时的LoadLibraryA()窄字符集和LoadLibraryW()宽字符集的区别,后有介绍。
if(hInstLibrary == NULL)
{
FreeLibrary(hInstLibrary);//释放DLL获得的内存,若句柄无效
}
//获得函数的入口地址,还需类型转换
_AddFunc = (AddFunc)GetProcAddress(hInstLibrary,"add");
_FunctionFunc = (FunctionFunc)GetProcAddress(hInstLibrary,"function");
if(_AddFunc==NULL || _FunctionFunc ==NULL)//确定是否成功。
{
FreeLibrary(hInstLibrary);
}
std::cout<<_AddFunc(25,25)<<std::endl;
_FunctionFunc();
getchar();
FreeLibrary(hInstLibrary);
return 0;
}
//******************************************************************************
Windows 的执行文件可以划分为两种形式:程序和动态连接库(DLLs)。一般程序运行是用.EXE文件,但应用程序有时需要调用存储在DLL 中的函数。
当我们调用Windows 中的API 函数的时候,实际上就是调用存储在DLL 中的函数。
在如下几种情况下,调用DLL 是合理的:
1) 不同的程序使用相同的DLL ,这样只需要将DLL 在内存中装载一次,节省了内存的开销。
2) 当某些内容需要升级的时候,如果使用DLL 只需要改变DLL 就可以了,而不需要把整个程序都进行变动。
3) 由于DLL 是独立于语言的,所以,当不同语言习惯的人共同开发一个大型项目的时候,使用DLL 便于程序系统的交流,当然,Delphi开发的DLL 也可以在诸如Visual BASIC,C++ 等系统中使用。
只是要在公开的接口函数声明前面加上几个特定的修饰符。
//**********example1.h***********************************************************
#ifndef EXAMPLE1_H
#define EXAMPLE1_H
#define DLLIMPORT __declspec(dllexport)
DLLIMPORT void HelloWorld(void);
#endif
//******example1.c***************************************************************
#include "example1.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
DLLIMPORT void HelloWorld(void)
{
MessageBox(0,TEXT("Hello World from DLL!\n"),TEXT("Hi"),MB_ICONINFORMATION);
}
BOOL APIENTRY DllMain(HINSTANCE hInst,DWORD reason,LPVOID reserved)
{
switch(reason){
case DLL_PROCESS_ATTACH:
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return true;
}
__declspec(dllexport)标志着后面这个函数将成为对外的接口,使用包含在DLL中的函数,必须将其导入,导入操作通过dllimport来完成,dllexport和dllimport都是VC和BC所支持的关键字,但不能被自身格式所使用,通用格式为__declspec(dllexport)和__declspec(dllimport),为了简化,用宏名代替,若程序被编译成C++程序,同时希望C程序亦可使用,需要增加”C”的链接说明,
#define DLLEXPORT extern “C” __declspec(dllexport),就避免了标准C++命名损坏。、、有错。
BOOL APIENTRY DllMain()说明:
1、每个DLL必须有一个入口点,DllMain()是入口函数,负责初始化(initialization)和结束(termination)工作,每当一个新的进程或该进程的新的线程访问DLL时,或者不再使用DLL时,或结束时,都将调用DllMain。但是使用terminate Process或terminate Thread结束进程和线程,不会调用DllMain。
DllMain()函数的原型:
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_resong_for_call, LPVOID lpreserved)
{
switch(ul_reason_for_call){
case DLL_PROCESS_ATTACH:
…
case DLL_THREAD_ATTACH:
…
case DLL_PROCESS_DETTACH:
…
case DLL_THREAD_DETTACH:
…
}
return true;
}
参数:
hMoudle:是动态库被调用时所传递来的一个指向自己的句柄(实际上,它是指向_DGROUP段的一个选择符);HINSTANCE
ul_reason_for_call:是一个说明动态库被调原因的标志。当进程或线程装入或卸载动态连接库的时候,操作系统调用入口函数,并说明动态连接库被调用的原因。它所有的可能值为:
DLL_PROCESS_ATTACH: 进程被调用;
DLL_THREAD_ATTACH: 线程被调用;
DLL_PROCESS_DETACH: 进程被停止;
DLL_THREAD_DETACH: 线程被停止;
lpReserved:是一个被系统所保留的参数。
//问题:如何使用上面的DLL库文件?
资料:
动态链接库调用有两种函数:导出函数(export function)和内部函数(internal function)。导出函数可被其他模块调用,内部函数只在定义的DLL中内部使用。
其他模块使用导出函数的用法:
1) 传统方法
在模块定义文件的EXPORT部分指定需要的函数或变量,语法格式如下:
entryname[=internalname] [@ordinal[NONAME]] [DATE] [PRIVATE]
其中:entryname是导出函数或数据被引用的名称。
internalname 同entryname.
@ordinal 表示在输出表中的顺序号
NONAME仅仅在按顺序号输出时被使用,(不使用entryname)
DATA表示输出的是数据项,使用DLL输出数据的程序必须声明该数据项为__declspec(dllimport).
以上各项中,只有entryname是必须的,其他可以省略。
对于“C”函数来说,entryname可以等同于函数名;但是对“C++”函数(成员函数、非成员函数)来说,entryname是修饰名。可以从.map映像文件中得到要输出函数的修饰名,或者使用DUMPBIN /SYMBOLS得到,然后把它们写在.def文件的输出模块。DUMPBIN是VC提供的一个工具。
如果要输出一个“C++”类,则把要输出的数据和成员的修饰名都写入.def模块定义文件。
2) 命令行输出
对链接程序LINK指定/EXPORT命令行参数。
3) 使用MFC提供的修饰符号 __declspec(dllexport)
在要导出的函数、类、数据的声明前加上 __declspec(dllexport) 修饰符,表示导出。
extern “C”使在C++中使用C编译方式,在C++中定义C函数,需加extern “C”关键字,用extern “C”来指明该函数使用C编译方式,导出的C函数可在C代码中调用。
例如:在一个C++文件中,有如下函数
extern “C” {void __declspec(dllexport) __cdecl Test(int var);}
其输出函数名为 Test (非C++方式)
MFC提供了一些宏,就有这样的作用。省略 #define
AFX_CLASS_IMPORT:__declspec(dllexport)
AFX_API_IMPORT:__declspec(dllexport)
AFX_DATA_IMPORT:__declspec(dllexport)
AFX_CLASS_EXPORT:__declspec(dllexport)
AFX_API_EXPORT:__declspec(dllexport)
AFX_DATA_EXPORT:__declspec(dllexport)
AFX_EXT_CLASS: #ifdef _AFX_EXT
AFX_CLASS_EXPORT
#else
AFX_CLASS_IMPORT
AFX_EXT_API:#ifdef _AFX_EXT
AFX_API_EXPORT
#else
AFX_API_IMPORT
AFX_EXT_DATA:#ifdef _AFX_EXT
AFX_DATA_EXPORT
#else
AFX_DATA_IMPORT
像AFX_EXT_CLASS这样的宏,如果用于DLL应用程序的实现中,则表示输出(因为_AFX_EXT被定义,通常是在编译器的标识参数中指定该选项/D_AFX_EXT);如果用于使用DLL的应用程序中,则表示输入(_AFX_EXT没有定义)。
要输出整个的类,对类使用_declspec(_dllexpot);要输出类的成员函数,则对该函数使用_declspec(_dllexport)。如:
class AFX_EXT_CLASS CTextDoc : public CDocument
{
…
}
extern "C" AFX_EXT_API void WINAPI InitMYDLL();
这几种方法中,最好采用第三种,方便好用;其次是第一种,如果按顺序号输出,调用效率会高些;最次是第二种。
一、创建DLL文件
1) 使用VS2005创建DLL空工程。
2) 新建头文件 builder1.h,程序:
//*************************************************************************
#ifndef BUILDER1_H
#define BUILDER1_H
#ifdef BUILDER1_DLL
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif
//注意#endif的位置
extern "C"{ //原样编译
//__stdcall 使非C/C++语言能够调用API
DLL_API int __stdcall Max(int a, int b);
}
#endif
//******************************************************************************
3) 新建源文件 builder1.cpp
//******************************************************************************
#define BUILDER1_DLL
#include "builder1.h"
DLL_API int __stdcall Max(int a,int b)
{
return a>b ? a:b;
}
//*****************************************************************************
4) 用.def文件创建动态链接库 build1.dll
a、删除build1工程中的builder1.h文件
b、在builder1.cpp中删除#include ”builder1.h”语句。内容:
int __stdcall Max(int a,int b)
{
return a>b ? a:b;
}
c、向该工程中加入一个文本文件,命名为builder1.def,内容:错误
LIBRARY build1 //标准格式 LIBRARY “build1”
EXPORTS
Max @ 1 //注意:一定要有空格,语法的格式
d、编译生出dll文件
二、调用动态链接库DLL
1、隐式调用:
1) 创建控制台工程build2
2) 将build1.dll, build1.lib, builder1.h拷贝到工程所在目录,(主程序cpp所在的文件夹下)
三个文件必须全要。
3) 新建源文件 build2.cpp
//***********************************************************************
#include "builder1.h"
#pragma comment(lib,"build1.lib")
#include <iostream>
int main()
{
int value;
value = Max(100,250);
std::cout << "Value="<<value<<std::endl;
getchar();
return 0;
}
//************************成功**************************************************
2、显式调用
1) 建立控制台工程
2) 将close1.dll拷贝到工程目录(close1.dll是由.def文件生成)
3) 用vc/bin下的Dumpbin.exe的小程序,查看DLL文件(DllDemo.dll)中的函数结构。
4) 使用类型定义关键字typedef,定义指向和DLL中相同的函数原型指针。
typedef int(__stdcall *lpMax)(int a, int b); //一定要和dll中头文件声明一致
5) 通过LoadLibrary()将DLL加载到当前的应用程序中,并返回当前DLL文件的句柄:
HINSTANCE hDLL; //声明一个DLL文件实例句柄
hDLL = LoadLibrary(close1.dll); //导入动态链接库
6) 通过GetProcAddress()函数获得导入到应用程序的函数指针。
lpMax Max;
Max = (lpMax)GetProcAddress(hDll,” Max”);
int value;
value = Max(250, 500);
std::cout <<”Value = ”<< value<<std::endl;
7) 函数调用完毕后,使用FreeLibrary(),释放DLL文件
FreeLibrary(hDll);
8) 编译生成exe文件。
一、创建
1) File----New建立一个新的DLL工程,生成一个DLL的工程框架。入口函数:
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason,
void* lpReserved)
{
return 1;
}
DllEntryPoint()函数为一个入口方法,如果使用者在DLL被系统初始化或者注销时被调用,用来写入对DLL的初始化程序和卸载程序;参数:hinst用来指示DLL的基地址;reason用来指示DLL的调用方式,用于区别多线程单线程对DLL的调用、创建、卸载DLL;lpReaerved为保留参数;
#pragma argsused
extern "C"{
__declspec(dllexport) int __stdcall test(void); //函数声明
}
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
return 1;
}
//---------------------------------------------------------------------------
int __stdcall test(void) //函数定义
{
std::cout <<"My first BCB DLL project"<<std::endl;
return 250;
}
//……………………………Make project 生成DLL库文件…………………………………………………………
二、调用DLL
1、静态调用法
控制台应用程序,工程添加输入接口库(import library),在project manager中添加。
//---------------------------------------------------------------------------
#include <iostream>
#pragma hdrstop
//---------------------------------------------------------------------------
#pragma argsused
extern "C" __declspec(dllimport) int __stdcall test(void); //接口声明
int main(int argc, char* argv[])
{
std::cout <<test()<<std::endl;
std::cout<<"first dll"<<std::endl;
getchar();
return 0;
}
//---------------------------------------------------------------------------
注意:
(1)动态链接库调用过程、函数时CALL方式 与创建时方式一样不写为__cdecl,其它需要声明。
(2)BCB创建的DLL有对应的输入接口库(import library),如只有DLL而无库时,可用BCB的implib工具产生:implib xxx.lib xxx.dll;另外可用:tlibxxx.lib,xxx.lst 产生DLL的内部函数列表,许多Windows的未公开技术就是用这种方法发现的。
2、动态调用法
动态调用法要用Windows API 中的LoadLibrary()和GetProcAddress()来调入DLL库,指出库中函数位置,这种方法较常见。先设置Project Options:
Packages标签:去除Builder with runtime packages检查框。(在框下面)
Linker标签:去除Use dynamic RTL检查框。 右下角
否则创建的DLL需要Runtime packages or Runtime library。
#include <iostream>
#include <windows>
#pragma hdrstop
//---------------------------------------------------------------------------
#pragma argsused
typedef int (__stdcall *TEST)(void);
int main(int argc, char* argv[])
{
HINSTANCE hDll;
hDll = LoadLibrary("Project2.dll");
TEST ddd;
ddd = GetProcAddress(hDll,"test");
std::cout<<"ddd()="<<ddd()<<std::endl;
FreeLibrary(hDll);
getchar();
return 0;
}
//-------------------成功--------------------------------------------------------
一、创建
New-〉projects-〉Win32 Static Libaray,空的工程。
创建一个.h文件 staticLib.h
//************************************************************************
#ifndef _STATIC_LIB_H
#define _STATIC_LIB_H
class staticLibTest{
private:
int m_value;
public:
staticLibTest();
void SetVal(int para=0);
int GetVal();
void Display();
};
#endif
//*************************************************************************
创建对应的cpp文件 fighting.cpp
//************************************************************************
#include <iostream>
#include "staticLib.h"
staticLibTest::staticLibTest()
{
m_value = 8;
}
void staticLibTest::SetVal(int para)
{
m_value = para;
}
int staticLibTest::GetVal()
{
return m_value;
}
void staticLibTest::Display()
{
std::cout <<"output value is: "<<m_value<<std::endl;
}
//*************************Ctrl+F7编译,F7生成LIB****************************
二、创建中间层(用外部函数来引用静态对象中的数据(数据由对象公有函数传递))。以便C++类中的成员函数能够在C中被调用(通过中间层),将中间层做成静态库。
新建一个静态链接库工程如上,建好工程wrap后,新建头文件wrap.h
//******************************************************************************
#ifndef WRAP_H
#define WRAP_H
#ifdef __cplusplus
extern "C"{
#endif
void ExtcSetVal(int para);
int ExtcGetVal(void);
void ExtcDisplay(void);
#ifdef __cplusplus
}
#endif
#endif
//******************************************************************************
对应的.cpp文件 wrap.cpp
//*****************************************************************************
#include "wrap.h"
#include "staticLib.h"
#pragma comment(lib,"view1.lib");
staticLibTest staticLibObj;
void ExtcSetVal(int para)
{
staticLibObj.SetVal(para);
}
int ExtcGetVal()
{
return staticLibObj.GetVal();
}
void ExtcDisplay()
{
staticLibObj.Display();
}
注意:在编译这个工程之前要把1中的头文件staticLib.h和静态库staticLib.lib拷到当前目录下,
编译工程,生成wrap.lib。
三、编写测试工程
新建一个Win32 Console Application工程,test1。新建test.cpp
//*****************************************************************************
#include <stdio.h>
#include "wrap.h"
#pragma comment(lib,"wrap.lib")
int main()
{
ExtcSetVal(250);
ExtcDisplay();
return 0;
}
//******************************************************************************
将之前生成的两个静态库staticLib.lib和wrap.lib还有头文件wrap.h拷到当前目录下,
编译运行即可
1.制作的步骤:
(1)新建MFC AppWizard(dll)工程,工程名为MFCDll,选择空工程 类型。
(2)在生成的MFCDll.cpp文件后面增加下面函数代码:
int sum(int a, int b)
{
return a+b;
}
(3)在生成的MFCDll.def文件后面增加如下:
sum @1 ;表示第一个函数是sum
(4)编译后会产生两个文件MFCDll.lib,MFCDll.dll
2、调用
(1)隐式调用法: 将MFCDll.lib拷贝到需要应用该DLL的工程的目录下,将MyDll.dll拷贝到产生的应用程序的目录下,并在需要应用该DLL中的函数的CPP文件中添加如下几行:
//注意这里没有在MFCDll.h中声明函数,所以不能直接包含MFCDll.h来声明函数。
以下是引用片段:
#pragma comment(lib,"MFCDll");
int sum(int a, int b);
//当然如果你的DLL中有很多函数,那可以另外写个MFCDll.h,包含所有的函数声明,然后直接将头文件包含进去
(2)显示调用法:与Win32的调用方法一样,不需要#pragma comment(lib,"MFCDll");,但是需要在Project->Setting->Link->Object/library modules的框中增加MFCDll.lib这个库。(研究中)
**************************************************************************
DLL(Dynamic Linkable Library)可以看成一些可以直接拿来用的变量、函数或类的集合。在库的发展史上经历了“无库-静态链接库-动态链接库”的时代。
静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib中的指令都被直接包含在最终生成的EXE文件中了。但是若使用DLL,该DLL不必被包含在最终EXE文件中,EXE文件执行时可以“动态”地引用和卸载这个与EXE独立的DLL文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。
对动态链接库,我们还需建立如下概念:
(1)DLL 的编制与具体的编程语言及编译器无关
只要遵循约定的DLL接口规范和调用方式,用各种语言编写的DLL都可以相互调用。譬如Windows提供的系统DLL(其中包括了Windows的API),在任何开发环境中都能被调用,不在乎其是Visual Basic、Visual C++还是Delphi。
(2)动态链接库随处可见
我们在Windows目录下的system32文件夹中会看到kernel32.dll、user32.dll和gdi32.dll,windows的大多数API都包含在这些DLL中。kernel32.dll中的函数主要处理内存管理和进程调度;user32.dll中的函数主要控制用户界面;gdi32.dll中的函数则负责图形方面的操作。
一般的程序员都用过类似MessageBox的函数,其实它就包含在user32.dll这个动态链接库中。由此可见DLL对我们来说其实并不陌生。
(3)VC动态链接库的分类
Visual C++支持三种DLL,它们分别是Non-MFC DLL(非MFC动态库)、MFC Regular DLL(MFC规则DLL)、MFC Extension DLL(MFC扩展DLL)。
非MFC动态库不采用MFC类库结构,其导出函数为标准的C接口,能被非MFC或MFC编写的应用程序所调用;MFC规则DLL 包含一个继承自CWinApp的类,但其无消息循环;MFC扩展DLL采用MFC的动态链接版本创建,它只能被用MFC类库所编写的应用程序所调用。
隐式调用的方法比较方便,但DLL改变后,应用程序须从新编译。并且,所有所调用的DLL在应用程序加载的同时被加载到内存中,当应用程序调用的DLL比较多时,装入的过程十分慢。显式调用则比较灵活,但比较麻烦.
静态链接MFC DLL和共享MFC DLL的区别:静态链接把程序中要用的在MFC DLL里的东西全部编译到创建的DLL中,体积会大一点,但可以在没有装MFC的机器上运行;通过共享创建的DLL体积小,但在没有安装MFC的机器上有可能不能运行,因为没有相应的库。