win32 - 对话框、静态库、动态库

对话框

与普通窗口相比,处理消息的方式不一样。

  • 普通窗口:自定义函数调用缺省函数
    WndProc(...){
        ...
        DefWindowProc(...); // 缺省函数
    }
  • 对话框窗口:缺省函数调用自定义函数
    缺省函数(...){
        ...
        自定义函数(...); // 调用用户写的函数
        ...
    }

1. 对话框原理

分类:

  1. 模式对话框:对话框显示后,禁止本进程其他窗口和用户交互
  2. 无模式对话框:对话框显示后,其他窗口仍然可以和用户交互

对话框基本使用:

  1. 对话框窗口处理函数
  2. 注册窗口类(不使用,系统已经注册好了,默认提供了一个处理消息的缺省函数)
    // 用户自定义函数 (INT)(HWND,UINT,WPARAM,LPARAM)
    // 返回 TRUE:缺省函数无需处理
    // 返回 FALSE:交给缺省函数处理
    INT CALLBACK DialogProc(
        HWND hWndDlg,
        UINT uMsg,
        WPARAM wParam,
        LPARAM lParam
    );
  3. 创建对话框
  4. 对话框的关闭

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); // EndDialog(hwnd, 0) 用于销毁模式对话框,只能隐藏无模式对话框,不能销毁
		}
		break;
	}
	default:
		break;
	}
	return FALSE; // 返回FALSE,让默认消息处理函数处理
}

 

4. 间接创建无模式对话框

在MFC中只有无模式对话框,使用 EnableWindowGetParent模拟模式对话框

这种方式似乎和直接创建没有区别,但是MFC中使用了这种方式。

  1. 添加对话框资源
  2. 查找资源 FindResource
  3. 加载资源 LoadResource
  4. 锁定资源 LockResource
  5. 创建无模式对话框 CreateDialogIndirect

 

#include <Windows.h>
#include "resource.h"

#include <stdio.h>
#include <string.h>

// 用于在 win32 中获取控制台
HANDLE g_hOutput = NULL;

// 全局保存实例对象
HINSTANCE g_hInstance = NULL;

INT_PTR CALLBACK DlgProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    switch (msg)
    {
    case WM_SYSCOMMAND:
    {
        if (wparam == SC_CLOSE)
        {
            EnableWindow(GetParent(hwnd), TRUE); // 获得对话框窗口的父窗口,并设置可用
            DestroyWindow(hwnd);
        }
        break;
    }
    default:
        break;
    }

    return FALSE;
}

void OnNoModel(HWND hWnd)
{
    EnableWindow(hWnd, FALSE); // 设置窗口是否可用,这里用于模拟模式对话框

    // 直接创建
    //HWND hDlg = CreateDialog(g_hInstance, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, DlgProc);

    // 间接创建
    HRSRC hRs = FindResource(g_hInstance, MAKEINTRESOURCE(IDD_DIALOG1), RT_DIALOG); // 将资源加载到内存
    HGLOBAL hGl = LoadResource(g_hInstance, hRs);                                   // 找到对话框的有用数据
    LPCDLGTEMPLATE pTemplate =  (LPCDLGTEMPLATE)LockResource(hGl);                  // 将内存中数据放入结构体
    HWND hDlg = CreateDialogIndirect(g_hInstance, pTemplate, hWnd, DlgProc);

    ShowWindow(hDlg, SW_SHOW);
}

void OnCommand(HWND hWnd, WPARAM wparam, LPARAM lparam)
{
    switch (LOWORD(wparam))
    {
    case ID_NOMODEL:
    {
        OnNoModel(hWnd);
        break;
    }
    default:
        break;
    }
}

LRESULT CALLBACK WnProc(
    HWND hwnd,
    UINT msg,
    WPARAM wparam,
    LPARAM lparam)
{
    switch (msg)
    {
    case WM_COMMAND:
    {
        OnCommand(hwnd, wparam, lparam);
        break;
    }
    case WM_CLOSE:
    {
        DestroyWindow(hwnd);
        PostQuitMessage(0);
    }
    default:
        break;
    }

    return DefWindowProc(hwnd, msg, wparam, lparam);
}

int WINAPI WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    PSTR szCmdLine,
    int nCmdShow)
{
    g_hInstance = hInstance;

    // 在 win32 中获取控制台
    AllocConsole();
    g_hOutput = GetStdHandle(STD_OUTPUT_HANDLE);


    WNDCLASS wndclass;
    ZeroMemory(&wndclass, sizeof(WNDCLASS));

    wndclass.lpfnWndProc = WnProc;
    wndclass.lpszClassName = "MyWndClass";

    if (!RegisterClass(&wndclass))
    {
        MessageBox(NULL, TEXT("regist windwos class failed!"), "failed", MB_ICONERROR);
        return 0;
    }

    HWND hwnd = CreateWindow(
        wndclass.lpszClassName,
        "window name",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL,
        LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1)),
        hInstance,
        NULL
    );

    ShowWindow(hwnd, SW_NORMAL);
    UpdateWindow(hwnd);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

 


 

 静态库

