📄 19.5 从 dll中导出 c++类.txt
字号:
19.5 从 DLL中导出 C++类
上面,我们介绍了如何从动态链接库中导出函数,以供其他程序使用。实际上,在一个动态链接库中还可以导出一个 C++类。为了实现这样的功能,仍以 Dll1为例,首先打开 Dll1工程,然后在 Dll l. h文件中添加如例 19-10所示代码。
例 19-10
class DLL1 API point
public:
void output(int x , int y);
如例 19-10所示代码定义一个 Point类,井且为该类定义了一个 public访问权限的函数: output,该函数有两个 int类型的参数: X和 y。为了从动态链接库中导出一个类,需要在class关键字和类名之间加入导出标识符,这样就可以导出整个类了。但读者应注意,在访问该类的函数时,仍受限于函数自身的访问权限。也就是说,如果该类的某个函数访问权限是 private,那么外部程序仍无法访问这个函数。
本例中,我们希望当外部程序调用 Point类的 output函数时,该函数将参数 x和 y的值显示在该调用程序的窗口中。因此,接下来,在 Dll1.cpp文件中实现 Point这个类中的成员函数: output,结果如例 19-11所示。
例 19-11
void Point: :output(int x , int y)
//返回调用者进程当前正在使用的那个窗口的句柄
HWND hwnd =GetForegroundWindow( ) ;
//获取 DC
HDC hdc=GetDC(hwnd};
char buf [20);
memset(buf , 0 , 20) ;
sprintf(buf , "x=%d,y=%d",x,y};
//输出坐标
TextOut(hdc , 0, 0,buf , strl en (buf}};
//释放DC
ReleaseDC(hwnd , hdc ) ;
Windows API提供的GetForegroundWindow函数将返回前景窗口的句柄,这个前景窗口就是当前用户正在使用的那个程序窗口。因此在上述例 19-11所示代码中,通过调用 GetForegroundWindow函数,获得调用者进程当前正在使用的那个窗口的句柄。对于上述例子来说, Dll1.dll的客户端程序是DllTest程序,于是调用GetForegroundWindow函数后得到的就是DllTest程序主对话框窗口的句柄。
得到了窗口句柄之后,该窗口的DC也就得到了 (通过调用GetDC函数得到)。之后定义了一个字符缓冲区 Cbuf),并通过调用memset函数将该字符数组中的元素都置为0。然后调用 sprintf函数将坐标 Cx,y)格式化到该字符数组中。最后,就可以调用TextOut函数在窗口 0,0)位置处输出坐标了。在上述例19-11所示代码的最后调用ReleaseDC函数释放设备句柄。
因为上述例 19-11所示代码中使用了WindowsAPI函数,所以程序需要包含相应的头文件: Windows.h>另外,还需要包含C语言的标准输入/输出头文件: stdio.h。也就是说,需要在Dll1.cpp文件的前部添加下述代码:
.
#include <Windows.h>
#include <stdio.h>
然后利用Build命令生成Dll1动态链接库。并将生成的动态链接库文件: Dll1.dll和引入库文件: Dll1.lib复制到测试工程: DllTest所在目录下。读者一定要注意,如果采用隐式链接方式加载DLL,一旦Dll1.dll更新了,一定要将新的Dll1.dll和Dll1.lib文件复制到测试工程下。为了避免这么麻烦,读者可以把动态链接库文件所在目录添加到系统的环境变量: pa由中,这样就不需要将Dlll.dll和Dlll.lib文件复制到测试程序所在目录下,后者可以自动搜索到这两个文件。
为了测试这个新生成的DLL,接着来打开DllTest工程,并在其对话框中再增加一个按钮,将其ID属性设置为IDC_BTN_OUTPUT, Caption属性设置为: Output,然后双击该按钮,为该按钮添加鼠标单击命令响应函数:OnBtnOutput,并在此函数中添加如例 19-12所示代码。
例 19-12
void CDIITestDlg::OnBtnOutput(}
// TODO: Add your control notification handler code here
Point pt;
pt.output(5 , 3) ;
在上述如例19-12所示代码中,首先定义了一个Point类型的对象:pt,然后调用该对象的output函数。
利用Build命令生成DIITest执行程序井执行,但程序弹出如图 19.12所示的提示对话框,提示:无法定位程序输入点?output@Point@@QAEXHH@Z于动态链接库Dlll.dl1上。
无法找到入口型时'
图 19.12错误提示对话框
之所以会出现上述问题,是因为先前在介绍 Depends工具时,为了让DllTest进程能够找到Dll1.dll文件,而将该文件复制到DllTest工程目录下的Debug目录下了。应用程序在链接时,首先会在该程序加载目录下查找所需文件,即这时首先会在DllTest程序的debug目录中寻找所需要的文件,然后才会在工程当前所在路径下查找,因此这时 DllTest目录下最新的Dlll.dll文件并没有被加载,加载的实际上是debug目录下的Dlll.dll,但是因为这时该Dl1l.dll文件不是最新版本,在其中没有找到Point类的output函数,所以程序运行就报告了上述错误。
为了解决这个问题,只需将DllTest工程所在目录下的Debug目录下的Dll1.dll文件删除即可。然后再次运行DIITest程序,当程序窗口显示后,单击该窗口上的【Output】按钮,即可在该程序窗口的左上角看到输出的信息,如图 19.13所示。
图 19.13访问DLL导出的C++类的成员函数的结果
现在,我们可以利用Dumpbin命令的exports选项查看Dlll.dl1这一动态链接库的导出情况。结果如图 19.14所示。可以看到,这时, Dlll.dl1导出了一个类: Point,还导出了这个类中的成员函数: output图 19.14 Dll 1. dll的导出信息(三〉
同样,可以利用 Dumpbin命令的 imports选项查看测试程序的导入情况。结果如图 19.15所示,可以看到, DllTest这一可执行程序从 Dll l. dl1中导入了 subtract、 add和 Point类的 output函数。
图 19.15 DllTest可执行程序的导入信息(二〉
另外,在实现动态链接库时,可以不导出整个类,而只导出该类中的某些函数。下面仍以 Dll1为例来讲解这种功能的实现。首先在 VC++开发环境中打开 Dll1工程,在 Dll1 .h这一头文件中,将声明 Point类时使用的 DLL1_API宏注释起来,然后在 output函数的声明前放置 DLLl API宏。这样,就表示只导出 Point类中的成员函数: output。为了证实 DLL仅导出 Point类的 output这一函数,而没有导出其他成员函数,我们为 Point这个类再添加一个成员函数: test,这时 Point类的定义代码如例 19-13所示。
19IJ 19-13
class /*DLL1_API*/ Point
public : . void DLL1_API output(int x , int y);
void test();
接着在 Dll1.cpp文件中添加 test
函数的实现,代码如例 19-14所示,该函数不做任何
处理。
例19-14
void Point::test()
.
}
然后,利用 Build命令生成 Dll1.dll,并再次利用 dumpbin命令的 exports选项查看最新的 Dll l.dll的导出信息。结果如图 19.16所示,可以看到,这时对 Point类来说, Dlll.dll 仅导出了它的 output成员函数。对于该类其他成员函数来说,因为在声明时,没有指定表示导出函数的宏,所以 Dll l. dll这一动态链接库没有导出这些函数。
图 19.16 Dll l. dll的导出信息 (四〕
由此,我们可以知道动态链接库导出整个类和仅导出该类的某些成员函数在实现方式上的区别:如果在声明类时,指定了导出标志,那么该类中的所有函数都将被导出:否则只有那些声明时指定了导出标志的类成员函数才被导出。但对这两种情况生成的 DLL.客户端程序在访问方式上是没有区别的,都是先构造该类的一个对象,然后利用该对象访问该类导出的成员函数。读者可以将最新的 Dll l. dll和 Dll l.lib复制到 DIITest工程所在目录下,然后在 VC++开发环境下运行 DllTest程序,单击 [Output】按钮,将看到程序的结果是一样的。另外,在导出类的成员函数时需要注意,该函数必须具有 public类型的访问权限,否则,该函数即使能够被导出,也不能被其他程序访问。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -