Windows
相关
创建Mutex
的有无Global
的影响
- 概述
Windows
内核对象(如Mutex
、Event
、Semaphore
)的命名规则决定了它们的可见范围:- 适用场景:跨会话(如服务进程与用户进程)、跨用户或系统级同步
- 加了
Global\
- 表示该对象位于 全局内核命名空间,可被 所有用户会话(
Sessions
) 和 进程 访问
- 表示该对象位于 全局内核命名空间,可被 所有用户会话(
- 加了
Local\
(或不加前缀)- 表示该对象位于 当前会话的本地命名空间,仅对 同一会话内的进程 可见
- 默认行为:若不显式指定前缀,系统默认使用
Local\
- 适用场景:同一用户会话内的进程间同步
关于会话
Windows
的 会话(Session
) 是操作系统隔离用户环境的逻辑单元,主要服务于 终端服务(Terminal Services
) 或多用户场景:- 每个登录用户 通常会分配一个 独立会话
Session 0
是特殊的系统会话
主线程里启动一个线程,PostThreadMessage
发消息能不能收到
- 主线程启动的新线程能否收到
PostThreadMessage
的消息,取决于目标线程是否已创建消息队列 - 消息队列的创建条件:当线程首次调用以下任一操作时,系统会为其分配消息队列:
- 创建窗口(
CreateWindow
/CreateWindowEx
) - 调用消息处理函数(
GetMessage
/PeekMessage
) - 其他隐式依赖消息队列的
UI
操作(如注册窗口类、处理对话框等)
- 创建窗口(
- 若消息队列未创建:
PostThreadMessage
发送的消息会丢失,且函数返回FALSE
,可通过GetLastError()
检查错误码(如ERROR_INVALID_THREAD_ID
)
创建一个窗口,怎么让它一直在父窗口上面
- 方法1
- 使用
WS_CHILD
样式(推荐的方法),具体如下: - 创建父窗口:使用
WS_OVERLAPPEDWINDOW
样式 - 创建子窗口:使用
WS_CHILD
样式,并指定父窗口句柄 - 系统会自动维护其
Z
序在父窗口之上,子窗口会随父窗口移动、最小化/恢复
- 使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
#include <windows.h> // 父窗口句柄 HWND g_hParentWnd = NULL; // 子窗口过程 LRESULT CALLBACK ChildWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CLOSE: DestroyWindow(hWnd); return 0; default: return DefWindowProc(hWnd, msg, wParam, lParam); } } // 父窗口过程 LRESULT CALLBACK ParentWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CREATE: // 创建子窗口 CreateWindow( "STATIC", // 简单控件类(如静态文本) "Child Window", WS_CHILD | WS_VISIBLE | WS_BORDER, // 子窗口样式 20, 20, 200, 100, hWnd, // 父窗口句柄 NULL, ((LPCREATESTRUCT)lParam)->hInstance, NULL ); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; default: return DefWindowProc(hWnd, msg, wParam, lParam); } } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { // 注册父窗口类 WNDCLASSEX wc = {0}; wc.cbSize = sizeof(WNDCLASSEX); wc.lpfnWndProc = ParentWndProc; wc.hInstance = hInstance; wc.lpszClassName = "ParentWindowClass"; RegisterClassEx(&wc); // 创建父窗口 g_hParentWnd = CreateWindow( "ParentWindowClass", "Parent Window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, NULL, NULL, hInstance, NULL ); ShowWindow(g_hParentWnd, nCmdShow); UpdateWindow(g_hParentWnd); // 消息循环 MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; } |
- 方法二
- 使用
HWND_TOPMOST
和父窗口关联,具体如下: - 创建父窗口:普通窗口样式(如
WS_OVERLAPPEDWINDOW
) - 创建子窗口:使用
WS_EX_TOPMOST
扩展样式,并通过SetWindowPos
绑定到父窗口 - 监听父窗口位置变化:在父窗口的
WM_WINDOWPOSCHANGED
消息中更新子窗口位置
- 使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
// 全局变量 HWND g_hParentWnd = NULL; HWND g_hChildWnd = NULL; // 子窗口过程 LRESULT CALLBACK ChildWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CLOSE: DestroyWindow(hWnd); return 0; default: return DefWindowProc(hWnd, msg, wParam, lParam); } } // 父窗口过程 LRESULT CALLBACK ParentWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CREATE: { // 创建子窗口(独立窗口,但始终位于父窗口上方) g_hChildWnd = CreateWindowEx( WS_EX_TOPMOST, // 扩展样式:置顶 "STATIC", "Child Window", WS_VISIBLE | WS_BORDER | WS_CAPTION | WS_SYSMENU, 20, 20, 200, 100, hWnd, // 关联父窗口(非子窗口) NULL, ((LPCREATESTRUCT)lParam)->hInstance, NULL ); // 强制子窗口跟随父窗口 SetWindowPos( g_hChildWnd, HWND_TOPMOST, // 保持置顶 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE ); return 0; } case WM_WINDOWPOSCHANGED: { // 父窗口移动时,更新子窗口位置 WINDOWPOS* pos = (WINDOWPOS*)lParam; if (!(pos->flags & SWP_NOMOVE)) { RECT rcParent; GetWindowRect(hWnd, &rcParent); SetWindowPos( g_hChildWnd, HWND_TOPMOST, rcParent.left + 20, // 相对于父窗口的位置 rcParent.top + 20, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE ); } return 0; } case WM_DESTROY: PostQuitMessage(0); return 0; default: return DefWindowProc(hWnd, msg, wParam, lParam); } } // WinMain 函数同上(略) |
- 创建一个模态的子窗口
- 但是这种方法会阻塞主线程的消息循环,具体如下:
- 使用普通窗口类,并在点击按钮或菜单时触发模态窗口的创建
- 通过
DialogBox
函数加载对话框资源,并绑定对话框过程函数 - 在资源文件(
.rc
)中定义对话框模板,或在代码中动态创建
1 2 3 4 5 6 7 8 9 |
IDD_MODAL_DIALOG DIALOGEX 0, 0, 200, 100 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Modal Dialog" FONT 9, "Arial" { DEFPUSHBUTTON "OK", IDOK, 50, 70, 50, 20 PUSHBUTTON "Cancel", IDCANCEL, 120, 70, 50, 20 LTEXT "This is a modal dialog.", -1, 20, 20, 160, 20 } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#include <windows.h> // 对话框资源 ID(需在资源文件中定义或动态创建) #define IDD_MODAL_DIALOG 101 // 父窗口过程 LRESULT CALLBACK ParentWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CREATE: // 创建按钮用于触发模态窗口 CreateWindow("BUTTON", "Show Modal", WS_VISIBLE | WS_CHILD, 20, 20, 100, 30, hWnd, (HMENU)1, NULL, NULL); return 0; case WM_COMMAND: if (LOWORD(wParam) == 1) { // 显示模态对话框 DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_MODAL_DIALOG), hWnd, DialogProc); } return 0; case WM_DESTROY: PostQuitMessage(0); return 0; default: return DefWindowProc(hWnd, msg, wParam, lParam); } } // WinMain 函数(略) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
INT_PTR CALLBACK DialogProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: // 初始化对话框控件 return TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { // 关闭对话框 EndDialog(hDlg, LOWORD(wParam)); return TRUE; } break; } return FALSE; } |
栈的增长方向
- 由高地址向低地址增长
MDd
和MTd
- 概述
d debug
M
多线程T
Text
代码D
动态
/MT
/MTd
指静态编译(多线程静态版本),使用lib
以及MSVC
相关的静态库- 定义了它后,编译器把
LIBCMT.lib
安置到OBJ
文件中,让链接器使用LIBCMT.lib
处理外部符号
- 定义了它后,编译器把
/MD
/MDd
指动态编译(多线程DLL
版本),使用相应的DLL
版本编译- 定义了它后,编译器把
MSVCRT.lib
安置到OBJ
文件中,它连接到DLL
的方式是静态链接,实际上工作的库是MSVCR80.DLL
- 定义了它后,编译器把
Windbg
相关
查看内存的命令相关
d*
db
:以 字节(Byte
)形式显示内存,同时显示ASCII
字符
1 |
db <address> [length] // 示例:db 0012ff40 L20 (显示32字节) |
dw
:以 字(Word
,2
字节) 形式显示内存
1 |
dw 0012ff40 // 显示16进制字 |
dd
:以双字(DWORD
,4
字节)形式显示内存
1 |
dd 0012ff40 L10 // 显示16个DWORD |
dq
:以四字(QWORD
,8
字节)形式显示内存
1 |
dq @rsp // 显示栈顶内容 |
dc
:以DWORD + ASCII
混合形式显示
1 |
dc 0012ff40 // 适合查看字符串和结构 |
da
- 显示
ANSI
字符串
1 |
da 0012ff40 // 显示直到'\0'的ANSI字符串 |
du
- 显示
Unicode
字符串
1 |
du 0012ff40 // 显示Unicode字符串(宽字符) |
dds
或dps
- 将内存内容视为 符号地址(需符号加载)
1 |
dds @esp L4 // 显示栈中前4个地址对应的符号 |
1 |
dps @rsp L8 // 显示栈中指针链(调试调用栈常用) |
!address
- 分析指定地址的 内存区域属性(如堆、栈、保留内存等):
BaseAddress
:内存块起始地址RegionSize
:内存块大小Type
:类型(MEM_PRIVATE
,MEM_IMAGE
等)Protect
:保护属性(PAGE_READWRITE
等)
1 |
!address 0012ff40 // 显示内存类型、大小、权限等 |
!vprot
(查看内存保护状态)
- 快速查看内存页的 保护属性:
1 |
!vprot 0012ff40 // 显示是否可读/写/执行 |
s
(搜索内存)
- 在内存中搜索特定字节序列或字符串:
1 2 3 |
s -d 00100000 L20000 0xdeadbeef // 在00100000~00120000搜索DWORD 0xdeadbeef s -a 00100000 L20000 "Hello" // 搜索ANSI字符串"Hello" s -u 00100000 L20000 "W"o"r"l"d" // 搜索Unicode字符串"World" |
ed
或ew
或eb
(编辑内存)
- 直接修改内存内容(慎用!):
1 2 |
eb 0012ff40 41 42 43 // 将地址0012ff40开始的3字节改为0x41,0x42,0x43 ed 0012ff40 12345678 // 修改DWORD值为0x12345678 |
dt
- 根据符号信息显示 数据结构:
1 2 3 |
dt ntdll!_PEB @$peb // 显示当前进程的PEB结构 dt MyStruct 0012ff40 // 显示0012ff40处的MyStruct实例 dt -r2 MyStruct 0012ff40 // 递归显示2层嵌套结构 |
.formats
- 将内存值转换为多种格式(十进制、十六进制、
ASCII
等):
1 2 |
.formats 0012ff40 // 显示地址0012ff40的多种表示形式 .formats poi(0012ff40) // 显示0012ff40指向的内容 |
!heap
- 查看堆内存分配情况:
1 2 3 |
!heap -h 0 // 显示进程所有堆句柄 !heap -x 0012ff40 // 检查地址0012ff40是否在堆中,及其分配信息 !heap -p -a 0012ff40 // 显示堆块详细信息(包含调用栈) |
查看栈内存
1 2 |
dd @esp L20 // 显示栈顶前0x20字节(32位) dq @rsp L20 // 显示栈顶前0x20四字(64位) |
查看线程环境块(TEB
)
1 |
dt _TEB @$teb // 显示当前线程的TEB |
查看进程环境块(PEB
)
1 |
dt _PEB @$peb // 显示当前进程的PEB |
内存断点
1 2 |
ba r4 0012ff40 // 当地址0012ff40被读取时中断 ba w4 0012ff40 // 当地址0012ff40被写入时中断 |
内存差异比较
1 |
.compare <address1> <address2> <length> // 比较两个内存块内容 |
怎么查看结构体
- 查看结构体定义
1 |
dt <结构体名> // 显示结构体成员布局 |
1 2 |
dt ntdll!_PEB // 查看PEB结构定义 dt MyApp!MyStruct // 查看自定义结构体MyStruct的定义 |
- 显示内存中的结构体实例
1 |
dt <结构体名> <地址> // 将指定地址按结构体解析 |
1 2 |
dt _PEB @$peb // 解析当前进程的PEB(@$peb是伪寄存器) dt MyStruct 0x0012ff40 // 将地址0x0012ff40按MyStruct结构解析 |
- 递归显示嵌套结构体
- 使用
-r
参数指定递归深度:
- 使用
1 2 |
dt -r <结构体名> <地址> // 递归显示所有层级 dt -r2 _PEB @$peb // 递归显示PEB及其内部结构的前两层 |
- 显示结构体大小
1 |
?? sizeof(ntdll!_PEB) // 显示PEB结构的大小(字节) |
- 模糊查找符号
1 |
dt *!*PEB* // 查找符号名中包含"PEB"的结构体 |
显示数组或指针内容
- 对数组或指针成员展开显示:
1 2 |
dt MyStruct.array 0x0012ff40 L5 // 显示数组前5个元素 dt MyStruct.ptr 0x0012ff40 // 显示指针指向的内容 |
COM
相关
CoInitialize
和CoInitializeEX
区别
CoInitialize
- 其默认将当前线程设置为单线程单元(
STA
,Single-Threaded Apartment
) - 这意味着:
COM
对象只能通过该线程的窗口消息队列(如GetMessage
/DispatchMessage
)进行并发控制,避免多线程竞争- 通常用于需要与
UI
交互的线程(例如主线程),例如操作剪贴板、拖放功能等
- 其默认将当前线程设置为单线程单元(
CoInitializeEx
COINIT_APARTMENTTHREADED
:与CoInitialize
等效COINIT_MULTITHREADED
:线程加入多线程套间,所有MTA
线程共享一个上下文,不需要消息队列COINIT_DISABLE_OLE1DDE
:禁用对旧版OLE 1.0
的支持(可选)
- 场景:
CoInitialize
:在遗留代码中常见,默认要求线程有消息泵(消息循环)
示例:传统的ActiveX
控件或COM
对象需要在主线程中使用CoInitializeEx
:
STA
模式,兼容CoInitialize
行为,但明确指定线程模型
MTA
模式,适用于后台线程处理不需要UI
的COM
对象(如并行计算)
示例:工作线程中调用无需GUI
交互的COM
组件
- 推荐:
- 优先使用
CoInitializeEx
CoInitialize
已被微软标记为过时(obsolescent
) - 需显式指定线程模型,避免默认行为的潜在歧义
- 优先使用
- 注意:
- 每个线程只能初始化一次
COM
重复调用会失败(返回RPC_E_CHANGED_MODE
),除非上一次调用已被CoUninitialize
- 清理资源
调用CoUninitialize
配对释放资源,通常置于__finally
块或析构函数中 - 线程模型选择的影响
STA
:线程需要处理消息队列(常见于需要COM
对象与用户交互)
MTA
:无需消息泵,但必须手动处理线程安全和同步
- 每个线程只能初始化一次
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 使用 CoInitializeEx 初始化 STA(等效于 CoInitialize) HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); if (SUCCEEDED(hr)) { // 使用 COM 对象 CoUninitialize(); } // 初始化 MTA(无消息循环需求的线程) HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if (SUCCEEDED(hr)) { // 多线程 COM 操作 CoUninitialize(); } |
CoCreateInstance
里面是怎么实现的
- 概述
- 是
COM
(Component Object Model
)中用于创建对象实例的核心API
- 其内部实现涉及多个关键步骤,包括注册表查询、组件加载、类工厂获取及实例创建等
- 是
- 步骤如下:
- 参数验证
- 验证传入的
rclsid
(CLSID
)、pUnkOuter
(聚合指针)、dwClsContext
(执行上下文)和riid
(接口IID
)是否有效 - 若参数非法(如
riid
为空),立即返回E_INVALIDARG
- 验证传入的
- 解析
CLSID
并查找注册信息- 注册表查询:根据
CLSID
在注册表中查找对应的组件实现路径。关键注册表路径为: - 上下文匹配:根据
dwClsContext
(如CLSCTX_INPROC_SERVER
、CLSCTX_LOCAL_SERVER
)筛选可用组件
- 注册表查询:根据
1 2 3 |
HKEY_CLASSES_ROOT\CLSID\{CLSID}\InProcServer32 // 进程内 DLL HKEY_CLASSES_ROOT\CLSID\{CLSID}\LocalServer32 // 本地 EXE HKEY_CLASSES_ROOT\CLSID\{CLSID}\InprocHandler32 // 自定义处理程序 |
- 加载组件
- 进程内组件(
DLL
)
加载DLL
:通过LoadLibrary
加载InProcServer32
指定的DLL
获取类工厂:调用DLL
的导出函数DllGetClassObject
,传入CLSID
和IID_IClassFactory
,获取类工厂接口指针 - 本地
EXE
组件
启动进程:通过CreateProcess
启动LocalServer32
指定的EXE
跨进程通信:EXE
启动后向COM
注册类工厂,CoCreateInstance
通过COM
的SCM
(Service Control Manager
)跨进程获取类工厂
- 进程内组件(
- 获取类工厂并创建实例
- 调用类工厂:通过获得的
IClassFactory
指针,调用其CreateInstance
方法 - 聚合处理:若
pUnkOuter
非空(聚合请求),确保被创建对象支持聚合
- 调用类工厂:通过获得的
1 2 |
IClassFactory* pFactory = ...; pFactory->CreateInstance(pUnkOuter, riid, ppv); |
- 处理线程模型与封送(
Marshaling
)- 线程模型检查:比较调用线程的公寓类型(
STA/MTA
)与组件注册的线程模型(如ThreadingModel
值):
兼容时:直接返回对象指针
不兼容时:创建接口代理(Proxy
),将调用封送至目标线程或进程 - 封送初始化:若对象跨线程/进程,调用
CoMarshalInterface
生成代理存根(Proxy-Stub
)
- 线程模型检查:比较调用线程的公寓类型(
- 错误处理与资源释放
- 错误码返回:若任一步骤失败(如注册表未找到
CLSID
),返回对应错误码(如REGDB_E_CLASSNOTREG
) - 释放资源:若中途失败,确保释放已加载的
DLL
或类工厂指针,避免内存泄漏
- 错误码返回:若任一步骤失败(如注册表未找到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
HRESULT CoCreateInstance(REFCLSID rclsid, IUnknown* pUnkOuter, DWORD dwClsContext, REFIID riid, void** ppv) { // 1. 参数校验 if (!ppv || !riid) return E_INVALIDARG; *ppv = nullptr; // 2. 获取类工厂 IClassFactory* pFactory = nullptr; HRESULT hr = CoGetClassObject(rclsid, dwClsContext, nullptr, IID_IClassFactory, (void**)&pFactory); if (FAILED(hr)) return hr; // 3. 创建实例 hr = pFactory->CreateInstance(pUnkOuter, riid, ppv); pFactory->Release(); // 4. 处理封送 if (SUCCEEDED(hr)) { hr = CoMarshalInterThreadInterfaceInStream(riid, *ppv, &pStream); // 跨线程时封送 // ... 其他封送逻辑 } return hr; } |
com
的idl
文件会生成哪些默认的接口
IUnknown
接口方法- 所有
COM
接口必须继承自IUnknown
,因此IDL
生成的接口会默认包含其三个核心方法: QueryInterface
:查询对象是否支持指定接口,并返回接口指针AddRef
:增加对象的引用计数Release
:减少对象的引用计数,计数归零时销毁对象
- 所有
- 用户自定义接口
IDL
文件中显式定义的接口会被编译为C++
抽象类,包含用户定义的方法。例如:
1 2 3 4 |
// IDL 定义 interface IMyInterface : IUnknown { HRESULT MyMethod([in] int param); }; |
1 2 3 4 |
// 生成的 C++ 头文件 struct IMyInterface : public IUnknown { virtual HRESULT STDMETHODCALLTYPE MyMethod(int param) = 0; }; |
- 类型库相关接口(可选)
- 如果
IDL
文件包含library
块生成类型库(.tlb
),可能涉及以下接口: IDispatch
:支持后期绑定(如VBScript
、VBA
)
GetTypeInfoCount
GetTypeInfo
GetIDsOfNames
Invoke
IClassFactory
:创建组件实例
CreateInstance
LockServer
- 如果
- 代理存根代码(
Proxy-Stub
)- 为支持跨进程/线程调用,
MIDL
编译器会生成 代理存根 相关代码,包含以下关键接口: IRpcProxyBuffer
:管理代理对象的RPC
通信
Connect
Disconnect
IRpcStubBuffer
:管理存根对象的 RPC 通信
Invoke
IsIIDSupported
- 为支持跨进程/线程调用,
- 全局唯一标识符(
GUID
)IDL
中定义的每个接口和类会生成对应的GUID
(全局唯一标识符),存储在头文件中:
1 2 3 4 5 |
// 示例:接口 IMyInterface 的 GUID EXTERN_C const IID IID_IMyInterface; #ifdef __cplusplus } // extern "C" #endif |
编程规范
多线程相关
定义一个全局的bool,主线程设置true,其他线程发现true则退出,可行吗
- 使用
std::atomic
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include <atomic> #include <thread> std::atomic<bool> g_exit_flag(false); // 必须用 atomic<bool> void worker_thread() { while (!g_exit_flag.load(std::memory_order_acquire)) { // 检查退出标志 // 工作代码... } } int main() { std::thread t1(worker_thread); std::thread t2(worker_thread); // 主线程设置退出标志 g_exit_flag.store(true, std::memory_order_release); t1.join(); t2.join(); return 0; } |
- 使用
condition_variable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <mutex> #include <condition_variable> std::mutex g_mtx; std::condition_variable g_cv; bool g_exit_flag = false; void worker_thread() { std::unique_lock<std::mutex> lock(g_mtx); while (!g_exit_flag) { g_cv.wait(lock); // 休眠直到通知或虚假唤醒 } } int main() { std::thread t(worker_thread); { std::lock_guard<std::mutex> lock(g_mtx); g_exit_flag = true; } g_cv.notify_all(); // 唤醒所有线程 t.join(); return 0; } |
C++11
后面的特性
智能指针
auto_ptr
被淘汰的原因- 已弃用,因为赋值操作会导致原来的智能指针失去对内存的所有权
1 2 3 |
auto_ptr<int> p1(new int(10)); auto_ptr<int> p2 = p1; // p1 变为空指针! // 此时访问 *p1 会导致未定义行为 |
shared_ptr
</li> <li>
weak_ptr
weak_ptr
的场景- 打破循环引用
- 缓存系统
缓存需要临时保存对象的弱引用,当外部不再使用对象时,允许其自动释放;若缓存需要时对象仍存在,可重新获取 - 观察者模式
- 临时访问共享资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Resource { /* ... */ }; class Cache { private: std::unordered_map<int, std::weak_ptr<Resource>> cache; public: std::shared_ptr<Resource> getResource(int id) { auto it = cache.find(id); if (it != cache.end()) { auto sptr = it->second.lock(); // 尝试提升为 shared_ptr if (sptr) return sptr; // 资源仍存在,直接返回 } // 资源不存在,重新创建并更新缓存 auto sptr = std::make_shared<Resource>(); cache[id] = sptr; return sptr; } }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void processResource(std::weak_ptr<Resource> weak_res) { if (auto res = weak_res.lock()) { // 检查资源是否存活 res->use(); // 安全使用资源 } else { std::cout << "Resource no longer exists.\n"; } } int main() { std::shared_ptr<Resource> res = std::make_shared<Resource>(); processResource(res); // 传递 weak_ptr res.reset(); // 释放资源 processResource(res); // 输出 "Resource no longer exists." } |
string_view
和const string&
- 底层实现
const string&
是对一个已存在的std::string
对象的常量引用,依赖std::string
本身的内存管理(如堆分配的字符数组)
必须指向一个有效的std::string
对象
传递非std::string
参数时(如字符串字面量"Hello"
),会隐式构造临时std::string
,可能导致不必要的内存分配和拷贝std::string_view
是一个轻量级的非拥有视图,仅包含指向字符数据的指针和长度信息,不管理内存
可以引用任何连续的字符序列(如std::string
、C
风格字符串、字符数组等)
不涉及内存分配或数据拷贝,构造和析构成本极低
- 内存和生命周期
const string&
如果引用的是临时对象(如函数返回的std::string
),临时对象的生命周期会延长到引用结束
但传递非std::string
参数时(如char*
),需要构造临时std::string
,可能引发性能问题std::string_view
不管理内存,仅持有数据的指针和长度
必须确保底层数据的生命周期长于std::string_view
本身,否则可能导致悬垂引用
无法延长被引用数据的生命周期
- 性能
const string&
当参数本身是std::string
时,无额外开销
当传递非std::string
参数时(如char*
或"Hello"
),会构造临时std::string
,导致堆内存分配和数据拷贝std::string_view
构造和析构几乎无开销(仅复制指针和长度)
无内存分配或数据拷贝,适合高频调用或处理大字符串的场景
- 场景
const string&
需要与现有std::string
对象交互
需要依赖std::string
的成员函数或保证数据安全
不确定底层数据的生命周期时(避免悬垂引用)std::string_view
需要高效处理只读字符串参数(如工具函数、日志、解析器等)
需要接受多种字符串类型(std::string
,char*
, 子字符串等
明确知道底层数据的生命周期足够长
lambda
std::forward
std::async
std::future
std::package_task
std::promise
其他相关内容
WS_OVERLAPPEDWINDOW
样式
Windows API
中用于创建标准顶层应用程序窗口的 组合窗口样式- 它整合了多个基础样式,提供了完整的用户界面元素,适用于大多数主窗口场景
1 2 3 4 5 6 7 |
#define WS_OVERLAPPEDWINDOW \ (WS_OVERLAPPED | \ WS_CAPTION | \ WS_SYSMENU | \ WS_THICKFRAME | \ WS_MINIMIZEBOX | \ WS_MAXIMIZEBOX) |
- 样式详解
样式 | 功能描述 |
WS_OVERLAPPED |
创建一个可重叠的顶层窗口(带有边框和标题栏的基础样式)。 |
WS_CAPTION |
包含标题栏(必须与 WS_BORDER 或 WS_DLGFRAME 组合使用)。 |
WS_SYSMENU |
添加系统菜单(点击标题栏图标可弹出 关闭/最小化/最大化 等选项)。 |
WS_THICKFRAME |
允许用户通过拖拽边框调整窗口大小(显示可调节的窗口边框)。 |
WS_MINIMIZEBOX |
在标题栏右侧显示 最小化按钮(⚪)。 |
WS_MAXIMIZEBOX |
在标题栏右侧显示 最大化按钮(⬜)。 |
- 移除最大化按钮
1 2 |
DWORD style = WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX; // 禁用最大化按钮 CreateWindow(..., style, ...); |
- 窗口初始化状态
1 2 |
ShowWindow(hWnd, SW_SHOWMAXIMIZED); // 启动时最大化 ShowWindow(hWnd, SW_SHOWMINIMIZED); // 启动时最小化 |
关于样式冲突
WS_POPUP
- 与
WS_OVERLAPPEDWINDOW
冲突,后者包含WS_OVERLAPPED
- 与
WS_CHILD
- 不能与
WS_OVERLAPPEDWINDOW
同时使用,子窗口需用独立样式
- 不能与
WS_CHILD
样式
Windows API
中用于创建 子窗口(Child Window
) 的核心样式- 它是构建复杂用户界面的基础,常见于对话框控件、面板容器等场景
- 子窗口必须依附于父窗口
- 使用
WS_CHILD
创建的窗口必须通过CreateWindowEx
的hWndParent
参数指定父窗口句柄 - 子窗口的生命周期、可见性和坐标均受父窗口控制
- 使用
- 不可独立存在
- 子窗口无法脱离父窗口单独显示,关闭父窗口时会自动销毁所有子窗口
- 坐标系统:
- 子窗口的位置(
x
/y
)相对于父窗口的 客户区左上角,而非屏幕坐标
- 子窗口的位置(
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// 父窗口句柄 HWND hParentWnd = ...; // 创建子窗口(例如按钮) HWND hChildButton = CreateWindowEx( 0, // 扩展样式 "BUTTON", // 预定义控件类 "Click Me", // 控件文本 WS_CHILD | WS_VISIBLE, // 必须包含 WS_CHILD 10, 10, 100, 30, // 相对于父窗口客户区的坐标和大小 hParentWnd, // 父窗口句柄(必须指定) (HMENU)IDC_BUTTON1, // 控件ID(通过HMENU传递) GetModuleHandle(NULL), // 实例句柄 NULL ); |
-
可见性与裁剪
- 裁剪区域:子窗口超出父窗口客户区的部分会被自动裁剪(不可见)
- 可见性继承:若父窗口被隐藏(
ShowWindow(hParent, SW_HIDE)
),所有子窗口也会隐藏
-
子窗口的消息响应
- 在子窗口过程中处理必要消息,未处理的消息转发给默认处理函数:
1 2 3 4 5 6 7 8 |
LRESULT CALLBACK ChildWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_PAINT: /* ... */ break; default: return DefWindowProc(hWnd, msg, wParam, lParam); // 必须调用 } return 0; } |
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ 2022_03_0703/09
- ♥ 2020_05_11_0105/14
- ♥ 2022_03_0203/09
- ♥ 2023_02_0902/11
- ♥ 2022_03_1403/17
- ♥ 2023_02_2703/06