对话框
与普通窗口相比,处理消息的方式不一样。
- 普通窗口:自定义函数调用缺省函数
WndProc(...){ ... DefWindowProc(...); // 缺省函数 }
- 对话框窗口:缺省函数调用自定义函数
缺省函数(...){ ... 自定义函数(...); // 调用用户写的函数 ... }
1. 对话框原理
分类:
- 模式对话框:对话框显示后,禁止本进程其他窗口和用户交互
- 无模式对话框:对话框显示后,其他窗口仍然可以和用户交互
对话框基本使用:
- 对话框窗口处理函数
- 注册窗口类(不使用,系统已经注册好了,默认提供了一个处理消息的缺省函数)
// 用户自定义函数 (INT)(HWND,UINT,WPARAM,LPARAM) // 返回 TRUE:缺省函数无需处理 // 返回 FALSE:交给缺省函数处理 INT CALLBACK DialogProc( HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam );
- 创建对话框
- 对话框的关闭
2. 模式对话框
创建对话框
// 这是阻塞函数,只有当对话框关闭后才返回执行后续代码
// 返回值通过 EndDialog 设置
INT DialogBox(
hInstance, // 实例句柄
lpTemplate, // 对话框资源ID
hWndParent, // 对话框父窗口
lpDialogFunc // 自定义函数
);
关闭对话框
// 关闭时只能使用这个函数,不能用 DestoryWindow 等函数
// => DestoryWindow可以销毁对话框,但是无法解除 DialogBox 的阻塞状态 <=
BOOL
EndDialog(
_In_ HWND hDlg, // 要关闭的对话框
_In_ INT_PTR nResult // 关闭的返回值
);
对话框特殊消息:
WM_INITDIALOG
对话框创建之后,显示之前。通知对话框窗口处理函数完成自己的初始化相关操作
与WM_CREATE
类似,其他消息都与普通窗口相同
例子:
创建一个菜单资源 和 一个对话框资源:
// 当点击菜单栏中的 ID_MODEL 菜单项时创建对话框
case WM_COMMAND:
{
switch (LOWORD(wparam))
{
case ID_MODEL:
{
// DialogBox 是一个阻塞函数,返回值 nRet 就是 EndDlg 设置的返回值100
int nRet = DialogBox(g_hInstance, (CHAR*)IDD_DIALOG1, hwnd, DlgProc);
break;
}
default:
break;
}
break;
}
// 自定义消息处理函数
INT_PTR CALLBACK DlgProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch (msg)
{
case WM_SYSCOMMAND:
{
if (wparam == SC_CLOSE)
{
EndDialog(hwnd, 100);
}
break;
}
default:
break;
}
return FALSE; // 返回FALSE,让默认消息处理函数处理
}
3. 无模式对话框
非阻塞模式
// 创建成功返回窗口句柄,要使用 ShowWindow 函数显示对话框
// 关闭时需要使用 DestoryWindow 销毁,不能使用 EndDialog,后者用于销毁阻塞对话框
HWND
CreateDialogA(
hInstance, // 实例句柄
lpName, // 模板资源ID
hWndParent, // 父窗口
lpDialogFunc // 自定义函数
);
例子:
// 点击菜单项 ID_NOT_MODEL 创建非模式对话框
case WM_COMMAND:
{
switch (LOWORD(wparam))
{
case ID_NOT_MODEL:
{
HWND hwndDlg = CreateDialog(g_hInstance, (CHAR*)IDD_DIALOG1, hwnd, DlgProc);
ShowWindow(hwndDlg, SW_NORMAL);
break;
}
default:
break;
}
break;
}
// 自定义消息处理函数
INT_PTR CALLBACK DlgProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch (msg)
{
case WM_SYSCOMMAND:
{
if (wparam == SC_CLOSE)
{
DestroyWindow(hwnd);
}
break;
}
default:
break;
}
return FALSE; // 返回FALSE,让默认消息处理函数处理
}
静态库
使用C语言使用静态库可以不用函数声明;
但是使用CPP时需要使用函数声明,否则保存。
1. 静态库特点
无法运行;静态库源码被链接到调用程序中;目标成像的归档。
2. C语言静态库
创建:
- 创建静态库文件
- 添加库程序,源文件用C文件
int add(int x, int y) { return x + y; } int sub(int x, int y) { return x - y; }
向c文件添加函数
编译生成,生成的 .lib 文件在解决方案的 x64/DEBUG 目录下。
编译时出现问题:在查找预编译头时遇到意外的文件结尾。是否忘记了向源中添加“#include “pch.h””?
解决方法:取消预编译头
项目属性 —— 配置属性 —— c/c++ —— 预编译头
使用:
库路径设置:可使用 pragma 关键字设置
#pragma comment(lib, "../lib/clib.lib")
注意:这里的相对路径是相对于项目的路径
项目为 D:/code/WinApplication
,静态库在 D:/code/WinApplication/clib.lib
,那么应该输入 .
;
如果在 D:/code/WinSolution/clib.lib
,那么应该输入 ../WinSolution/clib.lib
- 创建控制台程序调用 lib 库
- 在文件头部指定静态库位置
#include <stdio.h>
// 指出静态库位置,这里放在了项目文件夹下。让链接器链接
// 如果放在系统路径下可无需这句话
#pragma comment(lib, "./CStaticLib.lib")
int main()
{
int x = 1;
int y = 1;
printf("add: %d/nsub: %d/n", add(x, y), sub(x, y));
return 0;
}
3. C++静态库
创建一个静态库项目,使用cpp文件。使用同样的语句设置路径。
创建静态库工程,添加代码:
int add(int x, int y){
return x + y;
}
int sub(int x, int y){
return x - y;
}
创建C++工程:
#include <iostream>
// 向链接器指出静态库位置
#pragma comment(lib, "../WinConsoleApplication01/x64/DEBUG/CppStaticLib.lib")
// 下面两个函数声明给编译器看
// 正确做法是使用头文件包含
int add(int x, int y);
int sub(int x, int y);
int main()
{
int x = 1;
int y = 1;
std::cout << "sum: " << add(x, y) << " sub: " << sub(x, y) << std::endl;
return 0;
}
4. 使用 c++ 调用c静态库
c++有换名机制,c语言没有。
使用C语言使用静态库可以不用函数声明;
但是使用CPP时需要使用函数声明,否则保存。
使用cpp调用c静态库会提示“找不到函数”因为c++由换名机制,将函数名换为一个其他的长字符串,在c静态库中没有,故cpp无法调用。cpp静态库中保存的函数名也是经过换名的。
在函数声明前加上
extern "C"
表示使用C语言方式编译,不适用换名机制。这样cpp就可以使用c静态库了Cpp需要函数声明就是为了知晓是否需要对函数进行换名。
#include <iostream>
// 向链接器指出静态库位置
//#pragma comment(lib, "../WinConsoleApplication01/x64/DEBUG/CppStaticLib.lib")
#pragma comment(lib, "../WinConsoleApplication01/x64/DEBUG/CStaticLib.lib")
// 下面两个函数声明给编译器看
// 正确做法是使用头文件包含
int add(int x, int y);
int sub(int x, int y);
int main()
{
int x = 1;
int y = 1;
std::cout << "sum: " << add(x, y) << " sub: " << sub(x, y) << std::endl;
return 0;
}
报错:
正确方法:
#include <iostream>
// 向链接器指出静态库位置
//#pragma comment(lib, "../WinConsoleApplication01/x64/DEBUG/CppStaticLib.lib")
#pragma comment(lib, "../WinConsoleApplication01/x64/DEBUG/CStaticLib.lib")
// 下面两个函数声明给编译器看
// 正确做法是使用头文件包含
extern "C" int add(int x, int y);
extern "C" int sub(int x, int y);
int main()
{
int x = 1;
int y = 1;
std::cout << "sum: " << add(x, y) << " sub: " << sub(x, y) << std::endl;
return 0;
}
动态库
1. 动态库特点
- 运行时独立存在
依附其他程序启动,启动后是一个独立的进程 - 源码不会链接到程序
- 使用时加载
相比静态库:
- 静态库是镶嵌入程序中,每个程序都会复制一份库。动态库只要存在一份,其他程序通过函数地址使用,程序体积小
- 静态库变化后新代码需要重新链接嵌入。动态库变化后,如果库函数定义和地址未变化,其他程序无需重新链接
2. 动态库创建
- 创建动态库项目
- 添加库程序
- 库程序导出:提供使用者信息
两种方法:- 声明导出:使用 _declspec(dllexport) 导出函数
注意:动态库编译链接后,也生成一个LIB文件,是作为动态库函数映射使用,与静态库不完全相同 - 模块定义文件 .def
例如:
LIBRARY DLLFunc 库
EXPORTS 库导出表
DLL_Mul @1 导出的函数
- 声明导出:使用 _declspec(dllexport) 导出函数
创建项目,输入代码:
_declspec(dllexport)
int add(int x, int y)
{
return x + y;
}
_declspec(dllexport)
int sub(int x, int y)
{
return x - y;
}
编译生成文件:
- dll 文件:分为文件体和文件头
文件体包含了函数的代码与具体实现
文件头包含了 函数编号、函数名、函数入口地址(相对于动态库入口地址的相对地址) - lib文件
包含了配套dll文件的文件名,库函数名和dll中函数标号
内部没有函数实现 - lib文件与dll文件中函数名、函数标号都是一致的
3. 动态库的使用
隐式链接
os负责使动态库执行
- 头文件和函数原型
可以在函数原型的声明前增加_declspec(dllimport)
- 导入动态库的LIB文件
- 在程序中使用函数
- 隐式链接情况下,dll可存放目录:
按照以下顺序查找,建议放在同一目录下- 执行文件同一目录下
- 当前工作目录
- Window 目录
- Window/System32 目录
- Window/System 目录
- 环境变量PATH指定目录
创建dll项目:
_declspec(dllexport)
int add(int x, int y)
{
return x + y;
}
_declspec(dllexport)
int sub(int x, int y)
{
return x - y;
}
编译生成文件:位于 解决方案/x64/debug/ 目录下
创建使用dll的项目:
#include <iostream>
// 通知链接器从哪里找到 函数名和函数编号和dll文件名
#pragma comment(lib, "../WinConsoleApplication01/x64/DEBUG/CPPDLL.lib")
// 下面两个函数声明给编译器看
// 正确做法是使用头文件包含
_declspec(dllimport) int add(int x, int y);
_declspec(dllimport) int sub(int x, int y);
int main()
{
int x = 1;
int y = 1;
std::cout << "sum: " << add(x, y) << " sub: " << sub(x, y) << std::endl;
return 0;
}
默认情况下生成的dll文件和exe文件都在同一解决方案的同一目录下。
显式链接
用户自己负责使动态库运行
- 定义函数指针类型 typedef
- 加载动态库
HMODULE // 返回DLL的实例句柄,就是动态库的 HINSTANCE LoadLibrary( _In_ LPCSTR lpLibFileName // 动态库文件名(与exe同一目录或在系统文件夹下)或全路径 );
- 获取函数(真实/绝对)地址
WINBASEAPI FARPROC // 成功则返回函数地址 WINAPI GetProcAddress( _In_ HMODULE hModule, // DLL 句柄 _In_ LPCSTR lpProcName // 函数名称 );
- 使用函数
- 卸载动态库
WINBASEAPI BOOL WINAPI FreeLibrary( _In_ HMODULE hLibModule // DLL 的实例句柄 );
创建DLL项目的步骤相同,调用dll的代码:
#include <iostream>
#include <Windows.h>
typedef int(*ADD)(int x, int y);
typedef int(*SUB)(int x, int y);
int main()
{
HINSTANCE hDll = LoadLibrary("CPPDLL.dll");
std::cout << "hDll: " << hDll << std::endl;
ADD add = (ADD)GetProcAddress(hDll, "?add@@YAHHH@Z"); // 注意此处要使用CPP中换名之后的函数名称
std::cout << "add address: " << add << std::endl;
SUB sub = (SUB)GetProcAddress(hDll, "sub"); // 使用原名称会报错,因为函数名被替换
std::cout << "sub address: " << sub << std::endl;
int x = 1;
int y = 1;
std::cout << "sum: " << add(x, y) << " sub: " << sub(x, y) << std::endl;
FreeLibrary(hDll);
return 0;
}
如果导出函数名没有被替换则更加方便,这时就要使用 模块定义文件 .def 在其中书写要导出的函数信息。这样导出的函数没有被换名
LIBRARY DLL // 关键字 + 无后缀的动态库文件名
EXPORTS // 关键字,下面是库导出表
FunName1 @1 // 要导出的函数
FunName2 @2
// ...
CPPDLL 项目中:
dllmain.cpp:
// 使用def文件导出
int add(int x, int y)
{
return x + y;
}
// 继续使用声明导出
_declspec(dllexport)
int sub(int x, int y)
{
return x - y;
}
CPPDLL.def:
调用dll的文件:
#include <iostream>
#include <Windows.h>
typedef int(*ADD)(int x, int y);
typedef int(*SUB)(int x, int y);
int main()
{
HINSTANCE hDll = LoadLibrary("CPPDLL.dll");
std::cout << "hDll: " << hDll << std::endl;
ADD add = (ADD)GetProcAddress(hDll, "add"); // 此函数使用模块定义导出,可直接使用原文件名
std::cout << "add address: " << add << std::endl;
SUB sub = (SUB)GetProcAddress(hDll, "?sub@@YAHHH@Z"); // 使用声明导出,需要用这种替换后的函数名
std::cout << "sub address: " << sub << std::endl;
int x = 1;
int y = 1;
std::cout << "sum: " << add(x, y) << " sub: " << sub(x, y) << std::endl;
FreeLibrary(hDll);
return 0;
}
4. 动态库中封装类
对于类一般直接使用声明导出,不用模块定义文件。模块定义文件用于导出函数地址。
在类名前增加 _declspec(dllexport) 定义,例如:
class _declspec(dllexport) CMath {
...
};
// 使用预编译开关切换导入导出的定义
#ifdef DLLCLASS_EXPORTS
#define EXT_CLASS _declspec(dllexport) // DLL 中
#else
#define EXT_CLASS _declspec(import) // 使用者中定义
#endif
class EXT_CLASS CMath{
...
};
创建一个DLL项目:
ClassDLL.h:
#pragma once
#ifndef _CLASSDLL_H
#define _CLASSDLL_H
#ifdef DLLCLASS_EXPORTS
#define EXT_CLASS _declspec(dllexport) // DLL 中
#else
#define EXT_CLASS _declspec(dllimport) // 使用者中定义
#endif
class
EXT_CLASS // 导出这两个成员函数的相对地址
CMath
{
public:
int Add(int, int);
int Sub(int, int);
private:
};
#endif // !_CLASSDLL_H
ClassDll.cpp:
#include "ClassDll.h"
#define DLLCLASS_EXPORTS
int CMath::Add(int x, int y)
{
return x + y;
}
int CMath::Sub(int x, int y)
{
return x - y;
}
生成dll:
调用dll的项目,这个项目需要导入dll对应的头文件和lib文件:
#include <iostream>
#include <Windows.h>
#include "../CPPDLL/ClassDll.h"
#pragma comment(lib, "../WinConsoleApplication01/x64/DEBUG/CPPDLL.lib")
int main()
{
CMath math;
std::cout << math.Add(1, 2) << "/n" << math.Sub(1, 2) << std::endl;
return 0;
}
结果:
原创文章,作者:kepupublish,如若转载,请注明出处:https://blog.ytso.com/278397.html