导出动态库中的函数和类首先需要加载动态库,加载的方式有两种,分为:隐式加载和显示加载。(编译器:VC++ 6.0)
对隐式加载还是显示加载的选择不明白请百度。
(一)隐式加载动态库
新建动态库工程,添加头文件和源文件,用以下几种方式分别导出“加减乘除”4个函数和point类,由于实现函数需要,因此添加了两个头文件。
动态库工程头文件(dlltest.h)代码如下:
#include<windows.h>
#include<stdio.h>
//_declspec(dllexport)作为导出函数的前缀,但是导出的函数名会发生改变
_declspec(dllexport) int add(int a, int b);
//extern "C"作为导出函数的前缀,导出的函数名不会发生改变,可以解决C和C++函数
//调用时名字改编的问题,但是extern "C"只能导出全局函数
extern "C" _declspec(dllexport) int sub(int m, int n);
//添加.def文件导出此函数,解决了标准调用(pascal)和c调用之间名字改编的问题。
//当实际函数名和def文件设置要导出的函数名一致时,导出的函数按实际名称导出;
_declspec(dllexport) int mul(int x, int y);
//当二者不一致时,可以按照如下格式导出函数:entryname = internalname
_declspec(dllexport) int fun(int i, int j);
class _declspec(dllexport) point
{
public:
point(int x, int y);
void output();
private:
int m;
int n;
};
动态库工程源文件(dlltest.cpp)代码如下:
#include "dlltest.h"
int add(int a, int b)
{
return a+b;
}
int sub(int m, int n)
{
return m-n;
}
int mul(int x, int y)
{
return x*y;
}
int fun(int i, int j)
{
return i/j;
}
point::point(int x, int y)
{
m = x;
n = y;
}
void point::output()
{
HWND m_hwnd = GetForegroundWindow();
HDC m_hdc = GetDC(m_hwnd);
char buf[20] = {0};
sprintf(buf,"x=%d, y=%d",m,n);
TextOut(m_hdc, 0, 0, buf, strlen(buf));
ReleaseDC(m_hwnd, m_hdc);
}
动态库工程编译成功之后生成引入库文件(dlltest.lib)和动态库文件(dlltest.dll).
导出的函数和类在VC++ 6.0自带的Denpedency Walker中查看如下所示,可以看到有的函数名字已经改变了,而有的函数没有改变:
接下来创建一个对话框工程,在对话框只放置一个按钮,按钮里对动态库的导出函数进行测试,分别创建对话框工程的头文件和源文件。
对话框工程的头文件(dllimport.h)代码如下:
//extern作为前缀声明add是外部定义的,此处也可以用_declspec(dllimport)
//做外部声明,效率更高。
extern int add(int a, int b);
//由于在动态库中定义时前面还添加了extern "C" ,因此这里也必须添加,否
//则编译不成功!
extern "C" _declspec(dllimport) int sub(int m, int n);<pre name="code" class="cpp">
//_declspec(dllimport)为前缀声明point类为导入类;//extern "C"只能用来导出全局函数,不能导出类和成员函数。class _declspec(dllimport) point {public:point(int x, int y);void output();private:int m;int n;};
对话框工程源文件(dlltest_jiemianDlg.cpp)按钮响应函数添加代码如下:
void CDlltest_jiemianDlg::OnBtnTest()
{
// TODO: Add your control notification handler code here
point pt(3,5);
pt.output();
int num;
CString strNum;
num = add(1,2);
strNum.Format("%d",num);
MessageBox(strNum);
num = sub(5,3);
strNum.Format("%d",num);
MessageBox(strNum);
}
对话框生成可执行程序(dlltest_jiemian.exe)
当对话框程序静态调用动态库时,需要做到3个步骤即可实现调用:
1>先把动态库的引入库文件和动态库文件放到对话框工程目录或Debug目录;
2>在对话框工程的Project->Settings->Object/library modules:设置栏添加动态库的引入库文件;
3>确保对话框对动态库中的导出函数和类做了声明(即是否包含dllimport.h这类文件)。
经测试,可以调用动态库导出的函数和类,结果如下图:
(二)显示加载动态库
由于显示加载和隐式加载动态库差不太多,这里就简单讲了。
显示加载和隐式加载声明函数的方式有很大不同,需注意!
头文件添加如下代码,此处的ADDFUC是定义的函数指针,声明sub函数式外部定义:
//>>=======================显式调用动态库dlltest.dll======================>>
//注意:显式调用动态库无法导入类和类成员函数!!
typedef int (*SUBFUC)(int a, int b);
//<<=======================显式调用动态库dlltest.dll======================<<
源文件的按钮响应函数添加如下代码:
void CDlltest_jiemianDlg::OnBtnTest()
{
// TODO: Add your control notification handler code here
HMODULE hmodule;
int num;
CString strNum;
hmodule = LoadLibrary("dlltest.dll");
if (NULL == hmodule)
{
MessageBox("can not find the dll");
return;
}
SUBFUC subfun = (SUBFUC)GetProcAddress(hmodule, "sub");
if (!subfun)
{
MessageBox("get fun address failure");
return;
}
num = subfun(5, 3);
strNum.Format("%d",num);
MessageBox("5-3="+strNum);
}
运行结果如下:
由于动态库导出的add函数名字改成成了“?add@@YAHHH@”,因此若要导出add函数,则GetProcAddress函数的第二个参数不能之家些“add”,
而应该写“?add@@YAHHH@”,表示导出动态库的add函数,修改如下:
SUBFUC subfun = (SUBFUC)GetProcAddress(hmodule, "?add@@YAHHH@Z");
动态加载动态库有几个关键函数要注意,具体用法自行百度:
1>HMODULE WINAPI LoadLibrary( _In_ LPCTSTR lpFileName);
2>BOOL WINAPI FreeLibrary( _In_ HMODULE hModule);
3>FARPROC GetProcAddress(HMODULE hModule,LPCSTR lpProcName);
当对话框程序静态调用动态库时,需要做到3个步骤即可实现调用:
1>先把动态库的引入库文件和动态库文件放到对话框工程目录或Debug目录;
2>确保对话框对动态库中的导出函数和类做了声明(即是否包含dllimport.h这类文件)。
尤其注意,不要在对话框工程的Project->Settings->Object/library modules:设置栏添加动态库的引入库文件!