一 建立工程dll1
工程就一个dll1.cpp文件,代码如下:
_declspec(dllexport)
int
add(
int
a,
int
b)
{
return
a
+
b;
}
_declspec(dllexport)
int
subtract(
int
a,
int
b)
{
return
a
-
b;
}
会在dubeg目录下生成dll1.lib和dll1.dll两个主要文件
二 建立测试程序dllTest
extern
int
add(
int
,
int
);
extern
int
subtract(
int
,
int
);
void
CdllTestDlg::OnBnClickedButton1()
{
//
TODO: 在此添加控件通知处理程序代码
CString str;
str.Format(_T(
"
5+3=%d
"
),add(
5
,
3
));
MessageBox(str);
}
void
CdllTestDlg::OnBnClickedButton2()
{
//
TODO: 在此添加控件通知处理程序代码
CString str;
str.Format(_T(
"
5-3=%d
"
),subtract(
5
,
3
));
MessageBox(str);
}
编译时出错:
error LNK2019: 无法解析的外部符号 "int __cdecl add(int,int)" (?add@@YAHHH@Z),该符号在函数 "public: void __thiscall CdllTestDlg::OnBnClickedButton1(void)" (?OnBnClickedButton1@CdllTestDlg@@QAEXXZ) 中被引用
error LNK2019: 无法解析的外部符号 "int __cdecl subtract(int,int)" (?subtract@@YAHHH@Z),该符号在函数 "public: void __thiscall CdllTestDlg::OnBnClickedButton2(void)" (?OnBnClickedButton2@CdllTestDlg@@QAEXXZ) 中被引用
解决办法:
连接的时候要找到这两个函数的实现
将用到的dll1.dll的引入库文件dll1.lib文件拷到当前工程的目录下
#pragma
comment(lib,"dll1.lib")
上面的代码在编译的时候是没有问题的,输出如下:
1>------ 已启动生成: 项目: dllTest, 配置: Debug Win32 ------
1>正在编译...
1>正在跳过...(未检测到相关更改)
1>dllTestDlg.cpp
1>生成日志保存在“file://e:\vs2005项目\dllTest\dllTest\Debug\BuildLog.htm”
1>dllTest - 0 个错误,0 个警告
========== 生成: 1 已成功, 0 已失败, 0 最新, 0 已跳过 ==========
但是在运行的时候还是出错了,提示没有找到dll1.dll。我们发现在编译时不会出错,但是运行时出错,这是因为DLL是运行时才加载到进程空间的,所以是动态链接库。那就把dll1.dll加到当前工程的目录下就可以
DLL的搜寻路径:
1)进程的当前目录
2)Windows目录的系统目录
3)Windows目录
4)PATH环境变量中列出的各个目录
我们之前是用extern来声明DLL中的这些函数是在外部可以找到,我们可以直接告诉编译器这些是在dll的lib文件中找到。
//
从外部引入
//
extern int add(int,int);
//
extern int subtract(int,int);
//
告诉编译器我们是从.lib文件引入
//
注意:这里是_declspec(dllimport)而不是_declspec(dllexport)是引入而不是导出
_declspec(dllimport)
int
add(
int
,
int
);
_declspec(dllimport)
int
subtract(
int
,
int
);
void
CdllTestDlg::OnBnClickedButton1()
{
//
TODO: 在此添加控件通知处理程序代码
CString str;
str.Format(_T(
"
5+3=%d
"
),add(
5
,
3
));
MessageBox(str);
}
void
CdllTestDlg::OnBnClickedButton2()
{
//
TODO: 在此添加控件通知处理程序代码
CString str;
str.Format(_T(
"
5-3=%d
"
),subtract(
5
,
3
));
MessageBox(str);
}
我们发现这样在代码中这样的声明是很杂乱的,不利于管理,我们希望把这些导出函数的声明都由DLL的制作者来提高,因为本来DLL提供哪些导出函数
也只有DLL的制作者才知道和了解,那么就应该由DLL的制作者来提供一个.h文件
/*
dll1.h(v1.0)声明这些函数是DLL的导出函数
*/
_declspec(dllimport)
int
add(
int
a,
int
b);
_declspec(dllimport)
int
subtract(
int
a,
int
b);
这样我们的用户只需要
包含这个头文件就可以直接使用了
#include
"
..\dll1\dll1.h
" //返回当前目录的上一层目录的dll1文件夹下的dll1.h
现在我们想这个头文件同时包含在dll1.cpp和dllTest.cpp里面我们怎么办,要实现的功能显然是不一样的,我们想到了条件编译。
/*
dll1.h(v2.0)
*/
#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);
在dll1.cpp下面这样来做
/*
dll1.cpp
*/
#define
DLL1_API _declspec(dllexport)
#include
"
Dll1.h
"
int
add(
int
a,
int
b)
{
return
a
+
b;
}
int
subtract(
int
a,
int
b)
{
return
a
-
b;
}
在DLL的创建者这边,先定义了DLL1_API为导出函数,如果不在dll1.h之前这么定义,那么实际上就被dll1.h定义成导入函数,而我们
DLL的使用者就不需要定义这个宏,直接包含了就可以了。
我们不但是只想用dll中的全局函数,还想用dll中的类,我们怎么办。
class
DLL1_API Point
{
public
:
void
output(
int
x,
int
y);
void
test();
};
这样这个类就可以被dllTest.cpp使用了,在dllTest.cpp中#include "..\dll1\dll1.h"就可以像下面这样使用了
/*
dll1.cpp
*/
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);
}
void
Point::test()
{
}
/*
dllTest.cpp
*/
void CDllTestDlg::OnBtnOutput()
{
// TODO: Add your control notification handler code here
Point pt;
pt.output(5,3);
}
如果我们不想导出整个类而只是想导出类中的某个成员函数,我们可以这么做
class
/*
DLL1_API
*/
Point
{
public
:
DLL1_API
void
output(
int
x,
int
y);//只导出成员函数output
void
test();
};
是