又是权限问题

上周五发布了Beta版之后, 老板觉得这次加的功能挺多就试用了一下, 结果瞬间就崩了, 上去一顿猛批啊。 请他们试用了很久才发现又是因为权限问题。

在%ProgramFiles%下普通用户只有读取和执行的权限, 由于历史原因, 我们Client有一部分DLL是从其他地方copy到client.exe的当前目录下来动态加载的, 而这一版本恰好将管理员权限去掉了(因为Win7及以上版本在管理员权限不能访问网络共享路径), 两个巧合就碰撞到一起导致了这个bug的产生, 当然其实有些代码不是很规范, 不然应该只是加载某个DLL失败而已。

将所有需要动态释放的文件放到了一个与用户相关的目录, 使用DLL的绝对路径去LoadLibrary, 一开始没有任何问题, 直到完全卸载之后安装时就开始发现有些DLL加载不成功, 明明路径存在, LoadLibrary就是会fail。 在MSDN上找到这篇文章之后才找到问题所在, 使用绝对路径去LoadLibrary时, 被使用绝对路径去LoadLibrary的DLL如果静态依赖于另一个DLL, 那么这个被依赖的DLL就会被按照一定的目录顺序搜索, 不幸的是我们释放的DLL所在的路径恰好不在Windows的搜索中径范围之内。 使用MSDN推荐LoadLibrary(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH)将使用绝对路径加载的DLL所在的目录加入搜索范围问题即可解决。

又一次踩到权限的坑, 以后写代码一定要注意如非有必要尽量不使用root权限, 这样就不会碰到这种中途取消管理员权限之后产生的各种权限及附带引发的其他问题。

多线程调DLL

最近写代码一不小心又着了多线程的道, 背景如下:
前不久写了这样一个DLL:

const wchar_t *a = L"xxxx";
const wchar_t *b = L"xxxx";

int do_something_a(struct axx *param_a)
{
...
}
int do_something_b(struct bxx *param_b)
{
...
}


在do_something_a与do_something_b中分别用到了字符串a, b.本来这样相安无事, 可是很多地方会用到这个DLL的代码, 但是字符串a, b并不一样, 而字符中a, b可以根据param_a, param_b中的信息来生成, 本着代码正交性的原则, 将DLL重构如下:

wchar_t a[..];
wchar_t b[..];

int do_something_a(struct axx *param_a)
{
gen_a(param_a, a);
...
}
int do_something_b(struct bxx *param_b)
{
gen_b(param_b, b);
...
}

这样咋一看是没什么问题, 代码简洁了, 程序完美了. 可是我忽略了两个问题, 一个进程中不管调LoadLibrary多少次, DLL只会被加载一次. 而我这个DLL是会在多个线程同时加载使用的.
这样一来, 问题就来了, 由于全局数组a与b的存在, 所有的函数都不是线程安全的, 在低并发量的线程中冲突并不严重, 所以问题很难发现, 但是在高并发的线程中两个函数就会大量执行失败.

找到问题了后将代码重构如下:

int do_something_a(struct axx *param_a)
{
wchar_t a[..];
gen_a(param_a, a);
...
}
int do_something_b(struct bxx *param_b)
{
wchar_t b[..];
gen_b(param_b, b);
...
}

这次bug再次给我提了醒, 多线程代码要处处小心, 一不小心就会掉坑里.

关于用DLL接中使用std::vector之后出现的问题

最近在代码中用了这样一个DLL,采用静态加载方式使用,原型类似如下:

XXX_API  int xx_func(std::vector<struct xx> &xx_tbl, ..., ...);
//代码中会用xx_tbl.push_back(xx);之类的代码向xx_tbl里面填充数据

但是却出现一个奇葩问题,每当调用这个DLL的程序退出时Debug版本有很大概率会崩溃在这个std::vector<struct xx>的析构函数上。

研究了好久才发现,当DLL中调用push_back函数时,其实std::vector<struct xx>的构造函数分配的内存是属于这个DLL的资源,当程序退出时会首先卸载这个DLL程序,那么与他相关的内存也随之被释放。

当主程序最后退出时,就会引发xx_tbl的析构函数,但是由于xx_tbl中的某些元素的内存是在DLL中分配的,而且已经被释放了,那么这些内存在被析构函数释放时就会引出错误,Debug版的代码是有内存检查的,因此每次Debug代码退出时就会崩溃。

因此,对于DLL中尽量采用纯C的结构,不要使用对象。

————————————————————————————————–
后来找到原因,因为动态链接库使用的CRT库是静态链接的,与Exe程序使用的是两个不同的CRT,因此在DLL中向vector中push数据之后会,会调用DLL链接的CRT进行分配内存,如果在Exe中使用了被DLL操作过的vector变量时,就会导致vector变量进行resize,而由于Exe与DLL使用的是两个CRT库,这时就会造成信息错乱,引起程序崩溃,只要将所有DLL设成使用动态加载CRT即可解决此问题。