广度遍历删除文件

最近遇到一个要删除文件夹的问题,文件夹内有大量文件和子文件夹,而且结构非常复杂,删除特别慢。于是思考了一下如何能加速文件删除的问题。我看到大部分的实现方法都是深度遍历,即遇到新的文件夹就进入文件夹遍历文件,直到结束后返回上一层继续遍历。实际上这种方法存在一个问题,在我们的硬盘上,文件夹和文件一般是B-Tree分布的,所以同一层文件夹的文件的数据都比较相近,而不同的层的文件夹的文件可能差距比较远,于是深度遍历让硬盘磁头从一个文件夹跨越到另外一个文件夹,磁头花的时间更长了,更何况子文件夹遍历结束,还得从子文件夹在移动回父文件夹。而是用广度遍历就不同,他把当前目录遍历完成后才去遍历另外一个,这样磁头移动的总距离肯定更少,遍历速度当然更快。以下代码我的一个实现:

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
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <vector>
#include <stack>
#include <string>
class AutoFindHandle {
public:
AutoFindHandle(HANDLE find_handle) : find_handle_(find_handle) {}
~AutoFindHandle() {
FindClose(find_handle_);
}
private:
HANDLE find_handle_;
};
BOOL CollectFileAndDir(const std::wstring &dir, std::vector<std::wstring> &file_path, std::vector<std::wstring> &dir_path)
{
HANDLE find_handle;
WIN32_FIND_DATAW data;
std::wstring temp_path;
std::wstring current_dir = dir;
std::stack<std::wstring> temp_dir;
if (current_dir[current_dir.length() - 1] != L'\\') {
current_dir += L"\\";
}
std::wstring path = current_dir + L"*";
find_handle = FindFirstFileW(path.c_str(), &data);
if (find_handle == INVALID_HANDLE_VALUE) {
return TRUE;
}
AutoFindHandle auto_find_handle(find_handle);
if (wcscmp(data.cFileName, L"..") != 0 &&
wcscmp(data.cFileName, L".") != 0) {
temp_path = current_dir;
temp_path += data.cFileName;
if ((data.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)) != 0) {
SetFileAttributesW(temp_path.c_str(), FILE_ATTRIBUTE_NORMAL);
}
if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
temp_dir.push(temp_path);
}
else {
file_path.push_back(temp_path);
}
}
while (FindNextFileW(find_handle, &data)) {
if (wcscmp(data.cFileName, L"..") != 0 &&
wcscmp(data.cFileName, L".") != 0) {
temp_path = current_dir;
temp_path += data.cFileName;
if ((data.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)) != 0) {
SetFileAttributesW(temp_path.c_str(), FILE_ATTRIBUTE_NORMAL);
}
if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
temp_dir.push(temp_path);
}
else {
file_path.push_back(temp_path);
}
}
}
while (!temp_dir.empty()) {
CollectFileAndDir(temp_dir.top(), file_path, dir_path);
temp_dir.pop();
}
dir_path.push_back(dir);
return TRUE;
}
BOOL DeleteFolder(LPCWSTR path)
{
std::vector<std::wstring> file_path;
std::vector<std::wstring> dir_path;
if (!CollectFileAndDir(path, file_path, dir_path)) {
return FALSE;
}
for (std::vector<std::wstring>::iterator it = file_path.begin(); it != file_path.end(); ++it) {
DeleteFileW(it->c_str());
}
for (std::vector<std::wstring>::iterator it = dir_path.begin(); it != dir_path.end(); ++it) {
RemoveDirectoryW(it->c_str());
}
return TRUE;
}

Tips

鸡年总结,狗年计划

虽然没到十五,但是这个年基本上也是过完了。是收心总结鸡年,展望狗年的时候了。

