结对项目——图形界面实现与dll动态链接
先来一发软件截图~~~
- 生成题目的界面
- 测评界面
- 第三块本来准备做一个文件历史记录的界面,但是由于时间不够,暂时还没做完。
图形界面的设计与实现
由于对传统的对话框风格不太满意,所以这次作业的图形界面我使用了完全重绘界面的方法来开发。本次图形界面基于MFC的对话框界面开发,但是去掉了对话框的边框,使用设备环境句柄自绘界面,虽然配色不太好,但是我对整体的效果还是比较满意的。
MFC虽然默认的窗口不太好看,但是对各种消息的回调函数接口还是比较完善的,基于此,我重载了onPaint函数来自绘界面,重载OnLButtonDown函数设置鼠标点击事件,重载OnMouseMove设置鼠标悬停事件来实现鼠标悬停在按钮上时变色。
预计的三个模块(生成题目、计算答案、历史记录)分别为三个非模态子窗口,鼠标左键点击不同标题之后改变每个窗口的SW_SHOW
和SW_HIDE
属性。
界面模块和核心模块的松耦合
- 核心模块
-
核心模块是使用
c++ Console Application
编写,为了使它能够被MFC程序调用,我们就需要将工程转为dll Project
。 -
新建dll工程之后,我们先将原来的代码加入该工程,之后创建一个新类用于动态加载函数
#ifdef WIN32DLL_EXPORTS
#define MY_TEST_API __declspec(dllexport)
#else
#define MY_TEST_API __declspec(dllimport)
#endif
#include "ProblemSet.h"
class MY_TEST_API CCore
{
public:
CCore();
~CCore();
};
extern "C"
{
MY_TEST_API void gen(int a, int b, int c, int d, int e, int f, int g, char *s);
MY_TEST_API void calc(char *s, char *ret);
}
这是统一的函数声明方式,之前考虑直接在类中创建动态加载的函数,但是由于某种未知原因,类中的函数总是不能加载,所以就采取了extern "C"
的方式。
在对应的.cpp文件中实现这两个函数即完成了dll工程的编写。编译之后的.dll文件即可被其他程序调用。
- 界面模块
- typedef需要用到的函数指针
typedef void(*FUNC_GEN)(int a, int b,int c,int d, int e, int f,int g,char *ch);
typedef void(*FUNC_CALC)(char* a, char *b);
- 在需要使用DLL函数的时候动态加载DLL文件
HMODULE hDLL = ::LoadLibrary(L"ArchCore.dll");
if (!hDLL)
{
MessageBox(L"未找到ArchCore.dll文件!");
return;
}
- 使用GetProcAddress函数获取想使用的函数的函数指针
FUNC_GEN gen = (FUNC_GEN)::GetProcAddress(hDLL, "gen");
if (gen != NULL)
{
...
}
FUNC_CALC calc = (FUNC_CALC)::GetProcAddress(hDLL, "calc");
if (calc != NULL)
{
...
}
与另一个团队进行互测
在结对项目开始之初,我就与另一个团队(pair16,学号后四位为1179)订好了公共的接口方案,在我们各自完成了自己的部分之后,我们交换了core部分的dll文件进行互测。
由于我们采用的是在运行时动态加载dll的方法,因此交换dll并不需要对代码进行修改,仅仅需要把HMODULE hDLL = ::LoadLibrary(L"ArchCore.dll");
之中的dll文件名修改为另一个同学的文件名就好。
经过测试,我们的dll在互相换过后也完全能够运行,没有任何问题。只不过由于我的core文件采用了输入输出流的方式处理输入输出,因此运行起来会有一些慢。
一些感想
我万万没想到的是采用重载输入输出流的方式输出表达式居然会对程序效率产生这么大的影响!!原本以为只有在ACM竞赛这样对时间要求很严格的情况下流输入才会有影响,没想到在这次项目中也遇到了这个问题(对同样的题目进行计算标准答案并输出,使用我的Core需要大概20s才能出解, 而使用另一个团队的core则仅需5s即可出解,我们的算法设计没有什么区别,唯一的区别就是我采用了流输入输出,而他则是直接用一个toString函数处理的)。
这次的图形界面设计和core设计几乎是完全独立的,仅需要通过dll进行连接,这在之前是从没有接触过的,感觉非常神奇,不过我对dll的使用仍然存在许多不理解的地方,比如为什么只有通过extern "C"
的方式生命的函数能够正确调用,而在CCore类中声明的函数则无法正确调用(在网上查资料说c++生成dll之后的函数名会发生变化,但是我也无法知道到底是变成了什么)。这些都是以后需要学习的地方。
BTW,我还是计划加入”历史记录“这个功能,另外,文件的选取希望不是通过手动输入路径的方式进行,而是调用windows资源管理器进行选取。