在我几年前上大学那会,我就想开发一个小软件,里面集成更多的功能,方便更多的人使用,但因为各种原因,软件始终没做成。到了现在,我觉得我可以重新建立好这个软件。本着学习交流的目的,我将软件开源,开源协议GPL3.0,并将里面的工具类授权为MIT,方便需要找代码的朋友们直接使用。
标签: Win32
C++:模拟键盘
Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /www/wwwroot/fawdlstty.com/wp-content/plugins/wp-syntax/wp-syntax.php on line 383
Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /www/wwwroot/fawdlstty.com/wp-content/plugins/wp-syntax/wp-syntax.php on line 383
模拟键盘是一个简单的话题,随便普通程序猿都能说出好多种方式。不同的方式应用于不同场合,总的来说分为三大类:
1、用户层模拟键盘
这个层来模拟是最方便的,但也是最容易无效的。总的来说有三种方式,第一种是直接往目标窗口发送按键消息;第二种是使用剪贴板复制待粘贴消息然后在目标窗口模拟Ctrl+V;第三种是用户层触发按键事件
这儿贴一个模拟输入一个字节的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | bool input (char ch) { DWORD KeyScan = ::OemKeyScan (ch); bool bShift = !!(KeyScan >> 16); UINT uCode = KeyScan & 0xFFFF; UINT uScanCode = ::MapVirtualKey (uCode, MAPVK_VK_TO_VSC); UINT uShiftScanCode = ::MapVirtualKey (VK_SHIFT, MAPVK_VK_TO_VSC); if (bShift) { ::keybd_event (VK_SHIFT, (BYTE) uShiftScanCode, KEYEVENTF_EXTENDEDKEY, 0); std::this_thread::sleep_for (std::chrono::milliseconds (20)); } ::keybd_event (uCode, (BYTE) uScanCode, KEYEVENTF_EXTENDEDKEY, 0); std::this_thread::sleep_for (std::chrono::milliseconds (20)); ::keybd_event (uCode, (BYTE) uScanCode, KEYEVENTF_KEYUP, 0); std::this_thread::sleep_for (std::chrono::milliseconds (20)); if (bShift) { ::keybd_event (VK_SHIFT, (BYTE) uShiftScanCode, KEYEVENTF_KEYUP, 0); std::this_thread::sleep_for (std::chrono::milliseconds (20)); } return true; } |
这个函数很可能在一些有简单安全措施的软件里面失效。软件屏蔽键盘模拟按键一般是使用钩子,那么可以dll注入然后反钩子,挂钩SetWindowsHookEx即可。注意反钩子必须和目标在同一个进程里,否则M$的Copy-On-Write会让反钩子失效。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | BOOL func_unhook (LPVOID func_ptr, WORD param_num, DWORD ret_val) { // mov eax, 12345678h // ret 0004h BYTE bBuf [] = { '\xB8', '\x00', '\x00', '\x00', '\x00', '\xC2', '\x00', '\x00' }; *(DWORD*) (&bBuf [1]) = ret_val; *(WORD*) (&bBuf [6]) = param_num * 4; DWORD dw = 0, dw2 = 0; BOOL bRet = ::VirtualProtect (func_ptr, 8, PAGE_EXECUTE_READWRITE, &dw); if (!bRet) return FALSE; ::memcpy (func_ptr, bBuf, 8); if (func_ptr != ::VirtualProtect) ::VirtualProtect (func_ptr, 8, dw, &dw2); return TRUE; } func_unhook (::SetWindowsHookA, 2, 1); func_unhook (::SetWindowsHookW, 2, 1); func_unhook (::UnhookWindowsHook, 2, 1); func_unhook (::SetWindowsHookExA, 4, 1); func_unhook (::SetWindowsHookExW, 4, 1); func_unhook (::UnhookWindowsHookEx, 1, 1); |
反钩子后模拟键盘事件差不多可以过绝大部分弱保护的安全措施了,不过这也不完全总是灵的。如果以上方法都不行,可以试试其他方案。
继续阅读C++:模拟键盘
C++:时间
时间在计算机中,通常以三种形式存储,三种方式各有各的特点。
1、Unix时间戳
这个东西算是时间表示的元老级结构了,因为出现的早,所以用的也最广泛。原理是,一个32位的数字,代表从1970年1月1日到现在的秒数。由于最高位代表符号位未被使用,所以可以表示的最长的时间点为2038年左右。目前所有Unix及类Unix操作系统都使用这种方式表示时间。著名的水果1970变砖BUG就是因为这种时间表示方式的固有特性所导致。这种时间表示方式还有一个非常大的问题就是,不能计算闰秒。闰秒的含义是,因为地球绕太阳自转的速度越来越慢,导致地球公转一圈的时间更久。现代的人们感受不出来,但如果时间线放长,并且不闰秒,那么可以预见,未来某一天的正午12点是晚上,凌晨12点是白天。所以,在某个时间点的闰秒尤为重要。闰秒通常在某个分钟的变化时,秒数记为57、58、59、60、0、1、2……,也就是说,多出来一个第60秒。闰秒虽好,统一地球公转,为太阳系历法做贡献,但每次闰秒通常会造成几千万美元的经济损失,原因为,使用了Unix时间戳这种计时方法的操作系统,并不能区分闰秒,导致操作系统时间与世界时间不同,然后在金融等领域,一秒钟多计算了什么什么,少计算了什么什么,导致结果不是实际想要的。这时候计算误差所导致的经济损失累积起来,就有这么严重了。另外,这种计时方式最多只能计算到2038年,之后就无能为力了。如果这些操作系统迟迟不更新原子计时方式,那么,等待它们的,只有,系统罢工了。因为以上两个问题太严重了,导致人们常常忽略了它的第三个问题:精度太差。最高精度就是秒了,但对于计算机来说,计算很多东西精度常常需要达到毫秒才够用,有的甚至需要微秒级精度,所以这些地方也用不了这种时间表示方式。
基于这种计时方式,出现了一个分支,使用64位进行计时,这就不存在年份限制这BUG了,不过这分支用的比较少。
继续阅读C++:时间
C++:字符串编码与字符串
1、编码
在讲字符串之前首先说说编码方式。字符串在程序用用数据类型进行存储,同时数据类型存储的也可以是不同编码方式的字符串。总的来说,常用编码方式有以下几种:
ASCII:最古老的编码方式,只使用后7位,可以存储英语大小写、数字及几乎所有常用半角符号。
ISO-8859-1:西欧地区使用的编码方式,兼容ASCII码,在最高位为1时用于描述西文符号。
GB2312/GBK/GB18030:这个是天朝用户专用编码方式,兼容ASCII码,对于英文字符使用1字节进行存储,对于中文使用2字节进行存储,同时两个字节的最高位均为1。值得注意的是,GB2312在Win32开发中常常被称作Ansi编码;其次,GBK为GB2312的扩充,GB18030为GBK的扩充。以前它们是不同的编码方式,但现在也没有严格的划分,通常三者代表同一种编码方式。
BIG-5:也是天朝用户专用编码方式,兼容ASCII码,与GB2312不同的是,它只能编码繁体字,不能编码简体字。
UTF-16/UCS-2:这两个名称所代表的是同一种编码方式,使用两个字节来存储一个中文字符或者一个字母,不兼容ASCII码。这种编码方式也划分为两种不同的子编码方式,分别为UCS-2 Big Endian与UCS2 Little Endian。常说的UTF-16或者UCS-2通常指的是UCS-2 Big Endian。这两种子编码方式的区别为,Big Endian高字节在前,低字节在后;Little Endian低字节在前,高字节在后。这种编码方式在Win32开发中常常被称作Unicode编码,但它属于一种误称;另外,这种编码方式有点浪费存储空间,并且也不能描述世界上所有的符号,相比其他编码,唯一优势是,字符串长度就等于字符个数。
UTF-8:使用最广泛的编码方式,没有之一!几乎所有的网页、XML描述文件、Json数据文件、大多数数据库以及Linux系统均使用的编码方式,相比而言GB2312、UTF-16只有在Windows平台用用而已,仗着Windows平台用的人多,所以也作为常用的编码方式,对于英文字符使用1字节进行存储,因此兼容ASCII编码;它同时也能编码世界上所有的文字,对于汉字而言这种编码方式使用3个字节进行存储,但理论上可以使用2、3、4、5或6字节来编码一个特定字符。
UTF-32/UCS-4:由于UTF-16不能编码所有的编码方式,但发明这编码的人不服,爱搞事,所以发明了4个字节来编码一个字符的编码方式,理论上可以描述世界上所有的字符,但由于一个字母都需要4个字节,过于浪费存储空间,所以这种编码方式几乎没有人使用。
以上是需要了解的编码方式,除了上面几个之外,不同地方也有他们自己的编码方式,以下为不完全统计:
西欧语系:ISO-8859-1
东欧语系:ISO-8859-2
土耳其语:ISO-8859-3
波罗的语:ISO-8859-4
斯拉夫语:ISO-8859-5
阿拉伯文:ISO-8859-6
希腊文:ISO-8859-7
希伯来文:ISO-8859-8
日文:Shift-JIS
韩文:EUC-KR
……
继续阅读C++:字符串编码与字符串
C++:偏移类型
Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /www/wwwroot/fawdlstty.com/wp-content/plugins/wp-syntax/wp-syntax.php on line 383
Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /www/wwwroot/fawdlstty.com/wp-content/plugins/wp-syntax/wp-syntax.php on line 383
地址偏移类型在C艹中算是偏冷门的技术,但在特定场合下可以节省大量的代码从而实现需求。这种类型的定义为:类或结构体中的数据成员相对于基址的偏移量。比如,一个类里面成员a地址相对于类基地址偏移量为4;成员b地址相对于类基地址偏移量为8等等。
示例代码如下:
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 | #include <iostream> using namespace std; class A { public: int a, b, c; A () : a (3), b (5), c (7) {} }; int main (int argc, char* argv[]) { int (A::* x) = &A::a; int A::* y = &A::b; using TZ = int (A::*); TZ z = &A::c; A *p = (A*)nullptr; cout << "地址偏移与偏移量值:" << endl; cout << &(p->a) << '\t' << &(p->*x) << endl; cout << &(p->b) << '\t' << &(p->*y) << endl; cout << &(p->c) << '\t' << &(p->*z) << endl; cout << "直接访问的结果:" << endl; cout << x << '\t' << y << '\t' << z << endl; return 0; } |
代码含义为:首先创建一个类,类里面有三个int类型成员属性,然后在main函数里面写了三种生成地址偏移类型的方法,其中第三种为首先构造地址偏移类型,然后用类型来定义。从经验上看,很容易看出偏移类型为0、4、8,实际运行结果也确实如此。
本机运行结果如下:
直接访问的结果有点出人意料,按道理说也应该是0、4、8才对,难道只代表一个符号么?
于是,在Disassembly上调试,为便于查看,我将代码改为cout << x;,以上代码反汇编结果为:
通过断点调试,x、y、z的值确实是0、4、8,另外反汇编代码可以明显看出,直接打印的含义为,如果值为0xFFFFFFFF,那么显示0,否则为1。因为通常不需要直接打印偏移长度,所以就把这个功能给省略了。真正需要用到的是,这个偏移是不是有效的。定义为,假如偏移长度为0xFFFFFFFF,那么代表这个偏移是无效的。于是我简单加了个hack:
1 2 | __asm { mov x, 0ffffffffh } cout << x; |
代码不出所料,结果为0。这儿的0和1分别代表此处偏移类型是否有效。
Windows服务访问控制
Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /www/wwwroot/fawdlstty.com/wp-content/plugins/wp-syntax/wp-syntax.php on line 383
服务控制在win32用户层系统编程中比较重要,一般用于创建自启动项或加载驱动,这样的话启动服务就是载入驱动,停止服务就是卸载驱动,极大方便了驱动控制。下面贴一个服务控制代码,可用于方便的控制服务。
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | #pragma once #ifndef __HSERVER_HPP__ #define __HSERVER_HPP__ #include <Windows.h> class hService { SC_HANDLE h_scm = NULL, h_service = NULL; QUERY_SERVICE_CONFIG *h_qsc = NULL; SERVICE_STATUS h_status; public: hService (LPCTSTR serv_name) { h_qsc = (LPQUERY_SERVICE_CONFIG)new BYTE [8 * 1024]; if ((h_scm = ::OpenSCManager (NULL, NULL, SC_MANAGER_ALL_ACCESS)) && serv_name && serv_name[0]) { h_service = ::OpenService (h_scm, serv_name, SERVICE_ALL_ACCESS); } } ~hService () { delete h_qsc; if (h_service) ::CloseServiceHandle (h_service); if (h_scm) ::CloseServiceHandle (h_scm); } //服务是否正在运行 BOOL is_running () { if (!h_service) return FALSE; if (::QueryServiceStatus (h_service, &h_status)) { if (h_status.dwCurrentState == SERVICE_RUNNING) return TRUE; } return FALSE; } //服务是否已停止 BOOL is_stopped () { if (!h_service) return FALSE; if (::QueryServiceStatus (h_service, &h_status)) { if (h_status.dwCurrentState == SERVICE_STOPPED) return TRUE; } return FALSE; } //服务是否已暂停 BOOL is_pause () { if (!h_service) return FALSE; if (::QueryServiceStatus (h_service, &h_status)) { if (h_status.dwCurrentState == SERVICE_PAUSED) return TRUE; } return FALSE; } //服务是否自动启动 BOOL is_auto_run () { DWORD d; if (!h_service) return FALSE; if (::QueryServiceConfig (h_service, h_qsc, 8 * 1024, &d)) { return h_qsc->dwStartType <= 2; } return FALSE; } //启动服务 BOOL start () { if (!h_service) return FALSE; if (is_running ()) return TRUE; return ::StartService (h_service, NULL, NULL); } //停止服务 BOOL stop () { if (!h_service) return FALSE; if (is_stopped ()) return TRUE; return ::ControlService (h_service, SERVICE_CONTROL_STOP, &h_status); } //暂停服务 BOOL pause () { if (!h_service) return FALSE; if (is_running ()) return ::ControlService (h_service, SERVICE_CONTROL_PAUSE, &h_status); return TRUE; } //恢复服务 BOOL resume () { if (!h_service) return FALSE; if (is_pause ()) return ::ControlService (h_service, SERVICE_CONTROL_CONTINUE, &h_status); return is_running (); } //设置自动启动服务 BOOL auto_start () { if (!h_service) return FALSE; if (is_auto_run ()) return TRUE; SC_LOCK sclLock = ::LockServiceDatabase (h_scm); BOOL bRet = ::ChangeServiceConfig (h_service, SERVICE_NO_CHANGE, SERVICE_AUTO_START, SERVICE_NO_CHANGE, NULL, NULL, NULL, NULL, NULL, NULL, NULL); if (sclLock) ::UnlockServiceDatabase (sclLock); return bRet; } //设置手动启动服务 BOOL demand_start () { if (!h_service) return FALSE; if (!is_auto_run ()) return TRUE; SC_LOCK sclLock = ::LockServiceDatabase (h_scm); BOOL bRet = ::ChangeServiceConfig (h_service, SERVICE_NO_CHANGE, SERVICE_DEMAND_START, SERVICE_NO_CHANGE, NULL, NULL, NULL, NULL, NULL, NULL, NULL); if (sclLock) ::UnlockServiceDatabase (sclLock); return bRet; } //创建服务 static hService *create_service (LPCTSTR serv_name, LPCTSTR display_name, DWORD service_type, LPCTSTR path) { hService *service = new hService (NULL); service->h_service = ::CreateService (service->h_scm, serv_name, display_name, SC_MANAGER_ALL_ACCESS, service_type, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, path, NULL, NULL, NULL, NULL, NULL); return service; } //删除服务 BOOL delete_service () { if (::DeleteService (h_service)) { h_service = NULL; return TRUE; } return FALSE; } }; #endif //__HSERVER_HPP__ |
创建头文件,引入以上代码之后,就可以方便进行服务控制了。调用方式在代码中已有注释。
Windows下编码转换
Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /www/wwwroot/fawdlstty.com/wp-content/plugins/wp-syntax/wp-syntax.php on line 383
对于网络程序来说经常需要用到编码转换,比如访问utf8编码网页下载之后,转为unicode进行显示。windows自带的编码API比较难用,所以对其进行简单的封装。分SDK和MFC两个版本代码,SDK版本如下
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 | #pragma once #ifndef __HCODEC_HPP__ #define __HCODEC_HPP__ #include <windows .h> #include <string> class hCodec { //不可实例化 hCodec () = delete; ~hCodec () = delete; static bool hCodec::_conv_Down (std::wstring& _old, std::string& _new, UINT ToType) { int lenOld = lstrlenW (_old.c_str ()); int lenNew = ::WideCharToMultiByte (ToType, 0, _old.c_str (), lenOld, NULL, 0, NULL, NULL); std::string s; s.resize (lenNew); bool bRet = ::WideCharToMultiByte (ToType, 0, _old.c_str (), lenOld, const_cast<char *>(s.c_str ()), lenNew, NULL, NULL); _new.clear (); _new = s.c_str (); return bRet; } static bool hCodec::_conv_Up (std::string& _old, std::wstring& _new, UINT ToType) { int lenOld = lstrlenA (_old.c_str ()); int lenNew = ::MultiByteToWideChar (ToType, 0, _old.c_str (), lenOld, NULL, 0); std::wstring s; s.resize (lenNew); bool bRet = ::MultiByteToWideChar (ToType, 0, _old.c_str (), lenOld, const_cast<wchar_t *>(s.c_str ()), lenNew); _new.clear (); _new = s.c_str (); return bRet; } public: static bool hCodec::AnsiToUnicode (std::string& _old, std::wstring& _new) { return hCodec::_conv_Up (_old, _new, CP_ACP); } static bool hCodec::UnicodeToAnsi (std::wstring& _old, std::string& _new) { return hCodec::_conv_Down (_old, _new, CP_ACP); } static bool hCodec::Utf8ToUnicode (std::string& _old, std::wstring& _new) { return hCodec::_conv_Up (_old, _new, CP_UTF8); } static bool hCodec::UnicodeToUtf8 (std::wstring& _old, std::string& _new) { return hCodec::_conv_Down (_old, _new, CP_UTF8); } static bool hCodec::AnsiToUtf8 (std::string& _old, std::string& _new) { std::wstring t; if (!hCodec::AnsiToUnicode (_old, t)) return false; return hCodec::UnicodeToUtf8 (t, _new); } static bool hCodec::Utf8ToAnsi (std::string& _old, std::string& _new) { std::wstring t; if (!hCodec::Utf8ToUnicode (_old, t)) return false; return hCodec::UnicodeToAnsi (t, _new); } }; #endif //__HCODEC_HPP__</wchar_t></char></string></windows> |
Windows注册表访问的封装
Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /www/wwwroot/fawdlstty.com/wp-content/plugins/wp-syntax/wp-syntax.php on line 383
注册表这东西用的越来越少了,但在特定场合还是不可替代的东西。比如开机启动项、环境变量和文件扩展名等各种系统信息都是放在注册表中。
由于注册表API访问稍微麻烦了点,于是我对其进行简单的封装。代码如下:
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | #pragma once #ifndef __HREG_HPP__ #define __HREG_HPP__ #include <Windows.h> // main_key 注册表根键可取值 // HKEY_CLASSES_ROOT // HKEY_CURRENT_CONFIG // HKEY_CURRENT_USER // HKEY_LOCAL_MACHINE // HKEY_USERS // type 键值类型可取值 // REG_BINARY 二进制数据 // REG_DWORD 双字 // REG_DWORD_LITTLE_ENDIAN 双字小头模式 // REG_DWORD_BIG_ENDIAN 双字大头模式 // REG_EXPAND_SZ 包含未展开的环境变量的引用的字串 // REG_LINK 一个用 RegCreateKeyEx 传 REG_OPTION_CREATE_LINK 创建的符号链接的字串 // REG_MULTI_SZ 多个字符串,用\0分隔,末尾为\0\0 // REG_NONE 没定义值类型 // REG_QWORD 四字 // REG_QWORD_LITTLE_ENDIAN 四字小头模式 // REG_SZ 普通字符串 class hReg { //不可实例化 hReg () = delete; ~hReg () = delete; hReg (hReg&) = delete; public: //创建注册表路径 //main_key: 注册表根键 //sub_key: 注册表路径 //type: 键值类型 //value: 值 //value_size: 值长度(字节) static BOOL set_path (HKEY main_key, LPCTSTR sub_key, DWORD type, LPBYTE value, DWORD value_size) { return ERROR_SUCCESS == ::RegSetValueEx (main_key, sub_key, 0, type, value, value_size); } //创建注册表键值 //main_key: 注册表根键 //sub_key: 注册表路径 //sub_key2: 键值名称 //type: 键值类型 //value: 值 //value_size: 值长度(字节) static BOOL set_key (HKEY main_key, LPCTSTR sub_key, LPCTSTR sub_key2, DWORD type, LPBYTE value, DWORD value_size) { HKEY hKey; if (ERROR_SUCCESS != ::RegOpenKeyEx (main_key, sub_key, 0, KEY_WRITE, &hKey)) return FALSE; BOOL bRet = ERROR_SUCCESS == ::RegSetValueEx (hKey, sub_key2, 0, type, value, value_size); ::RegCloseKey (hKey); return bRet; } //获取注册表路径键值 //main_key: 注册表根键 //sub_key: 注册表路径 //value: 值 //value_size: 值长度(字节) static BOOL get_path_value (HKEY main_key, LPCTSTR sub_key, LPBYTE value, DWORD &value_size) { return ERROR_SUCCESS == ::RegQueryValueEx (main_key, sub_key, NULL, NULL, value, &value_size); } //获取注册表路径键值 //main_key: 注册表根键 //sub_key: 注册表路径 //sub_key2: 键值名称 //value: 值 //value_size: 值长度(字节) static BOOL get_key_value (HKEY main_key, LPCTSTR sub_key, LPCTSTR sub_key2, LPBYTE value, DWORD &value_size) { HKEY hKey; if (ERROR_SUCCESS != ::RegOpenKeyEx (main_key, sub_key, 0, KEY_READ, &hKey)) return FALSE; BOOL bRet = ERROR_SUCCESS == ::RegQueryValueEx (hKey, sub_key2, NULL, NULL, value, &value_size); ::RegCloseKey (hKey); return bRet; } //删除注册表路径 //main_key: 注册表根键 //sub_key: 注册表路径 static BOOL delete_path (HKEY main_key, LPCTSTR sub_key) { return ERROR_SUCCESS == ::RegDeleteKey (main_key, sub_key); } //删除注册表键值 //main_key: 注册表根键 //sub_key: 注册表路径 //sub_key2: 注册表路径 static BOOL delete_key (HKEY main_key, LPCTSTR sub_key, LPCTSTR sub_key2) { HKEY hKey; if (ERROR_SUCCESS != ::RegOpenKeyEx (main_key, sub_key, 0, KEY_ALL_ACCESS, &hKey)) return FALSE; BOOL bRet = ERROR_SUCCESS == ::RegDeleteKey (hKey, sub_key2); ::RegCloseKey (hKey); return bRet; } }; #endif //__HREG_HPP__ |
全部为静态函数,注上了比较完整的注释,另外函数名也较之前清晰,统一返回BOOL表示执行成功或失败。
Win32打开文件/另存为/文件夹对话框实现
一个比较有年代的东西,适合收藏。效果如下:
调用稍微麻烦了点,已将其封装为库,首先引用hSelect.hpp,代码如下
继续阅读Win32打开文件/另存为/文件夹对话框实现