一个解析INI文件的类

虽然微软强烈推荐用注册表代替ini来记录软件配置,但是由于写ini文件的方便性和可读性的优势,还是让很多程序员选择把配置记录到ini文件中。但是用Windows API操作ini文件有个缺点,就是每次调用如GetPrivateProfileInt,WritePrivateProfileString等函数,都会产生一次文件打开关闭以及读写操作,并且对ini文件重新解析,这是非常低效的。所以如果需要大量的操作ini文件,例如需要读取很多配置信息以启动软件,那么这样的用法无疑会增加软件的冷启动时间。为了解决这个问题,我们就需要自己写一个模块,他能够一次性读取并且解析好ini文件。在需要读取的时候直接从内存读取,需要些的时候先全部写到内存里,最后在刷新到文件上。所以我写了一个ParseIni的类,来完成这个工作。代码如下:

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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
#ifndef __PARSE_INI_H__
#define __PARSE_INI_H__
#include <vector>
#include <string>
#include <fstream>
#include <windows.h>
#define INI_KEY_LINE 1
#define INI_SECTION_LINE 2
#define INI_COMMENT_LINE 3
#define INI_NC_KEY_LINE 4
#define INI_WORNG_SYNTAX 0
using namespace std;
class CParseIniA {
public:
CParseIniA() {}
~CParseIniA() {}
BOOL Open(const string &IniPath;, BOOL OpenAlways = TRUE);
vector EnumSectionNames();
vector EnumKeyNamesInSection(const string &SectionName;);
string GetSectionKeyValue(const string &SectionName;, const string &KeyName;, const string &DefaultValue;);
int GetSectionKeyValueInt(const string &SectionName;, const string &KeyName;, int DefaultValue);
BOOL SetSectionKeyValueInt(const string &SectionName;, const string &KeyName;, int value, BOOL CreateNew = TRUE);
BOOL SetSectionKeyValue(const string &SectionName;, const string &KeyName;, const string &Value;, BOOL CreateNew = TRUE);
BOOL Flush();
VOID Close();
private:
ULONG SyntaxCheck(const string &KeyLine;);
vector::iterator CreateSection(const string &SectionName;);
VOID CreateKeyValue(vector::iterator it, const string &KeyName;, const string &Value;);
BOOL IsSection(string &Line;);
BOOL SetKeyValue(string &Line;, const string &Value;);
string GetKeyValue(string &Line;);
string GetSectionName(string &Line;);
string GetKeyName(string &Line;);
string trim(const string& s ,const string& drop = " ");
vector m_IniContext;
string m_IniPath;
};
inline BOOL CParseIniA::Open( const string &IniPath;, BOOL OpenAlways)
{
ifstream IniFile(IniPath.c_str());
INT FileSize;
vector::iterator it;
if (!IniFile.is_open()) {
if (!OpenAlways) {
return FALSE;
}
m_IniPath = IniPath;
return TRUE;
}
m_IniPath = IniPath;
IniFile.seekg(0, std::ios_base::end);
FileSize = IniFile.tellg();
IniFile.seekg(0, std::ios_base::beg);
if (FileSize == 0) {
return TRUE;
}
while (IniFile) {
string IniLine;
getline(IniFile, IniLine);
m_IniContext.push_back(IniLine);
}
it = m_IniContext.end();
it--;
while (trim(*it).empty()) {
m_IniContext.pop_back();
it = m_IniContext.end();
it--;
}
return TRUE;
}
inline BOOL CParseIniA::IsSection( string &Line; )
{
string SectionLine = trim(Line);
BOOL Ret = FALSE;
if (SectionLine[0] == '[' && SectionLine[SectionLine.length() - 1] == ']') {
Ret = TRUE;
}
return Ret;
}
inline vector CParseIniA::EnumSectionNames()
{
vector SectionNames;
vector::iterator it;
for (it = m_IniContext.begin(); it != m_IniContext.end(); ++it) {
if (IsSection(*it)) {
SectionNames.push_back(GetSectionName(*it));
}
}
return SectionNames;
}
inline vector CParseIniA::EnumKeyNamesInSection(const string &SectionName; )
{
vector::iterator it;
vector KeyNames;
ULONG ScanState = 0;
for (it = m_IniContext.begin(); it != m_IniContext.end(); ++it) {
if (ScanState == 0) {
if (!IsSection(*it) || GetSectionName(*it) != SectionName) {
continue;
}
ScanState = 1;
}
else if (ScanState == 1) {
if (IsSection(*it)) {
break;
}
KeyNames.push_back(GetKeyName(*it));
}
}
return KeyNames;
}
inline string CParseIniA::GetSectionName( string &Line; )
{
INT Count = Line.length();
INT i;
BOOL Start = FALSE;
string SectionName;
for (i = 0; i < Count; i++) {
if (Line[i] == '[') {
Start = TRUE;
}
else if (Line[i] == ']') {
if (Start) {
break;
}
}
else {
if (Start) {
SectionName += Line[i];
continue;
}
}
}
return SectionName;
}
inline string CParseIniA::GetKeyName( string &Line; )
{
string KeyName;
KeyName = Line.substr(0, Line.find_first_of('='));
return trim(KeyName);
}
inline string CParseIniA::trim(const string& s, const string& drop)
{
string t(s);
string r = t.erase(t.find_last_not_of(drop) + 1);
return r.erase(0,r.find_first_not_of(drop));
}
inline int CParseIniA::GetSectionKeyValueInt( const string &SectionName;, const string &KeyName;, int DefaultValue )
{
char DefaultValueString[32] = {0};
sprintf_s(DefaultValueString, "%d", DefaultValue);
string ValueString = GetSectionKeyValue(SectionName, KeyName, DefaultValueString);
return atoi(ValueString.c_str());
}
inline string CParseIniA::GetSectionKeyValue(const string &SectionName;, const string &KeyName;, const string &DefaultValue; )
{
vector::iterator it;
ULONG ScanState = 0;
string Value = DefaultValue;
for (it = m_IniContext.begin(); it != m_IniContext.end(); ++it) {
if (ScanState == 0) {
if (!IsSection(*it) || GetSectionName(*it) != SectionName) {
continue;
}
ScanState = 1;
}
else if (ScanState == 1) {
if (IsSection(*it)) {
break;
}
if (SyntaxCheck(*it) != INI_KEY_LINE) {
continue;
}
if (GetKeyName(*it) == KeyName) {
Value = GetKeyValue(*it);
}
}
}
return Value;
}
inline string CParseIniA::GetKeyValue( string &Line; )
{
string KeyName;
KeyName = Line.substr(Line.find_first_of('=') + 1);
return trim(KeyName);
}
inline BOOL CParseIniA::SetKeyValue( string &Line;, const string &Value; )
{
INT Pos = Line.find_first_of('=');
if (Pos == string::npos) {
return FALSE;
}
Pos = Line.find_first_not_of(' ', Pos + 1);
if (Pos == string::npos) {
Pos = Line.find_first_of('=') + 1;
}
Line.erase(Pos);
Line += Value;
return TRUE;
}
inline BOOL CParseIniA::SetSectionKeyValueInt( const string &SectionName;, const string &KeyName;, int Value, BOOL CreateNew /*= TRUE*/ )
{
char ValueString[32] = {0};
sprintf_s(ValueString, "%d", Value);
return SetSectionKeyValue(SectionName, KeyName, ValueString, CreateNew);
}
inline BOOL CParseIniA::SetSectionKeyValue( const string &SectionName;, const string &KeyName;, const string &Value;, BOOL CreateNew )
{
vector::iterator it;
ULONG ScanState = 0;
BOOL Ret = FALSE;
for (it = m_IniContext.begin(); it != m_IniContext.end(); ++it) {
if (ScanState == 0) {
if (!IsSection(*it) || GetSectionName(*it) != SectionName) {
continue;
}
ScanState = 1;
}
else if (ScanState == 1) {
if (IsSection(*it)) {
break;
}
if (SyntaxCheck(*it) == INI_KEY_LINE || SyntaxCheck(*it) == INI_NC_KEY_LINE) {
if (GetKeyName(*it) == KeyName) {
Ret = SetKeyValue(*it, Value);
}
}
}
}
if (CreateNew && !Ret) {
if (ScanState == 0) {
it = CreateSection(SectionName);
CreateKeyValue(it, KeyName, Value);
}
else if (ScanState == 1) {
it--;
CreateKeyValue(it, KeyName, Value);
}
Ret = TRUE;
}
return Ret;
}
inline BOOL CParseIniA::Flush()
{
ofstream IniFile(m_IniPath.c_str());
vector::iterator it;
if (!IniFile.is_open()) {
return FALSE;
}
for (it = m_IniContext.begin(); it != m_IniContext.end(); ++it) {
IniFile << it->c_str() << endl;
}
return TRUE;
}
inline VOID CParseIniA::Close()
{
}
inline vector::iterator CParseIniA::CreateSection( const string &SectionName; )
{
string FullSectionName;
vector::iterator it;
FullSectionName += '[';
FullSectionName += SectionName;
FullSectionName += ']';
m_IniContext.push_back(FullSectionName);
it = m_IniContext.begin() + m_IniContext.size() - 1;
return it;
}
inline VOID CParseIniA::CreateKeyValue( vector::iterator it, const string &KeyName;, const string &Value; )
{
string KeyInfo;
KeyInfo += KeyName;
KeyInfo += " = ";
KeyInfo += Value;
while (it != m_IniContext.begin()) {
if (!trim(*it).empty()) {
break;
}
it--;
}
m_IniContext.insert(it + 1, KeyInfo);
}
inline ULONG CParseIniA::SyntaxCheck( const string &KeyLine; )
{
string Line = trim(KeyLine);
INT Pos, CommentPos1, CommentPos2;
string KeyName;
string Value;
if (IsSection(Line)) {
return INI_SECTION_LINE;
}
else if (Line[0] == ';' || Line[0] == '#') {
return INI_COMMENT_LINE;
}
else {
Pos = Line.find_first_of('=');
if (string::npos == Pos) {
return INI_WORNG_SYNTAX;
}
KeyName = trim(Line.substr(0, Pos));
if (KeyName.empty()) {
return INI_WORNG_SYNTAX;
}
Value = trim(Line.substr(Pos + 1));
if (Value.empty()) {
return INI_NC_KEY_LINE;
}
CommentPos1 = Value.find_first_of(';');
CommentPos2 = Value.find_first_of('#');
if (CommentPos1 == string::npos && CommentPos2 == string::npos) {
return INI_KEY_LINE;
}
CommentPos1 = CommentPos1 < CommentPos2 ? CommentPos1 : CommentPos2;
if (Value.erase(CommentPos1).empty()) {
return INI_NC_KEY_LINE;
}
return INI_KEY_LINE;
}
}
#endif