生活方面,鸡年过的不算顺心,尤其是上半年,麻烦事情接二连三,具体也事情也就不想多写了,不愉快的事情就应该被忘记。工作上,虽然没有明显的进步,但也算是得到了不错的回报,内心有愧的拿了一个优秀员工。重点来了,要说说技术方面的事情。去年下半年快到年底的时候,我做了一个决定,学习深度学习相关知识,作为今后转型的砝码。这里不得不说,做这个决定其实已经很晚了,因为已经有很多人早就踏入了这个领域。我总是想,如果能早两年开窍就好了,但是凡是哪有如果呢。更何况就在去年初我还在跟以前同事争论,坚定的认为个人PC行业还是非常宽阔的,但实际上那个想法也只是说自己的一厢情愿而已。下半年某企业找到我说想让我去他们的个人PC业务部门,给的等级还不错,薪资水平也很快给我翻倍了,但是我拒绝了,那个时候我就知道自己内心里已经不看好个人PC,尤其是Windows上面的发展了。于是乎,打算学习更接近未来趋势的一些东西。

当今最火的两个领域,区块链和深度学习,我很快的就选择学习深度学习的相关知识了。原因很简单,深度学习现在已经在很大程度上推进了社会发展。而区块链技术,除了在各种加密币方面非常火之外,貌似还没看到什么用途能促进社会进步,至少没有深度学习那么明显吧。下半年的两三个月里开始慢慢接触了深度学习,当然也包括一些其他的机器学习领域的知识。于是深深的感慨,数学在这些领域的重要性,当然又一次后悔没有学好数学,也不得不吐槽我们的高等数学教育是多么的死板无趣。

今年计划,生活上的事情其实没有什么计划可言,就希望过的更加顺心吧。工作方面,希望能主导一些真正有意义的项目,希望能推进公司往深度学习方面来发展。另外技术方面专注于数学和深度学习。具体的计划就不列了,因为貌似每次列出来也不一定能全部完成 =v=

Tips

ssh的反向连接

家里有个树莓派,基本上用来当了一个微型服务器,24小时跑着。主要用途是在我不在家的时候控制家里网络,充分利用带宽。一直以来,我都是用树莓派把IP更新到DDNS(家里是外网IP),并且在路由器上做端口映射。这样就能在其他地方访问树莓派了。但是不知为何,这段时间无法从外网ping通家里(后来发现是光猫问题,换了个猫就好了),从外围直接联通家里的树莓派这条路被封了,所以就折腾了SSH的反向连接功能。用这个方法也可以克服家里是内网IP的情况,当然前提是我们在外网有一台服务器或者vps。原理上和其他的反向连接是一样的,这里就不讨论原理了,直接介绍方法。

1
# ssh -fCNR remote_port:localhost:local_port [email protected]_addr
1
2
3
4
-f 后台执行ssh指令
-C 允许压缩数据
-N 不执行远程指令
-R 将远程主机(服务器)的某个端口转发到本地端指定机器的指定端口

这条命令的意思是:请将remote_addr机器里对remote_port的连接转发到本地机器的本地端口。举个例子,假设我有一台内网机器A,其ssh端口为6622,另外有一台VPS,地址是VPS_ADDR.com,SSH端口是8822。这个时候我想通过VPS的9922端口去访问树莓派的SSH。那么我需要用命令:

1
# ssh -fCNR 9922:localhost:6622 [email protected]_ADDR.com

当完成了这个命令后,我们就可以在VPS上登陆树莓派了:

1
# ssh -p 9922 [email protected]ocalhost

解决了这个问题后,我们需要解决另外一个问题。我们每次输入反向连接的命令的时候总需要输入VPS密码,这个非常不利于我们把命令设置为开机启动。为了解决这个问题,首先想到的是sshpass。这个工具可以帮助我们自动输入密码,命令如下:

1
# sshpass -p your_VPS_password ssh -fCNR 9922:localhost:6622 [email protected]_ADDR.com

把命令放到树莓派的开机启动命令后,重启树莓派,成功从VPS连入了树莓派。不过第二天,我又发现了另外一个问题,SSH的反向连接在无人访问的时候会超时,超时后会断开链接。这样明显不符合我想连就连的需求。于是就得上autossh这个工具了,这个工具会守护ssh,让ssh的反向连接保持联通。但是autossh不支持自动输入密码,网上很多解决办法是用证书的方式省去了输入密码的过程,不过我这里提供另外一个方法,使用expect工具。

1
2
3
4
5
6
# expect -c "
spawn autossh -M 11122 -CNR 9922:localhost:6622 [email protected]_ADDR.com -o StrictHostKeyChecking=no
expect \"Password:\"
send \"your_password\r\"
expect eof
"