使用C语言使用静态库可以不用函数声明;

但是使用CPP时需要使用函数声明,否则保存。

1. 静态库特点

无法运行;静态库源码被链接到调用程序中;目标成像的归档。

2. C语言静态库

创建:

  1. 创建静态库文件
  2. 添加库程序,源文件用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

  1. 创建控制台程序调用 lib 库
  2. 在文件头部指定静态库位置
#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. 动态库特点

  1. 运行时独立存在
    依附其他程序启动,启动后是一个独立的进程
  2. 源码不会链接到程序
  3. 使用时加载

相比静态库:

  1. 静态库是镶嵌入程序中,每个程序都会复制一份库。动态库只要存在一份,其他程序通过函数地址使用,程序体积小
  2. 静态库变化后新代码需要重新链接嵌入。动态库变化后,如果库函数定义和地址未变化,其他程序无需重新链接

2. 动态库创建

  1. 创建动态库项目
  2. 添加库程序
  3. 库程序导出:提供使用者信息
    两种方法:
    1. 声明导出:使用 _declspec(dllexport) 导出函数
      注意:动态库编译链接后,也生成一个LIB文件,是作为动态库函数映射使用,与静态库不完全相同
    2. 模块定义文件 .def
      例如:
      LIBRARY DLLFunc   库
      EXPORTS                库导出表
      DLL_Mul      @1     导出的函数

创建项目,输入代码:

_declspec(dllexport)
int add(int x, int y)
{
    return x + y;
}

_declspec(dllexport)
int sub(int x, int y)
{
    return x - y;
}

编译生成文件:

  1. dll 文件:分为文件体和文件头
    文件体包含了函数的代码与具体实现
    文件头包含了 函数编号、函数名、函数入口地址(相对于动态库入口地址的相对地址)
  2. lib文件
    包含了配套dll文件的文件名,库函数名和dll中函数标号
    内部没有函数实现
  3. lib文件与dll文件中函数名、函数标号都是一致的

 

3. 动态库的使用

隐式链接

os负责使动态库执行

 

  1. 头文件和函数原型
    可以在函数原型的声明前增加 _declspec(dllimport) 
  2. 导入动态库的LIB文件
  3. 在程序中使用函数
  4. 隐式链接情况下,dll可存放目录:
    按照以下顺序查找,建议放在同一目录下
    1. 执行文件同一目录下
    2. 当前工作目录
    3. Window 目录
    4. Window/System32 目录
    5. Window/System 目录
    6. 环境变量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文件都在同一解决方案的同一目录下。

 

 

显式链接

用户自己负责使动态库运行

  1. 定义函数指针类型 typedef
  2. 加载动态库
    HMODULE      // 返回DLL的实例句柄,就是动态库的 HINSTANCE
    LoadLibrary(
        _In_ LPCSTR lpLibFileName // 动态库文件名(与exe同一目录或在系统文件夹下)或全路径
        );
  3. 获取函数(真实/绝对)地址
    WINBASEAPI
    FARPROC   // 成功则返回函数地址
    WINAPI
    GetProcAddress(
        _In_ HMODULE hModule,   // DLL 句柄
        _In_ LPCSTR lpProcName  // 函数名称
        );
  4. 使用函数
  5. 卸载动态库
    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;
}

结果:

 

5. DllMain 函数

用于在被进程线程给加载卸载时申请或释放资源

BOOL APIENTRY DllMain( HANDLE hModule,           // 当前模块句柄
                      DWORD ul_reason_for_call,  // 调用原因
                      LPVOID lpReserved)
{
 switch (ul_reason_for_call)
 {
  case DLL_PROCESS_ATTACH:
   printf("\nprocess attach of dll");
   break;
  case DLL_THREAD_ATTACH:
   printf("\nthread attach of dll");
   break;
  case DLL_THREAD_DETACH:
   printf("\nthread detach of dll");
   break;
  case DLL_PROCESS_DETACH:
   printf("\nprocess detach of dll");
   break;
 }
 return TRUE;
}

 

查看DLL导出函数工具

dumbin -export  vs自带,进入 vs developer command 即可使用。

 

 

posted @ 2022-08-02 08:50  某某人8265  阅读(284)  评论(0编辑  收藏  举报