Tips

使用Windows未使用内存原理和应用

20150202104522

配置4G内存,并且使用过32bit Windows系统的人都知道,虽然自己有4G的物理内存,但是Windows还是明确的告诉你,它只会用其中的3GB多,还有几百MB物理内存是用不到的,即使你开启了PAE。当然如果你用的服务器系统,那就当我没说。至于微软为啥服务端32bit系统可以用4GB以上,而限制普通客户端系统,按照Windows Internals的说法,是为了考虑驱动程序的兼容性问题。我这里想介绍的是,如何使用这些没有使用的物理内存。

20150202105145

首先,要想使用这些内存,我们必须找到他们,但是找到他们之前,我们还得了解物理内存地址是怎么分配。物理内存地址除了要给RAM提供地址之外,还需要给设备内存提供地址。为了考虑驱动的兼容性,这些设备内存被分配到4G以内的地址上,这样一来,就会有部分RAM不得不分配到4G以外的地址上了,所以我们无法使用它们。

20150202110332

知道了这些,我们就需要聚焦到如何访问超过4GB内存的方法上了。不过方法也很简单,就是MmMapIoSpace函数,这个函数可以访问64bit的物理内存地址,并且将其映射到我们可以访问的虚拟内存上。

说到这里,程序的代码仿佛就呈现在脑海了,不过等等,还忽略了一个最困难的问题!到底有存在多少RAM内存在4GB以上的地址空间呢?说这个问题最为困难,是因为你需要根据不同的情况做出不同的选择。