这个工具可以自动和ssh进行交互,当VPS提示Password:的时候,我们send密码过去就行了,注意使用StrictHostKeyChecking=no来避免Host key的验证提示。把这个命令放在开机启动的时候执行,完美解决了外网访问家里树莓派的问题。

Tips

将图片转化成mnist格式

利用mnist数据对数字符号进行识别基本上算是深度学习的Hello World了。在我学习这个“hello world”的过程中,多多少少有点不爽,原因是无论是训练数据还是测试数据,都是mnist准备好的,即使最后训练的数字识别率很高,我也没有什么参与感。其实,我特别想测试自己手写数字的识别率。
为了完成上述想法,我第一个想到的是将普通图片数据转换成mnist数据。mnist的数据格式非常简单,如下图所示:

2017-12-10-t10k-images.idx3-ubyte

2017-12-10-t10k-labels.idx1-ubyte

两幅图分别表示了图形数据和标签数据。他们都有一个4字节长度的magic number,用来识别数据的具体格式。如果是标签数据,那么格式相对简单,后续是一个标签数量,接着的是标签数据(0-9的值)。如果是图像数据,那么magic number后,出了4个字节的数据数量以外,还有分别占4字节的行列数据,最后的就是图像数据。结构非常简单,但是值得注意的事情有两点:

  1. 数据使用big endian组织的。
  2. 图像数据中,255表示前景,也就是黑色,0表示背景,也就是白色,这和我们平时看到的RGB是不同的。

知道了数据格式,接下来的事情就是用程序将图像转换到mnist了。说实在的,如果对于操作二进制的数据,C比python还是方便不少的。但是C读取图像更加麻烦。所以这里推荐还是使用python对数据做转化。

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
# 首先导入图像处理库
from PIL import Image
from array import *
# 接下来打开图片,并且将像转化为8bit黑白像素
im = Image.open(path_to_image)
im = im.convert('L')
# 转换图像到mnist的大小28*28
im = im.resize((28,28))
# 获取图像长宽
width, height = im.size()
# 将图像数据转化位mnist
for x in range(0,width):
for y in range(0,height):
data_image.append(255 - pixel[y,x])
# 将数据写到mnist文件中
header = array('B')
# 写入magic number
header.extend([0,0,8,3])
# 写入数据数量,以一个图片为例
header.extend([0,0,0,1])
# 写入行列像素数
header.extend([0,0,0,28])
header.extend([0,0,0,28])
# 写入数据
header.extend(data_image)
# 最后写文件
output_file = open(r'data.mnist', 'wb')
header.tofile(output_file)
output_file.close()

以上是对图像数据的转换,标签数据的转换代码和以上代码基本一样,所以这里不再赘述。
有了这个方法,我们可以通过画图软件画上一堆自己手写的数字,通过python批量转化成mnist格式的数据,再让tensorflow进行测试,算出模型对我们自己手写数字的识别正确率。

好了,以上就是我说的第一种让模型识别自己的手写数字的方法。不过,这个方法不能实时的识别我手写的数字,让人总觉得缺点什么。于是就有了第二种方法,这种方法将借助浏览器,js以及web server等工具将手写的数字实时的传给后台的模型进行识别,然后把结果回复给用户。不过这个方法就要等待下一篇文章来描述了。

DeepLearner

TensorFlow pip安装(非GPU版)

TensorFlow安装有很多种方法,其中用dock安装是最方便的方式,源代码编译是最麻烦但是最能切合自己机器CPU的方法,当然了,你也可以用Anacaonda安装(一个科学计算的工具合集,以后有机会再介绍)。不过这次,我将介绍使用pip安装的方法。我个人认为这种方法其实是众多方法中最简单方便的方法了(系统基于ubuntu 16.04)。

先来说说常规用pip安装的方法:

1
pip3 install --upgrade tensorflow

首先当然是安装tensorflow框架了(当然你得有python,如果没有可以使用apt-get install python3-pip python3-dev来安装)

接下来就是安装temsorflow了

1
pip3 install tensorflow # Python 3.n; CPU support (no GPU support)

如果你的电脑和系统的配置正常,这个时候应该可以正常使用tensorflow了。不过,在我的电脑上出现了这么一个警告:

