用VC++5.0制作DLL经验一二
北京大唐电信软件中心
周志杰
---- 本文'一'、'二'两部分适用于对DLL的基本制作方法已经了解,或手头有关于DLL制作方法的书籍的读者.对于初次接触DLL制作的读者,建议您先按'三'中的步骤建立一个自己的DLL并在另一个应用程序中成功的调用它之后再阅读'一'、'二'.
---- DLL可以分为两个不同的类别:用C/C++(不用对象)编写的基于API的传统DLL和基于MFC对象的DLL.
一.两种类型的比较:
---- 1. 基于MFC的DLL限制在使用MFC的编译器中.
---- 基于API的DLL可以从一种编译器移植到另一种编译器.
---- 2. 基于MFC的DLL可以由制作向导构建框架,制作简单
---- 基于API的DLL的制作向导只为你制作了一个空的DLL工程,工程的维护和代码的编写全部需要手工完成(这一点在VC 6.0中有了改进)
---- 3. 基于MFC的DLL不适用与制作读取二进制文件的DLL
---- (无法正确读取与DOS应用程序共享的二进制文件)
---- 基于API的DLL可以正确读取在DOS环境中创建的二进制文件.
---- 造成该结果的原因其实很简单:
---- 在读取二进制文件的过程中通常会使用"结构(体)",基于MFC的DLL要加载MFC,它要求"结构成员位对齐"的比特位是8位,而且你无法方便的通过选中"Project- >Setting- >C/C++"选项卡中的"Code Generation"再修改"Struct member alignment"来使其变为1位(在VC5.0中即使改变了,在编译时该改变也会被忽略.不过这一不足在VC6.0中已经得到了改进,读者有兴趣的话可参见笔者《在VC6.0中读取二进制文件的惊喜》一文).
---- 而基于API的DLL则可以通过以上的方法方便的实现.
二.在实际制作与使用中的一点经验:
---- 1.制作DLL的目的之一是共享资源/代码.所谓"共享"当然不应该仅仅是几个VC++制作的应用程序可以使用,但是在与其他编程语言协作时,有些问题是需要注意的.
---- 在制作DLL时,VC++对函数的省缺声明是"__cedcl",也就是说,如果你在声明你的函数时不作特殊声明的话,你制作的DLL将只能被C/C++调用,如果你想用其他开发语言(比如VB5.0)调用它就会报错,即使调用方法完全正确.
---- 那么该怎么办呢,你一定已经猜到了---不要用省缺的声明方式---一个很好的选择是使用"WINAPI"来声明你的函数.它可以把你的DLL中的函数声明成WINDOWS API供其他程序调用(当然也包括C/C++制作的程序).
---- 2.建议你在制作DLL的同时制作包括导出函数原型声明的.H文件虽然这不是必须的但是若你的DLL是被C/C++调用,.H文件和.LIB文件可以为使用你的DLL的开发人员省去不少精力,当你需要修改/升级你的DLL时更是如此.DLL的C/C++使用者只要在工程中引入.H和.LIB文件可以象使用自己编写的函数一样方便的使用DLL中的函数,不必再使用存储DLL句柄、声明函数型指针、LoadLibrary、GetProcAddress那样繁复的调用方式.
---- 3.虽然你不必编写.DEF文件就能制作出基于API的.DLL文件.但是制作.DEF文件并不难,至少你有捷径可以走.有一个很简单的方法你不妨一试:
---- 例如,你用基于API的方法制作了一个DLL工程文件并为其编写了.CPP和.H文件,你可以保存并关闭该工程,然后在另一个目录中创建一个与其同名的基于MFC的DLL工程.好了,现在你已经知道怎么做了---将该目录中的.DEF文件移动过去就可以了.省下的工作就是再次打开基于API的DLL工程,并将.DEF文件加入工程,将你的导出函数的函数名加到EXPORTS之后,再重新编译工程就OK了.
---- 4.DLL文件的省缺名称是与工程名一致的(也是在.DEF文件中LIBRARY 之后的名字),不要试图在制作完毕之后通过简单的修改.DLL文件的文件名来改变它,这会导致使用该DLL的应用程序错误.
三.一个例子:
---- DLL中定义有两种函数:
---- 导出函数(exportfunction): 可以被其他模块调用
---- 内部函数(internalfunction): 只能在DLL内部使用
---- 创建一个基于API的DLL.本例只定义了导出函数.
---- 1.在FILE- >NEW- >PROJECTS中选择"WIN32 Dynamic-Link Library"在Project Name中输入 "a"按OK
---- 2.在FILE- >NEW- >FILES中选择C++ SOURCE FILE,在FILE中输入a.cpp,按OK
---- 在FILE- >NEW- >FILES中选择TEXT文件,在FILE中输入a.h,按OK
---- 在FILE- >NEW- >FILES中选择TEXT文件,在FILE中输入a.def,按OK
---- 3.源文件:
//---------------------------
//a.cpp
#include < windows >
WINAPI int add(int a,int b)
{ return (a+b);
}
//---------------------------
//a.h
WINAPI int add(int a,int b);
//---------------------------
//a.def
LIBRARY "aaa" ;指出DLL的名字
DESCRIPTION 'aaa Windows Dynamic Link Library'
;描述DLL的用途(此句可选)
EXPORTS add ;导出函数的名字
四.调用DLL的方法:
---- 1.通常我们在调用DLL时所需的DLL文件必须位于以下三个目录之一:
---- (1)Windows的系统目录:\windows\system;
---- (2)DOS中path所指出的任何目录;
---- (3)程序所在的目录;
---- 同时应注意管理好你的.lib文件和.h和文件
---- 2.建立一个工程,简单起见可建立一个控制台应用程序.
---- 3.在工程中引入a.lib:
---- (1)如果你的a.lib放在VC标准的LIB文件夹中.
单击Project- >Project Settings...
在link选卡的object/library modules中加上a.lib即可
---- (2)如果你的a.lib不是放在VC标准的LIB文件夹中
单击Project- >Add to Project- >files...
找到a.lib文件,按OK
< pre >
4.//------------------------
//call_a.cpp
#include< stdio.h >
#include "c:/a/a.h"
void main(void)
{ int c=0;
c=add(1,2);
printf("1+2=%d",c);
}
//本程序在VC5.0下调试通过
|
回复人: zhugw |
2007-4-2 17:57:39 |
Re:Re:用visual c++制作dll全过程
µ¼³öÎĵµÄÚÈÝ: d:\Program Files\GoldenSection Notes\export\Notes.csv
--------------------------------------------------------------------------------
Lesson19 动态库
2006-7-1 21:26:53
--------------------------------------------------------------------------------
动态链接库
自从微软推出第一个版本的Windows操作系统以来,动态链接库(DLL)一直是Windows操作系统的基础。
动态链接库通常都不能直接运行,也不能接收消息。它们是一些独立的文件,其中包含能被可执行程序或其它DLL调用来完成某项工作的函数。只有在其它模块调用动态链接库中的函数时,它才发挥作用。
Windows API中的所有函数都包含在DLL中。其中有3个最重要的DLL,Kernel32.dll,它包含用于管理内存、进程和线程的各个函数;User32.dll,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数;GDI32.dll,它包含用于画图和显示文本的各个函数。
C:\WINDOWS\system32
静态库和动态库
静态库:函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件(.EXE文件),不用和应用程序一起发布,但是应用程序比较大,静态库发生变化后应用程序必须重新编译。
在使用动态库的时候,往往提供两个文件:一个引入库和一个DLL。引入库包含被DLL导出的函数和变量的符号名,DLL包含实际的函数和数据。在编译链接可执行文件时,只需要链接引入库,DLL中的函数代码和数据并不复制到可执行文件中,在运行的时候,再去加载DLL,访问DLL中导出的函数。
使用动态链接库的好处
可以采用多种编程语言来编写。
增强产品的功能。
提供二次开发的平台。
简化项目管理。
可以节省磁盘空间和内存。
有助于资源的共享。
有助于实现应用程序的本地化。
动态链接库被多个进程访问
动态链接库加载的两种方式
隐式链接
显示加载
D:\Program Files\Microsoft Visual Studio\VC98\Bin\DUMPBIN.EXE -exports dllname.dll 查看动态库的导出函数
D:\Program Files\Microsoft Visual Studio\VC98\Bin\DUMPBIN.EXE -imports file.exe 查看应用程序使用的dll和函数
D:\Program Files\Microsoft Visual Studio\Tools\DEPENDS.EXE 查看应用程序使用的dll和函数
D:\Program Files\Microsoft Visual Studio\VC98\Bin\VCVARS32.BAT 建立vc的环境信息,每次执行只对当前命令窗口有效
@echo off
rem
rem Root of Visual Developer Studio Common files.
set VSCommonDir=D:\PROGRA~1\MICROS~2\Common
rem
rem Root of Visual Developer Studio installed files.
rem
set MSDevDir=D:\PROGRA~1\MICROS~2\Common\msdev98
rem
rem Root of Visual C++ installed files.
rem
set MSVCDir=D:\PROGRA~1\MICROS~2\VC98
rem
rem VcOsDir is used to help create either a Windows 95 or Windows NT specific path.
rem
set VcOsDir=WIN95
if "%OS%" == "Windows_NT" set VcOsDir=WINNT
rem
echo Setting environment for using Microsoft Visual C++ tools.
rem
if "%OS%" == "Windows_NT" set PATH=%MSDevDir%\BIN;%MSVCDir%\BIN;%VSCommonDir%\TOOLS\%VcOsDir%;%VSCommonDir%\TOOLS;%PATH%
if "%OS%" == "" set PATH="%MSDevDir%\BIN";"%MSVCDir%\BIN";"%VSCommonDir%\TOOLS\%VcOsDir%";"%VSCommonDir%\TOOLS";"%windir%\SYSTEM";"%PATH%"
set INCLUDE=%MSVCDir%\ATL\INCLUDE;%MSVCDir%\INCLUDE;%MSVCDir%\MFC\INCLUDE;%INCLUDE%
set LIB=%MSVCDir%\LIB;%MSVCDir%\MFC\LIB;%LIB%
set VcOsDir=
set VSCommonDir=
编写DllOne.dll
输出文件
查看导出函数(没有导出函数)
使用 _declspec(dllexport) 导出函数
产生 .lib文件(引入库文件)
在使用动态库的时候,往往提供两个文件:一个引入库和一个DLL。引入库包含被DLL导出的函数和变量的符号名,DLL包含实际的函数和数据。在编译链接可执行文件时,只需要链接引入库,DLL中的函数代码和数据并不复制到可执行文件中,在运行的时候,再去加载DLL,访问DLL中导出的函数。
查看导出函数1
ordinal 序号、RVA函数地址
name 由于c++实现函数重载进行名字改编(名字粉碎)
查看导出函数2
上面为导入函数,下面为导出函数,DllOne.dll没有导入函数,.exe有导入和导出函数
vc调用dll如下(必须被导出的函数才能被客户端程序调用)
//动态库代码如下
//导出Dll函数,需要在每个函数前增加
_declspec(dllexport) int add(int a, int b)
{
return a + b;
}
_declspec(dllexport) int subtract(int a, int b)
{
return a - b ;
}
提供.dll,.lib文件给客户端程序
release版本文件比较小
编译前是可以把debug,release目录删除的,编译后重新建立文件夹
可以编译成debug,release版本
debug版本文件比较大
客户端调用dll
在工程setting中取得输入库文件(当然要本项目能找到.lib文件,拷贝到当前目录)
如果没有找到输入库文件
如果工程没有设置.lib文件(报错)
定义了输入库,没有什么函数将报错(函数没有定义)
所以1.要设置工程的.lib文件,2.声明调用函数
/*---------------------------------------------------------
CVCDDllTestDlg.cpp中声明如下
动态库函数声明
1.函数声明可以不要extern关键字(变量什么必须要extern关键字)
int add(int a, int b);
int subtract(int,int);
2.
extern int add(int a, int b);
extern int subtract(int,int);
3.使用导入关键字(生成效率更高的代码,比较常用)
_declspec(dllimport) int add(int a, int b);
_declspec(dllimport) int subtract(int,int);
*/
_declspec(dllimport) int add(int a, int b);
_declspec(dllimport) int subtract(int,int);
void CVCDllTestDlg::OnButtonAdd()
{
CString str;
str.Format("10 + 10 = %d",add(10,10));
MessageBox(str);
}
void CVCDllTestDlg::OnButtonSubtract()
{
CString str;
str.Format("50 - 10 = %d",subtract(50,10));
AfxMessageBox(str);
}
编译程序,把动态库拷贝到相应的目录(发行软件时就不需要.lib文件了,但是动态库是必须的文件,
可以在path中设置,或者拷贝到当前目录,说明.lib文件只是动态库文件的函数名称映射表,链接程序要读此文件完成链接工作,
真正的函数和数据在dll中,所以必须要一起发布dll)
调用程序当然可以把dll函数什么放在头文件中(VCDllTestDlg.h : header file)
调用者也可以把函数什么写在头文件中
//DllOne.h
_declspec(dllimport) int add(int a,int b);
_declspec(dllimport) int subtract(int a,int b);
在使用到的地方包含此头文件
或者动态库的开发者定义头文件,调用者自己使用
当前目录是D:\Project\Study\SunXinC++\Lesson19\VCDllTest
#include "..\DllOne\DllOne.h"
..表示当前目录的上级目录
#include "..\\DllOne\\DllOne.h"
两个\\也是一样的效果
dll开发者统一提供头文件(他才知道函数的定义)
//DllOne.h
//没有定义则定义为导入函数(客户程序包含此文件时没有定义则声明为导入)
#ifndef DLLONE_API
#define DLLONE_API _declspec(dllimport)
#endif
//函数声明(没有包含在宏定义中,都要执行)
DLLONE_API int add(int a,int b);
DLLONE_API int subtract(int a,int b);
dll代码实现定义如下
//动态库实现文件,已经定义了
#define DLLONE_API _declspec(dllexport)
#include "DllOne.h"
/*DllOne.h展开时
没有定义则定义为导入函数(客户程序包含此文件时没有定义则声明为导入)
#ifndef DLLONE_API(现在定义了,则什么也不做)
#define DLLONE_API _declspec(dllimport)
#endif
//函数声明(这里总是要被执行)
DLLONE_API int add(int a,int b);
DLLONE_API int subtract(int a,int b);
*/
//定义时可以不要DLLONE_API,在声明中已经有了
int add(int a, int b)
{
return a + b;
}
int subtract(int a, int b)
{
return a - b ;
}
/*
DLLONE_API int add(int a, int b)
{
return a + b;
}
DLLONE_API int subtract(int a, int b)
{
return a - b ;
}
*/
调用者包含时#include "..\DllOne\DllOne.h"展开为
导出整个类(访问权限依然受类的控制)
class DLLONE_API MyClass
{
public:
void pubDisplay(int x, int y); //公有
private:
void PriDisplay(int x, int y); //私有
};
实现
#include <Windows.h>
#include <stdio.h>
void MyClass::pubDisplay(int x, int y)
{
//include Windows.h
HWND hwnd = GetForegroundWindow(); //调用者窗口
HDC hdc = GetDC(hwnd); //得到窗口设备
char buffer[20];
memset(buffer,0,20);
sprintf(buffer,"x=%d,y=%d",x,y);//<stdio.h>格式化
TextOut(hdc,0,0,buffer,strlen(buffer));//输出
ReleaseDC(hwnd,hdc);
}
void MyClass::PriDisplay(int x, int y)
{
//私有函数不能被调用,即使它被导出了
}
客户端调用
void CVCDllTestDlg::OnButtonExportClass()
{
MyClass obj;
//obj.PriDisplay(10,10); 私有不能被访问
obj.pubDisplay(10,10);
}
导出函数(私有函数不能被调用,即使它被导出了)
导入函数
//只是导出需要的函数
class MyClass
{
public:
DLLONE_API void pubDisplay(int x, int y); //公有,导出
//void DLLONE_API pubDisplay(int x, int y);//公有,导出也可以
private:
void PriDisplay(int x, int y); //私有,不导出
}
;
查看导出
没有导出不能被访问
按理说没有导出整个类,不能构造对象,但是c++没有限制,只要有public的函数被导出,就能调用
但是,如果导出的是private的成员函数,也不能被调用
如果构造函数为private,则即使导出也不能构造对象
class DLLONE_API MyClass
{
public:
void pubDisplay(int x, int y);
private:
MyClass()
{
}
}
;
调用约定
默认的调用约定为 _cdecl,和没有什么是一样的
DLLONE_API int _cdecl add(int a,int b); == DLLONE_API int add(int a,int b);
注意导出和导入的声明要匹配 extern "C" 必须为大写,有了extern "C"就不能导出类和成员函数,只能导出全局函数
导出 导入
_declspec(dllexport) int _stdcall add(int a,int b) _declspec(dllimport) int _stdcall add(int a,int b)
_declspec(dllexport) int add(int a,int b) _declspec(dllimport) int add(int a,int b)
_declspec(dllexport) int _cdecl add(int a,int b) _declspec(dllimport) int _cdecl add(int a,int b)
extern "C" _declspec(dllexport) int _stdcall add(int a,int b) extern "C"_declspec(dllimport) int _stdcall add(int a,int b)
在c++中,相同的编译器,不管用那一种声明,只要导出导入相同都可以调用dll中函数
调用约定 结果
_declspec(dllexport) int add(int a,int b) ?add@@YAHHH@Z
_declspec(dllexport) int _cdecl add(int a,int b) ?add@@YAHHH@Z
extern "C" _declspec(dllexport) int add(int a,int b) add
extern "C" _declspec(dllexport) int _cdecl add(int a,int b) add
_declspec(dllexport) int _stdcall add(int a,int b) ?add@@YGHHH@Z
extern "C" _declspec(dllexport) int _stdcall add(int a,int b) _add@8
dephi,pb,vb等语言必须用标准调用约定,pascal调用约定定义导出函数,__stdcall 函数的参数被从右到左推送到堆栈上,被调用函数在返回之前从堆栈中弹出这些参数。
_stdcall 标准调用约定,即pascal调用约定,或者winapi调用约定
没有/默认,和_cdecl为c调用约定,由调用者负责压堆,调用完成出栈
extern "C" 只是解决c++和c的名字改编方案,不是调用约定的改变,如果使用了调用约定,那么名称依然会改编
此时用模块定义文件可以解决名字改编问题,同时如果没有声明调用约定或者声明为_cdecl为c调用约定,要给pb,dephi调用
一样要使用_stdcall标准调用约定,模块定义文件中依然可以导出类成员函数,只要写函数名称就可以了,不一定要导出整个类
当然导出和导入的声明必须一致
extern "C"的使用不能导出类,因为c语言中没有class,当不管是按c导出还是c++导出,调用约定都要考虑
_cdecl
See Also
Argument Passing and Naming Conventions | C++ Keywords
Microsoft Specific
This is the default calling convention for C and C++ programs. Because the stack is cleaned up by the caller, it can do vararg functions. The __cdecl calling convention creates larger executables than __stdcall, because it requires each function call to include stack cleanup code. The following list shows the implementation of this calling convention.
Element Implementation
Argument-passing order Right to left
Stack-maintenance responsibility Calling function pops the arguments from the stack
Name-decoration convention Underscore character (_) is prefixed to names
Case-translation convention No case translation performed
Place the __cdecl modifier before a variable or a function name. Because the C naming and calling conventions are the default, the only time you need to use __cdecl is when you have specified the /Gz (stdcall) or /Gr (fastcall) compiler option. The /Gd compiler option forces the __cdecl calling convention.
Example
In the following example, the compiler is instructed to use C naming and calling conventions for the system function:
// Example of the __cdecl keyword on function
_CRTIMP int __cdecl system(const char *);
// Example of the __cdecl keyword on function pointer
typedef BOOL (__cdecl *funcname_ptr)(void * arg1, const char * arg2, DWORD flags, ...);
_stdcall
See Also
Argument Passing and Naming Conventions | C++ Keywords
Microsoft Specific
The __stdcall calling convention is used to call Win32 API functions. The callee cleans the stack, so the compiler makes vararg functions __cdecl. Functions that use this calling convention require a function prototype.
return-type __stdcall function-name[(argument-list)]
The following list shows the implementation of this calling convention.
Element Implementation
Argument-passing order Right to left.
Argument-passing convention By value, unless a pointer or reference type is passed.
Stack-maintenance responsibility Called function pops its own arguments from the stack.
Name-decoration convention An underscore (_) is prefixed to the name. The name is followed by the at sign (@) followed by the number of bytes (in decimal) in the argument list. Therefore, the function declared as int func( int a, double b ) is decorated as follows: _func@12
Case-translation convention None
The /Gz compiler option specifies __stdcall for all functions not explicitly declared with a different calling convention.
Functions declared using the __stdcall modifier return values the same way as functions declared using __cdecl.
Example
In the following example, use of __stdcall results in all WINAPI function types being handled as a standard call:
// Example of the __stdcall keyword
#define WINAPI __stdcall
// Example of the __stdcall keyword on function pointer
typedef BOOL (__stdcall *funcname_ptr)(void * arg1, const char * arg2, DWORD flags, ...);
See Also
Argument Passing and Naming Conventions | C++ Keywords
C++ Keywords
See Also
Lexical Conventions | C++ Operators
Keywords are predefined reserved identifiers that have special meanings. They cannot be used as identifiers in your program. The following keywords are reserved for Microsoft C++. Names with leading underscores are Microsoft extensions.
__abstract 2 __alignof __asm __assume
__based __box 2 __cdecl __declspec
__delegate 2 __event __except __fastcall
__finally __forceinline __gc 2 __hook 3
__identifier __if_exists __if_not_exists __inline
__int8 __int16 __int32 __int64
__interface __leave __m64 __m128
__m128d __m128i __multiple_inheritance __nogc 2
__noop __pin 2 __property 2 __raise
__sealed 2 __single_inheritance __stdcall __super
__try_cast 2 __try/__except,__try/__finally __unhook 3 __uuidof
__value 2 __virtual_inheritance __w64 bool
break case catch char
class const const_cast continue
default delete deprecated 1 dllexport 1
dllimport 1 do double dynamic_cast
else enum explicit extern
false float for friend
goto if inline int
long mutable naked 1 namespace
new noinline 1 noreturn 1 nothrow 1
novtable 1 operator private property 1
protected public register reinterpret_cast
return selectany 1 short signed
sizeof static static_cast struct
switch template this thread 1
throw true try typedef
typeid typename union unsigned
using declaration,
using directive uuid 1 virtual void
volatile __wchar_t, wchar_t while ?
Microsoft Specific
In Microsoft C++, identifiers with two leading underscores are reserved for compiler implementations. Therefore, the Microsoft convention is to precede Microsoft-specific keywords with double underscores. These words cannot be used as identifier names.
Microsoft extensions are enabled by default. To ensure that your programs are fully portable, you can disable Microsoft extensions by specifying the ANSI-compatible /Za command-line option (compile for ANSI compatibility) during compilation. When you do this, Microsoft-specific keywords are disabled.
When Microsoft extensions are enabled, you can use the Microsoft-specific keywords in your programs. For ANSI compliance, these keywords are prefaced by a double underscore. For backward compatibility, single-underscore versions of all the double-underscored keywords except __except, __finally, __leave, and __try are supported. In addition, __cdecl is available with no leading underscore.
The __asm keyword replaces C++ asm syntax. asm is reserved for compatibility with other C++ implementations, but not implemented. Use __asm.
The __based keyword has limited uses for 32-bit target compilations.
函数必须导出(_declspec(dllexport) 或者模块定义文件.def)
在函数前必须有_stdcall关键字
_declspec(dllexport) int add(int a,int b) ?add@@YAHHH@Z
_declspec(dllexport) int _cdecl add(int a,int b) ?add@@YAHHH@Z
_declspec(dllexport) int _stdcall add(int a,int b)?add@@YGHHH@Z
函数名称都有?......pb中认为是非法字符
extern "C" _declspec(dllexport) int add(int a,int b) add
Function int add(int a, int b) Library "DllOne.dll"
extern "C" _declspec(dllexport) int _cdecl add(int a,int b) add
Function int add(int a, int b) Library "DllOne.dll"
extern "C" _declspec(dllexport) int _stdcall add(int a,int b) _add@8
add可以
subtract错误
所以必须要用stdcall调用约定
_declspec(dllexport) int _stdcall add(int a,int b) ?add@@YGHHH@Z
但是函数名称被改编了
extern "C" _declspec(dllexport) int _stdcall add(int a,int b) _add@8
可以调用,但是前台调用函数不方便
怎么解决?--模块定义文件.def
动态库定义
int _stdcall add(int a, int b)
{
return a + b;
}
int _stdcall subtract(int a, int b)
{
return a - b;
}
模块定义文件(使用记事本定义DllTwo.def并且导入到工程中)
LIBRARY DllTwo
EXPORTS
add
subtract
Function int add(int a, int b) Library "DllTwo.dll"//stdcall .def
Function int subtract(int a, int b) Library "DllTwo.dll"//stdcall .def
int a,b,Result
a = 20
b = 10
Result = add(a,b) //stdcall .def ok
MessageBox('提示a+b',String(a) + "+" + String(b) + " = " + String(result))
Result = subtract(a,b) //stdcall .def ok
MessageBox('提示a-b',String(a) + "-" + String(b) + " = " + String(result))
使用模块定义文件也导出了函数(_stdcall导出,函数名称没有改编,是我想要的结果,pb可以调用了,但是c++怎么调用???)
copy模块定义文件到工程目录,工程设置DllTwo.lib
声明函数(不是_stdcall导入),不能调用
_declspec(dllimport) int add(int a, int b);
_declspec(dllimport) int subtract(int,int);
void CVCDllTestDlg::OnButtonAdd()
{
CString str;
str.Format("10 + 10 = %d",add(10,10));
MessageBox(str);
}
void CVCDllTestDlg::OnButtonSubtract()
{
CString str;
str.Format("50 - 10 = %d",subtract(50,10));
AfxMessageBox(str);
}
调用约定必须要一致才能调用(当然也可以提供.h给客户端使用)
_declspec(dllimport) int _stdcall add(int a, int b);
_declspec(dllimport) int _stdcall subtract(int,int);
模块定义文件怎么导出类(????),目前不知道,可以用模块定义文件导出全局函数,dllexport导出类结合使用
1.def
LIBRARY DllTwo
EXPORTS
add
subtract
2.dll
//.def中
int _stdcall add(int a, int b)
{
return a + b;
}
//.def中
int _stdcall subtract(int a, int b)
{
return a - b;
}
//_declspec(dllexport)
class _declspec(dllexport) MyClass
{
public:
void _stdcall display();
};
void MyClass::display()
{
}
3.使用(别忘了.lib文件)
_declspec(dllimport) int _stdcall add(int a, int b);
_declspec(dllimport) int _stdcall subtract(int,int);
class _declspec(dllimport) MyClass
{
public:
void _stdcall display();
};
隐式链接
将可执行文件链接到 DLL
为隐式链接到 DLL,可执行文件必须从 DLL 的提供程序获取下列各项:
包含导出函数和/或 C++ 类的声明的头文件(.H 文件)。
要链接的导入库(.LIB files)。(生成 DLL 时链接器创建导入库。)
实际的 DLL(.DLL 文件)。
使用 DLL 的可执行文件必须包括头文件,此头文件包含每个源文件中的导出函数(或 C++ 类),而这些源文件包含对导出函数的调用。从编码的角度讲,导出函数的函数调用与任何其他函数调用一样。若要生成调用可执行文件,必须与导入库链接。如果使用的是外部生成文件,请指定导入库的文件名,此导入库中列出了要链接到的其他对象 (.OBJ) 文件或库。
操作系统在加载调用可执行文件时,必须能够定位 .DLL 文件。
显式链接
将可执行文件链接到 DLL在显式链接下,应用程序必须进行函数调用以在运行时显式加载 DLL。为显式链接到 DLL,应用程序必须:
调用 LoadLibrary(或相似的函数)以加载 DLL 和获取模块句柄。
调用 GetProcAddress,以获取指向应用程序要调用的每个导出函数的函数指针。由于应用程序是通过指针调用 DLL 的函数,编译器不生成外部引用,故无需与导入库链接。
使用完 DLL 后调用 FreeLibrary。
例如:
typedef UINT (CALLBACK* LPFNDLLFUNC1)(DWORD,UINT);
...
HINSTANCE hDLL; // Handle to DLL
LPFNDLLFUNC1 lpfnDllFunc1; // Function pointer
DWORD dwParam1;
UINT uParam2, uReturnVal;
hDLL = LoadLibrary("MyDLL");
if (hDLL != NULL)
{
lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress(hDLL,
"DLLFunc1");
if (!lpfnDllFunc1)
{
// handle the error
FreeLibrary(hDLL);
return SOME_ERROR_CODE;
}
else
{
// call the function
uReturnVal = lpfnDllFunc1(dwParam1, uParam2);
}
}
LoadLibrary
进程调用 LoadLibrary(或 AfxLoadLibrary)以显式链接到 DLL。如果成功,函数将指定的 DLL 映射到调用进程的地址空间中并返回此 DLL 的句柄,该句柄可与用于显式链接的其他函数(如 GetProcAddress 和 FreeLibrary)一起使用。
LoadLibrary 尝试使用用于隐式链接的同一搜索序列来定位 DLL。如果系统无法找到 DLL 或者入口点函数返回 FALSE,LoadLibrary 将返回 NULL。如果对 LoadLibrary 的调用所指定的 DLL 模块已映射到调用进程的地址空间中,则函数仅返回 DLL 的句柄并递增模块的引用数。
如果 DLL 有入口点函数,则操作系统在调用 LoadLibrary 的进程上下文中调用此函数。如果由于以前调用了 LoadLibrary 但没有相应地调用 FreeLibrary 函数而导致 DLL 已经附加到进程,则不会调用此入口点函数。
加载扩展 DLL 的 MFC 应用程序应使用 AfxLoadLibrary 而不是 LoadLibrary。AfxLoadLibrary 在调用 LoadLibrary 之前处理线程同步。AfxLoadLibrary 的接口(函数原型)与 LoadLibrary 相同。
如果出于某种原因 Windows 无法加载 DLL,进程可以尝试从错误恢复。例如,进程可通知用户所发生的错误,并让用户指定 DLL 的其他路径。
安全说明???如果代码将在 Windows NT 4 或 Windows 2000 上运行,请务必要指定任何 DLL 的完整路径名。
GetProcAddress
显式链接到 DLL 的进程调用 GetProcAddress 来获取 DLL 导出函数的地址。使用返回的函数指针调用 DLL 函数。GetProcAddress 将(由 LoadLibrary、AfxLoadLibrary 或 GetModuleHandle 返回的)DLL 模块句柄和要调用的函数名或函数的导出序号用作参数。
由于是通过指针调用 DLL 函数并且没有编译时类型检查,需确保函数的参数是正确的,以便不会超出在堆栈上分配的内存和不会导致访问冲突。确保类型安全的一种方法是查看导出函数的函数原型,并创建函数指针的匹配 typedef。例如:
typedef UINT (CALLBACK* LPFNDLLFUNC1)(DWORD,UINT);
...
HINSTANCE hDLL; // Handle to DLL
LPFNDLLFUNC1 lpfnDllFunc1; // Function pointer
DWORD dwParam1;
UINT uParam2, uReturnVal;
hDLL = LoadLibrary("MyDLL");
if (hDLL != NULL)
{
lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress(hDLL,
"DLLFunc1");
if (!lpfnDllFunc1)
{
// handle the error
FreeLibrary(hDLL);
return SOME_ERROR_CODE;
}
else
{
// call the function
uReturnVal = lpfnDllFunc1(dwParam1, uParam2);
}
}
调用 GetProcAddress 时指定所需函数的方式取决于 DLL 的生成方式。
仅当要链接到的 DLL 是用模块定义 (.DEF) 文件生成的,并且序号在 DLL 的 .DEF 文件的 EXPORTS 节中与函数一起列出时,才能获取导出序号。如果 DLL 具有许多导出函数,则相对于使用函数名,使用导出序号调用 GetProcAddress 的速度稍快一些,因为导出序号是 DLL 导出表的索引。使用导出序号,GetProcAddress 可直接定位函数,而不是将指定名称与 DLL 导出表中的函数名进行比较。但是,仅当有权控制 .DEF 文件中导出函数的序号分配时,才应使用导出序号调用 GetProcAddress。
FreeLibrary
The FreeLibrary function decrements the reference count of the loaded dynamic-link library (DLL). When the reference count reaches zero, the module is unmapped from the address space of the calling process and the handle is no longer valid.
BOOL FreeLibrary(
HMODULE hModule
);
Parameters
hModule [in] Handle to the loaded DLL module. The LoadLibrary or GetModuleHandle function returns this handle.
Return Values
If the function succeeds, the return value is nonzero.
If the function fails, the return value is zero. To get extended error information, call GetLastError.
Windows 用来定位 DLL 的搜索路径
请参见
DLL
在显式链接和隐式链接下,Windows 都首先搜索一组预安装的 DLL,如性能库 (KERNEL32.DLL) 和安全库 (USER32.DLL)。Windows 然后按下列顺序搜索 DLL:
当前进程的可执行模块所在的目录。
当前目录。
Windows 系统目录。GetSystemDirectory 函数检索此目录的路径。
Windows 目录。GetWindowsDirectory 函数检索此目录的路径。
PATH 环境变量中列出的目录。
注意未使用 LIBPATH 环境变量。
//动态加载dll,工程中去掉DllTwo.lib
void CVCDllTestDlg::OnButtonAdd()
{
//不区分大小写,.dll可以省略
HMODULE hmodule = LoadLibrary("DllTwo.dll");
if (hmodule == NULL)
{
MessageBox("加载dll错误!");
return;
}
/*windef.h
#define CALLBACK _stdcall
#define WINAPI _stdcall
#define WINAPIV _cdecl
#define APIENTRY WINAPI
#define APIPRIVATE _stdcall
#define PASCAL _stdcall
*/
//导出使用_stdcall,调用时必须匹配,也可以使用上面的宏定义
typedef int (_stdcall*TypeAddProc)(int a, int b);
TypeAddProc add = NULL;
//函数名称大小写敏感,必须转换为函数指针类型
add = (TypeAddProc)GetProcAddress(hmodule,"add");
if (add == NULL)
{ //释放
FreeLibrary(hmodule);
MessageBox("获得函数地址错误!");
return;
}
CString str;
str.Format("10 + 10 = %d",add(10,10));
MessageBox(str);
//释放
FreeLibrary(hmodule);
}
调用是也可以直接用导出的发生名字改编的名称调用
ADDPROC Add=(ADDPROC)GetProcAddress(hInst,"?add@@YAHHH@Z");
也可以用序号调MAKEINTRESOURCE
ADDPROC Add=(ADDPROC)GetProcAddress(hInst,MAKEINTRESOURCE(1));
FARPROC GetProcAddress(
HMODULE hModule,
LPCSTR lpProcName
);
The MAKEINTRESOURCE macro converts an integer value to a resource type compatible with the resource-management functions. This macro is used in place of a string containing the name of the resource.
LPTSTR MAKEINTRESOURCE
(
WORD wInteger
);
一个完整的导出文件(def文件也可以导出类)
//.def中
int _stdcall add(int a, int b)
{
return a + b;
}
//.def中
int _stdcall subtract(int a, int b)
{
return a - b;
}
//_declspec(dllexport)
/*class _declspec(dllexport) MyClass
{
public:
int _stdcall display();
};
int MyClass::display()
{
return 100;
}
*/
//在.def中导出
class MyClass
{
public:
int _stdcall displayOne();
int _stdcall displayTwo();
int _stdcall displayThree();
};
int MyClass::displayOne()
{
return 100;
}
int MyClass::displayTwo()
{
return 200;
}
int MyClass::displayThree()
{
return 300;
}
导出模块定义文件
LIBRARY DllTwo
EXPORTS
add
subtract
displayOne
displayTwo
displayThree
产生mapfile
模块定义文件也可以使用mapfile中名称
class MyClass
{
public:
int _stdcall displayOne();
int _stdcall displayTwo();
int _stdcall displayThree();
};
int MyClass::displayOne()
{
return 1;
}
int MyClass::displayTwo()
{
return 2;
}
int MyClass::displayThree()
{
return 3;
}
导出
LIBRARY DllTwo
EXPORTS
add
subtract
?displayOne@MyClass@@QAGHXZ
displayTwo
?displayThree@MyClass@@QAGHXZ
调用
_declspec(dllimport) int _stdcall add(int a, int b);
_declspec(dllimport) int _stdcall subtract(int a, int b);
class _declspec(dllimport) MyClass
{
public:
int _stdcall displayOne();
int _stdcall displayTwo();
int _stdcall displayThree();
};
void CVCDllTestDlg::OnButtonAdd()
{
CString str;
str.Format("10 + 10 = %d",add(10,10));
MessageBox(str);
}
void CVCDllTestDlg::OnButtonSubtract()
{
CString str;
str.Format("50 - 10 = %d",subtract(50,10));
AfxMessageBox(str);
}
void CVCDllTestDlg::OnButtonExportClass()
{
MyClass obj ;
CString str;
str.Format(".def导出类成员函数displayOne = %d",obj.displayOne());
AfxMessageBox(str);
str.Format(".def导出类成员函数displayTwo = %d",obj.displayTwo());
AfxMessageBox(str);
str.Format(".def导出类成员函数displayThree = %d",obj.displayThree());
AfxMessageBox(str);
}
参见
MSDN DLL介绍
Visual C++ 概念:添加功能
DLL
动态链接库 (DLL) 是作为共享函数库的可执行文件。动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个 DLL 中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。DLL 还有助于共享数据和资源。多个应用程序可同时访问内存中单个 DLL 副本的内容。
动态链接与静态链接的不同之处在于:动态链接允许可执行模块(.dll 文件或 .exe 文件)仅包含在运行时定位 DLL 函数的可执行代码所需的信息。在静态链接中,链接器从静态链接库获取所有被引用的函数,并将库同代码一起放到可执行文件中。
使用动态链接代替静态链接有若干优点。DLL 节省内存,减少交换操作,节省磁盘空间,更易于升级,提供售后支持,提供扩展 MFC 库类的机制,支持多语言程序,并使国际版本的创建轻松完成。
本文章族提供有关编程 DLL 的详细信息。
本节内容
应用程序和 DLL 之间的差异 描述应用程序和 DLL 之间的基本差异。
使用 DLL 的优点 描述动态链接的优点。
DLL 类型 讨论如何决定在项目中使用何种 DLL。
Win16 DLL 与 Win32 DLL 之间的差异 描述 Win16 DLL 和 Win32 DLL 之间的更改。
DLL 常见问题 提供有关 DLL 的常见问题解答。
将可执行文件链接到 DLL 描述与 DLL 的显式链接和隐式链接。
初始化 DLL 讨论当 DLL 加载时必须执行的 DLL 初始化代码(如分配内存)。
运行时库行为 描述运行时库如何执行 DLL 启动序列。
LoadLibrary 和 AfxLoadLibrary 讨论如何使用 LoadLibrary 和 AfxLoadLibrary 显式链接到 DLL。
GetProcAddress 讨论如何使用 GetProcAddress 获取 DLL 中导出函数的地址。
FreeLibrary 和 AfxFreeLibrary 讨论当不再需要 DLL 模块时如何使用 FreeLibrary 和 AfxFreeLibrary。
Windows 用来定位 DLL 的搜索路径 描述 Windows 操作系统用来定位系统上的 DLL 的搜索路径。
动态链接到 MFC 的规则 DLL 的模块状态 描述动态链接到 MFC 的规则 DLL 的模块状态。
扩展 DLL 解释通常实现从现有 Microsoft 基础类库类派生的可重用类的 DLL。
创建纯资源 DLL 讨论只包含资源(如图标、位图、字符串和对话框等)的纯资源 DLL。
MFC 应用程序中的本地化资源:附属 DLL 提供对附属 DLL 的增强支持,该功能有助于创建针对多种语言进行本地化的应用程序。 导入和导出 描述如何将公共符号导入应用程序或从 DLL 导出函数。
Active 技术和 DLL 使对象服务器得以在 DLL 内完全实现。
DLL 中的自动化 描述“MFC DLL 向导”中的“自动化”选项提供的内容。
MFC DLL 命名约定 讨论 MFC 中包含的 DLL 和库如何遵循结构化命名约定。
从 Visual Basic 应用程序调用 DLL 函数 描述如何从 Visual Basic 应用程序中调用 DLL 函数。
相关章节
将 MFC 用作 DLL 的一部分 描述规则 DLL,它使您可以将 MFC 库作为 Windows 动态链接库的一部分来使用。 MFC 的 DLL 版本 描述如何将 MFCxx.DLL 和 MFCxxD.DLL(其中 x 是 MFC 版本号)共享动态链接库用于 MFC 应用程序和扩展 DLL。 添加功能 提供有关下列内容的主题链接:描述有关 Visual C++ 库的概念信息和讨论各种编码技术和方法。