20150202095553

1.主板支持通过Bios查询RAM内存分配情况,在这种情况下,我们可以调用中断来获得最真实的RAM分配表。
2.主板不支持通过Bios查询RAM内存分配情况,那么我们很无奈的必须采用一个简单粗暴的方法,Write & Test来获得4GB以外到底有多少RAM可用。

对于第二种情况,我们的做法是通过MmMapIoSpace函数,把4GB以上的物理地址逐一映射到虚拟内存中,然后写入特殊值,写入后马上读取,看是否写入成功,成功则证明RAM可用。
对于第一种情况,这里又要分为两个分支:
首先你的系统是NT6内核以下的情况,这时可以调用Ke386CallBios函数,调用中断接口。
而对于NT6的内核,我们需要调用新的x86BiosCall等一套函数。
需要调用的中断函数是0x15以及AX=E820,通过这个中断和功能号,我们就能获得SYSTEM MEMORY MAP

下面附带上这个功能的使用方法

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
INT 15 - newer BIOSes - GET SYSTEM MEMORY MAP
AX = E820h
EAX = 0000E820h
EDX = 534D4150h ('SMAP')
EBX = continuation value or 00000000h to start at beginning of map
ECX = size of buffer for result, in bytes (should be >= 20 bytes)
ES:DI -> buffer for result (see #00581)
Return: CF clear if successful
EAX = 534D4150h ('SMAP')
ES:DI buffer filled
EBX = next offset from which to copy or 00000000h if all done
ECX = actual length returned in bytes
CF set on error
AH = error code (86h) (see #00496 at INT 15/AH=80h)
Notes: originally introduced with the Phoenix BIOS v4.0, this function is
now supported by most newer BIOSes, since various versions of Windows
call it to find out about the system memory
a maximum of 20 bytes will be transferred at one time, even if ECX is
higher; some BIOSes (e.g. Award Modular BIOS v4.50PG) ignore the
value of ECX on entry, and always copy 20 bytes
some BIOSes expect the high word of EAX to be clear on entry, i.e.
EAX=0000E820h
if this function is not supported, an application should fall back
to AX=E802h, AX=E801h, and then AH=88h
the BIOS is permitted to return a nonzero continuation value in EBX
and indicate that the end of the list has already been reached by
returning with CF set on the next iteration
this function will return base memory and ISA/PCI memory contiguous
with base memory as normal memory ranges; it will indicate
chipset-defined address holes which are not in use and motherboard
memory-mapped devices, and all occurrences of the system BIOS as
reserved; standard PC address ranges will not be reported
SeeAlso: AH=C7h,AX=E801h"Phoenix",AX=E881h,MEM xxxxh:xxx0h"ACPI"
Format of Phoenix BIOS system memory map address range descriptor:
Offset Size Description (Table 00580)
00h QWORD base address
08h QWORD length in bytes
10h DWORD type of address range (see #00581)
(Table 00581)
Values for System Memory Map address type:
01h memory, available to OS
02h reserved, not available (e.g. system ROM, memory-mapped device)
03h ACPI Reclaim Memory (usable by OS after reading ACPI tables)
04h ACPI NVS Memory (OS is required to save this memory between NVS
sessions)
other not defined yet -- treat as Reserved
(from http://www.ctyme.com/intr/rb-1741.htm

获得了4GB内存以上的RAM范围以后,再加上MmMapIoSpace,我们就能够访问系统没有使用的内存了。

说了这么多,这个有什么具体应用呢?我见过最多利用这个特性做的产品就是内存盘了。也没看到什么用的特别好的其他应用的方面,我想原因主要有三点,第一,即使获得了物理内存,最后还是映射了4GB以内的虚拟内存上,内存操作终究无法访问直接更多物理内存;第二,这部分内存没有系统统一管理,同类型的软件无法共存;第三,有4G内存赶紧快去换个64位系统吧=v=。

NTInternals

分享SSD TRIM代码

14年的早些时候发布过SSD TRIM技术的文章,文章里面记录了关于TRIM技术的一些东西,其中就包括在Windows上如何使用TRIM。当然Windows 7及其更高版本的系统都是自带TRIM功能的,只有XP没有这个功能,当时我就写了个工具,在XP下进行TRIM。现在想想确实也没啥意思,就把代码分享说来吧。

https://github.com/0cch/SSDTrim.git

Tips

用Service Tag区分共享类型服务线程

Windows中有一种共享类型的服务,这种服务的特点是,他们可能同时有多个不同服务运行在同一个进程内。这些服务通常都是一些dll,他们被加载到宿主进程内运行,这个宿主进程我们见到最多的就是svchost了,如下图所示:

20150124220922

Windows这样做的好处就是尽可能的节约资源,当然不好地方就是,如果出了问题那么难以调试和定位。所以,为了更好的定位共享服务的工作线程,微软支持了一种叫做Service Tag的东西。Service Tag简单的说就是标注线程属于哪个服务的,这个是由Service Control Manager支持的。在TEB中有一个SubProcessTag字段,每当一个服务注册并且运行的时候,Service Control Manager会分配给服务线程一个id,这个id就是SubProcessTag,它能够唯一的表示服务,而且这个值是一直都被继承的,也就是说,如果服务线程再创建线程,那么新的线程的SubProcessTag也会被标记为父线程的id。当然,也有一种例外,那就是如果用了线程池,就不会继承SubProcessTag了。

在Advapi32.dll中有一个函数,叫做I_QueryTagInformation,这个函数可以根据SubProcessTag去查询Service的信息,有兴趣的同学可以看看下面的链接:http://wj32.org/wp/2010/03/30/howto-use-i_querytaginformation/

DebuggingTips

总结和展望:东方不亮西方亮

一转眼,一年又过去了,又到了总结过去的一年,计划新年的时候了。过去的一年里,最大的感觉是,好想经过了好多事情,但却没有经历什么事情。说起来挺绕的,慢慢来回忆下吧。

首先是工作上的事情,由于在前年整个部门被裁,去了一个价值观上和自己的很不同公司。事实证明,这确实不是什么好的决定,吐槽的事情就不细说了。之所以没换公司,是因为我觉得在北京待不了多久了,跳槽只会坑了其他的公司。所以也就勉强的继续干着。虽说是勉强的干活,但是工作的时间还是将近占用了每天的三分之二。工作的内容也没有什么创造性,基本上就是在可怕的代码里改来改去,当然了,也许这也是他这么可怕的原因吧。

多米乐骨效应,工作占用大量的时间,也导致我2014年的计划大打折扣。脚本编译器,写了一半听了下来,minikernel也没什么进展,唯一比较让人舒心的是,0CCh的山寨小工具,还是在慢慢的变多,其中还有花了大量精力写的everything_study,不过由于算法还没没达到目标效果(没有everything快,数据库也比他的大很多),所以没有放出来,也就是自己在用。确实也是因为工作的原因,没有太多时间和精力去改造文件id间相互索引的算法了。再说说健身,同样的理由,工作时间长影响身体和健身,现在25分钟基本上就跑个4.7km,13年的最后,都是能跑5km多点的。

不过东方不亮西方亮了,有失意的地方,总会在其他地方补回来=v=,生命中终于又多了一个人,能让我更加坚定的回老家了。所以,今年的计划特别难定,因为不知道回老家后到底是什么个情况。只能说回去之前继续坚持着,回去之后去找个心仪的事情。然后,编译器和minikernel希望能继续写,编译器希望能写玩,因为并不难。minikernel就比较复杂了,只能说能写多少写多少。另外我也特别喜欢山寨各种各样的小工具,自己造轮子自己用,赶紧挺开心的。健身方面,回去前照旧,回去后尽量保持。

其实生活上14年真的还算挺开心的,和sysdbg的博主一起还打完了好几个ps3的游戏。比如《神秘海域3》,《第一次世界大战——勇敢的心》等等=v=,还有他13年欠我的金钱豹还没请我吃呢…

最后,我还是希望2015年,我自己和家人、亲戚以及基友们,身体健康,阖家辛福快乐,工作顺利发大财!

Python获得文件版本信息

Python干啥都挺方便的,出了调用win32的api。当然了,可以用pywin32这种库。但我不喜欢为了一两个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
class VS_FIXEDFILEINFO(Structure):
_fields_ = [
("dwSignature", c_int),
("dwStrucVersion", c_int),
("dwFileVersionMS", c_int),
("dwFileVersionLS", c_int),
("dwProductVersionMS", c_int),
("dwProductVersionLS", c_int),
("dwFileFlagsMask", c_int),
("dwFileFlags", c_int),
("dwFileOS", c_int),
("dwFileType", c_int),
("dwFileSubtype", c_int),
("dwFileDateMS", c_int),
("dwFileDateLS", c_int)
]
def LOWORD(dword): return dword & 0x0000ffff
def HIWORD(dword): return dword >> 16
def GetFileVersion(filename):
size = windll.version.GetFileVersionInfoSizeW(filename, None)
if not size:
return ''
res = create_string_buffer(size)
windll.version.GetFileVersionInfoW(filename, None, size, res)
r = VS_FIXEDFILEINFO()
l = c_uint()
p = c_void_p()
windll.version.VerQueryValueW(res, '\\', byref(p), byref(l));
memmove(byref(r), p, sizeof(VS_FIXEDFILEINFO))
if not l.value:
return ''
return ('%d.%d.%d.%d' % (HIWORD(r.dwFileVersionMS), LOWORD(r.dwFileVersionMS),
HIWORD(r.dwProductVersionLS), LOWORD(r.dwProductVersionLS)));

Tips

DNSSwitcher —— 一个方便切换DNS的小工具

最近百度也推出的自己的公共DNS,现在可供我们选择使用的DNS也多了起来。但是每次更改DNS都输入IP,确实挺麻烦的。于是我周末在家就写了个切换DNS的小程序,绿色且易用,能在配置好的DNS直接切换,当然也能切换回自动获取DNS的模式。至于配置文件,可以手动修改,也可以通过程序来修改,都挺方便的。

20141214190237

配置文件格式如
[Google DNS]
dns1=8.8.8.8
dns2=8.8.4.4
[Open DNS]
dns1=208.67.222.222
dns2=208.67.220.220

下载:DNSSwitcher

Tips

获得使用打开保存对话框操作文件的记录

今天无意中看到了HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSavePidlMRU这个键值的用途,虽然感觉没啥实际用途,但是也挺有趣的,于是写了个小程序读取它。这个键值的意义非常明确,就是记录打开保存对话框的最近操作的文件的PIDL。所以我们可以通过PIDL来获得文件路径,就这么简单,确实没啥特别的实际用途吧,就当娱乐了。枚举的效果如下:

20141117231005

代码也很简单:

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
#include "stdafx.h"
#include <atlbase.h>
#include <atlstr.h>
#include <Shlobj.h>
#include <locale.h>
#include <vector>
#pragma comment(lib, "shell32.lib")
const TCHAR mru_path[] = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\OpenSavePidlMRU");
void EnumMRUValue(HKEY subkey, std::vector &mru;_files)
{
ULONG i = 0, j = 0;
TCHAR value_name[MAX_PATH];
ULONG value_name_length = MAX_PATH;
UCHAR data_buffer[1024];
ULONG data_length = sizeof(data_buffer);
while (RegEnumValue(subkey, i++, value_name, &value;_name_length, 0, NULL, data_buffer, &data;_length) == ERROR_SUCCESS)
{
if (_tcscmp(value_name, TEXT("MRUListEx")) != 0) {
CComPtr malloc_ptr;
HRESULT hr = SHGetMalloc(&malloc;_ptr);
LPITEMIDLIST file_pidl = (LPITEMIDLIST)malloc_ptr->Alloc(sizeof(UCHAR) + data_length);
if (file_pidl) {
memcpy(file_pidl, data_buffer, data_length);
WCHAR file_path[MAX_PATH] = { 0 };
if (SHGetPathFromIDList(file_pidl, file_path)) {
mru_files.push_back(file_path);
}
malloc_ptr->Free(file_pidl);
}
}
value_name_length = MAX_PATH;
data_length = sizeof(data_buffer);
}
}
BOOL PrintMRUFiles()
{
HKEY subkey;
LSTATUS l = RegOpenKeyEx(HKEY_CURRENT_USER, mru_path, 0, KEY_READ, &subkey;);
if (l != ERROR_SUCCESS) {
return FALSE;
}
ULONG i = 0;
TCHAR key_name[MAX_PATH];
ULONG key_name_length = MAX_PATH;
while (RegEnumKeyEx(subkey, i++, key_name, &key;_name_length, 0, NULL, NULL, NULL) == ERROR_SUCCESS)
{
HKEY ext_key;
LSTATUS l = RegOpenKeyEx(subkey, key_name, 0, KEY_READ, &ext;_key);
if (l == ERROR_SUCCESS) {
std::vector mru_files;
EnumMRUValue(ext_key, mru_files);
if (mru_files.size() > 0) {
_tprintf(TEXT("Extension Name : %s\n"), key_name);
ULONG j = 0;
for (std::vector::iterator it = mru_files.begin(); it != mru_files.end(); ++it) {
WIN32_FILE_ATTRIBUTE_DATA attribute_data = { 0 };
if (GetFileAttributesEx(it->GetString(), GetFileExInfoStandard, &attribute;_data)) {
ULONGLONG file_size = ((ULONGLONG)attribute_data.nFileSizeHigh) << 32 | attribute_data.nFileSizeLow;
_tprintf(TEXT("\t %u %s % 11I64u KB] %s\n"), j++,
(attribute_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0 ? TEXT("[FILE") : TEXT("[DIR "),
(file_size + 1023) / 1024,
it->GetString());
}
else {
_tprintf(TEXT("\t %u [ERROR_FILE_NOT_FOUND] %s\n"), j++,
it->GetString());
}
}
}
RegCloseKey(ext_key);
}
key_name_length = MAX_PATH;
}
RegCloseKey(subkey);
return TRUE;
}
int _tmain(int argc, _TCHAR* argv[])
{
setlocale(LC_ALL, "chs");
PrintMRUFiles();
return 0;
}

Tips

主线程退出前请先退出子线程

我们知道理论上如果一个进程的主线程退出,整个进程就会销毁,子线程自然也是要退出的。但是这并不意味着,程序退出的时候我们就能不问不管自己创建的子线程,因为不管他还真有可能出问题。下面是一个典型的主线程退出过程中,而子线程未退出,造成死锁整个进程的分析图,死锁在c runtime里,死锁原因看图便知,也不用太多解释了。

20141106094954

Tips