1
Your CPU supports instructions that this TensorFlow binary was not compiled to use: SSE4.1 SSE4.2 AVX AVX2 FMA

很明显,现代CPU支持折现特性已经很常见了,但是框架版本并不支持。难道真的要便宜一个么?(据我测试2G内存的机器是无法编译tensorflow的)。于是物品翻阅了github,发现以及有人走在前面,在 https://github.com/mind/wheels 里有很多已经编译的模块提供ubuntu。比如,在我的平台上适合的版本是

1
https://github.com/mind/wheels/releases/download/tf1.4-cpu/tensorflow-1.4.0-cp35-cp35m-linux_x86_64.whl

那么就可以使用这个版本来安装tensorflow:

1
pip --no-cache-dir install https://github.com/mind/wheels/releases/download/tf1.4-cpu/tensorflow-1.4.0-cp35-cp35m-linux_x86_64.whl

DeepLearner

C++20增加的生成器特性

不得不说,C++的语法真是越来越高级了,编译器为程序做的事情也是越来越多了。
比方说下面的这个程序,有两个点可以说说:

  1. 生成器 —— 编译器为了实现生成器,不得不把这一个函数拆成两个部分,分为初始化部分和程序运行部分。初始化部分主要用来初始保存生成器运行状态的内存空间。每当co_yield返回后,这片内存空间需要保持当前变量的值,以方便程序再次进入生成器后继续运行。

  2. 异步 —— 在新的标准里,我们实现异步的编码成本更低了。编译器同样为我们做了大量工作,这个例子中,主线程执行到subfuc函数的co_await后,会启动两个线程,一个执行异步函数awaitfuc,另外一个等待这个函数结束执行后面的代码,而主线程本身则是跳出函数执行printf函数。没错,我们简简单单的一句话就让编译器生成了这么多代码。

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
#include <experimental/generator>
#include <future>
#include <windows.h>
using namespace std::experimental;
generator<int> foo()
{
int p = 1;
int q = 2;
for (int j = 0; j < 10; j++) {
for (int i = 0; i < 10; i++) {
co_yield i + p;
}
}
int pp = 3;
}
std::future<int> awaitfuc()
{
Sleep(5000);
co_return 100;
}
std::future<int> subfuc()
{
auto p = co_await std::async(awaitfuc);
printf("return 100\n", p.get());
for (int i : foo()) {
printf("%d ", i);
}
co_return 0;
}
int main()
{
subfuc();
printf("hello\n");
Sleep(100000);
return 0;
}

Tips

调试器是怎么匹配程序的符号文件的

微软的天才软件工程师们设计的PE(Portable Executable)文件数据结构有极强的扩展性和兼容性。我们关心的符号文件信息存储在PE结构中,一个叫做Debug Directory的节里。

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
0:000> !dh -f ntdll
File Type: DLL
FILE HEADER VALUES
8664 machine (X64)
7 number of sections
590296CE time date stamp Thu Apr 27 18:11:42 2017
0 file pointer to symbol table
0 number of symbols
F0 size of optional header
2022 characteristics
Executable
App can handle >2gb addresses
DLL
OPTIONAL HEADER VALUES
20B magic #
9.00 linker version
FB800 size of code
A9600 size of initialized data
0 size of uninitialized data
0 address of entry point
1000 base of code
----- new -----
00000000774e0000 image base
1000 section alignment
200 file alignment
3 subsystem (Windows CUI)
6.01 operating system version
6.01 image version
6.01 subsystem version
1AA000 size of image
400 size of headers
1B5DB0 checksum
0000000000040000 size of stack reserve
0000000000001000 size of stack commit
0000000000100000 size of heap reserve
0000000000001000 size of heap commit
140 DLL characteristics
Dynamic base
NX compatible
101200 [ F1A3] address [size] of Export Directory
0 [ 0] address [size] of Import Directory
14E000 [ 5A028] address [size] of Resource Directory
13B000 [ 127EC] address [size] of Exception Directory
1A2E00 [ 4300] address [size] of Security Directory
1A9000 [ 4E8] address [size] of Base Relocation Directory
FC58C [ 38] address [size] of Debug Directory
0 [ 0] address [size] of Description Directory
0 [ 0] address [size] of Special Directory
0 [ 0] address [size] of Thread Storage Directory
0 [ 0] address [size] of Load Configuration Directory
0 [ 0] address [size] of Bound Import Directory
0 [ 0] address [size] of Import Address Table Directory
0 [ 0] address [size] of Delay Import Directory
0 [ 0] address [size] of COR20 Header Directory
0 [ 0] address [size] of Reserved Directory

使用!dh命令可以显示PE文件的关键信息,这里可以看到Debug Directory的偏移地址是FC58C,大小是38个字节,其对应结构体是_IMAGE_DEBUG_DIRECTORY。

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
typedef struct _IMAGE_DEBUG_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Type;
DWORD SizeOfData;
DWORD AddressOfRawData;
DWORD PointerToRawData;
} IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;
#define IMAGE_DEBUG_TYPE_UNKNOWN 0
#define IMAGE_DEBUG_TYPE_COFF 1
#define IMAGE_DEBUG_TYPE_CODEVIEW 2
#define IMAGE_DEBUG_TYPE_FPO 3
#define IMAGE_DEBUG_TYPE_MISC 4
#define IMAGE_DEBUG_TYPE_EXCEPTION 5
#define IMAGE_DEBUG_TYPE_FIXUP 6
#define IMAGE_DEBUG_TYPE_OMAP_TO_SRC 7
#define IMAGE_DEBUG_TYPE_OMAP_FROM_SRC 8
#define IMAGE_DEBUG_TYPE_BORLAND 9
#define IMAGE_DEBUG_TYPE_RESERVED10 10
#define IMAGE_DEBUG_TYPE_CLSID 11
1
2
3
4
5
6
7
8
9
10
11
0:000> dt ntdll+FC58C ole32!_IMAGE_DEBUG_DIRECTORY
+0x000 Characteristics : 0
+0x004 TimeDateStamp : 0x590288a9
+0x008 MajorVersion : 0
+0x00a MinorVersion : 0
+0x00c Type : 2
+0x010 SizeOfData : 0x22
+0x014 AddressOfRawData : 0xfc5c8
+0x018 PointerToRawData : 0xfb9c8

需要注意的是这三个数据成员,Type,SizeOfData以及AddressOfRawData。其中Type是Debug数据类型,SizeOfData是数据大小,AddressOfRawData是数据对应的内存地址。通过dt命令,可以查看结构体和数据的对应关系。从上面的输出可知Debug数据类型是CODEVIEW,数据大小是0x22个字节,数据的内存偏移是0xfc5c8。

1
2
3
4
5
6
7
8
9
10
11
0:000> db ntdll+0xfc5c8
00000000`775dc5c8 52 53 44 53 49 7b 4d 74-81 7b 0c 47 a2 d8 a8 d2 RSDSI{Mt.{.G....
00000000`775dc5d8 62 fc 8a 29 02 00 00 00-6e 74 64 6c 6c 2e 70 64 b..)....ntdll.pd
00000000`775dc5e8 62 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 b...............
00000000`775dc5f8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000`775dc608 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000`775dc618 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000`775dc628 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000`775dc638 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

使用db命令查看这部分数据,我们可以发现ntdll.pdb的字符串。实际上,通过type已经知道了Debug数据类型是CODEVIEW,这样就可以确定数据的结构体是:

1
2
3
4
5
6
7
8
9
struct CV_INFO_PDB
{
DWORD CvSignature;
GUID Signature;
DWORD Age;
BYTE PdbFileName[];
} ;
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
0:000> dt _guid ntdll+0xfc5c8+4
ntdll!_GUID
{744d7b49-7b81-470c-a2d8-a8d262fc8a29}
+0x000 Data1 : 0x744d7b49
+0x004 Data2 : 0x7b81
+0x006 Data3 : 0x470c
+0x008 Data4 : [8] "???"
可以看到CvSignature = “RSDS”, Signature = {744d7b49-7b81-470c-a2d8-a8d262fc8a29},Age = 2,PdbFileName=“ntdll.pdb”。
0:000> !lmi ntdll
Loaded Module Info: [ntdll]
Module: ntdll
Base Address: 00000000774e0000
Image Name: ntdll.dll
Machine Type: 34404 (X64)
Time Stamp: 590296ce Thu Apr 27 18:11:42 2017
Size: 1aa000
CheckSum: 1b5db0
Characteristics: 2022 perf
Debug Data Dirs: Type Size VA Pointer
CODEVIEW 22, fc5c8, fb9c8 RSDS - GUID: {744D7B49-7B81-470C-A2D8-A8D262FC8A29}
Age: 2, Pdb: ntdll.pdb
CLSID 4, fc5c4, fb9c4 [Data not mapped]
Image Type: FILE - Image read successfully from debugger.
C:\Windows\SYSTEM32\ntdll.dll
Symbol Type: PDB - Symbols loaded successfully from image path.
d:\symbols\ntdll.pdb\744D7B497B81470CA2D8A8D262FC8A292\ntdll.pdb
Load Report: public symbols , not source indexed
d:\symbols\ntdll.pdb\744D7B497B81470CA2D8A8D262FC8A292\ntdll.pdb

再来看看Windbg匹配ntdll.pdb的真实路径,d:\symbols\ntdll.pdb\744D7B497B81470CA2D8A8D262FC8A292\ntdll.pdb。对比一下就可以发现其中的奥秘。原来Windbg识别执行程序的PDB路径是依赖guid,age和PdbFileName。具体来说就是 {符号设置路径}{PdbFileName}{guid}{age}{PdbFileName}。
如果想写程序获取这些信息并不需要像上面那样解析PE文件结构,实际上微软给我们提供了这方面的支持,在dbghelp.dll里导出了一个叫做SymSrvGetFileIndexInfo的函数,这个函数获得的SYMSRV_INDEX_INFO结构中,就包含以上我们需要的数据。

Debugging

测试math-plugin

Inline

Simple inline \(a = b + c\).

This equation \(\cos 2\theta = \cos^2 \theta - \sin^2 \theta = 2 \cos^2 \theta - 1 \) is inline.

Block

$$\frac{\partial u}{\partial t}
= h^2 \left( \frac{\partial^2 u}{\partial x^2} +
\frac{\partial^2 u}{\partial y^2} +
\frac{\partial^2 u}{\partial z^2}\right)$$

$$\begin{aligned} \dot{x} & = \sigma(y-x) \\ \dot{y} & = \rho x - y - xz \\ \dot{z} & = -\beta z + xy \end{aligned}$$

Tips

调试COM的一个tip

最近遇到朋友的一个程序崩溃,原因是接口没有释放的时候调用了CoUninitialize,接着才释放接口。这个应该是个很明显的问题,但是朋友告诉我以前代码就是这个样子的,没有崩溃过,最近修改了部分代码但并不是这一块的。为了看看究竟什么回事,我把没有崩溃的程序抓了dump,看了COM的初始化引用计数:

1
2
3
4
5
6
7
8
9
10
11
0:000> dt _teb @$teb ReservedForOle
ntdll!_TEB
+0x1758 ReservedForOle : 0x00000000`00271b00 Void
0:000> dt ole32!SOleTlsData 0x00000000`00271b00 cComInits pNativeApt
+0x028 cComInits : 5
+0x080 pNativeApt : 0x00000000`00272680 CComApartment
0:000> dt 0x00000000`00272680 CComApartment _AptKind
ole32!CComApartment
+0x010 _AptKind : 4 ( APTKIND_APARTMENTTHREADED )

没有崩溃的时候,引用计数确实不为0,也能看出是个STA。后来朋友发现,之所以之前没有崩溃,是因为之前线程加载的某个dll中,有初始化COM的调用,所以引用计数不为0。后来移开了这个dll,问题就出现了。

Tips

gotcha sdk 文件监控功能更新

在15年的一篇blog中,我介绍了gotcha sdk。gotcha sdk 全盘文件名搜索开发库

当时gotcha sdk没有提供文件监控功能,也就是说当搜索文件发生变化的时候,这个变化不会体现到搜索结果列表中。其实这个功能一直在todo list中,只不过忙的时候没时间写这部分代码,闲的时候又忘了。前几天终于有时间把这部分代码补上,升级了sdk。

gotcha sdk 代码SVN:
http://code.taobao.org/svn/gotcha_sdk/

NTInternals