问题
查找问题
首先想到的解决思路是去查找这个插件有没有问题,这个 Controller 插件是别人开发的,并使用了我开发 的 dal 动态库,由于在 XP 上不能进行调试,所以使用了写日志的方式进行调试,通过日志的跟踪, 发现了问题出在 LoadLibrary 上,加载 dll 失败,这个其实是最初就知道的结果,只是对程序 中的动态加载机制不甚了解,所以这次调试只是熟悉了这个机制,并没有找到问题所在。
为什么是这个 dll 加载失败,而其它的 dll 没有问题呢。 那就写一个测试程序,使用 LoadLibrary 动态加载 Controller 动态库,程序在 XP 上一运行就出错,错误信息是
无法定位程序输入点 GetDynamicTimeZoneInformation 于动态链接库 KERNEL32.dll 上
开始我以为是这个测试程序有问题,就寻找 VS2013 开发 XP 兼容程序的解决方法,在多次尝试 之后,仍无法解决。GetDynamicTimeZoneInformation 这个信息一直没有引起我的注意,因 为我在程序中没有使用过这个函数。在经过一番努力之后,仍然没有解决问题,就转换一下 方法。
从网上得知,使用 process monitor 可以查看 dll 的加载情况,process monitor 是一个实时 进程监控软件,能监控系统中的文件操作和注册表的读写过程。通过监控跟踪,发现 dll 都 进行了加载,并没有发现问题,再换种方法。
使用 depends 查看 Controller 动态库的依赖关系,开始时怀疑是路径有问题,但其它 dll 没有问题,应该不是这个原因。然后仔细检查依赖项, 在依赖树中,发现 Controller 依 赖 dal,dal 又依赖 kernel32,而其中使用的函数就包括 GetDynamicTimeZoneInformation,但这个函数在 XP 下的 kernerl32 中找不到。于是查了一下 MSDN,发现这个函数支持的最低版本是 VISTA, 原来是这个函数在捣鬼,但是 dal 中没有使用 到这个函数,开始时我并没有怀疑 dal 中使用的开源库,因为那些库都是跨平台的。在找遍所有 的代码也没有找到与此相关的东西。那就用最笨的办法,把工程中的文件依次从工程中剔除, 编译后使用 depends 看是否使用了这个函数,到最后,整个工程只剩基本框架了,还是对这个函数有依赖, 此时只使用了 spdlog 这个开源库,那就从这个库中搜搜这个函数,没想到竟然找到了,原来 这个日志库获取时间信息要用到这个函数,而且源码中也加了宏判断,在只有系统版本高 于 VISTA,才使用这个函数,这个函数在 details/os.h 中。
inline int utc_minutes_offset(const std::tm& tm = details::os::localtime()) |
于是在预处理器定义中加上
WINVER=_WIN32_WINNT_WINXP |
编译后的程序在 XP 上不出现找不到插件的错误,而是直接报出异常信息,异常的位置还是出 现在 dal 动态库中。由于无法定位这个异常的位置,没办法在 XP 中安装了 windbg,使用 winbdg 调试,很快定位到内部函数 XXXGetLastError 上,这个函数是用于获取错误信息的, 使用唯一的技术就是 TLS(Thread Local Storage),不过使用的是静态 TLS:
__declspec(thread) DWORD g_error; |
查看 MSDN,发现延迟加载 DLL 的约束中有说明
如果在延迟加载的 DLL 的入口点发生每个进程初始化,则 DLL 的延迟加载可能不会造成进程行为相同。其他情况包括静态 TLS(线程本地存储区),它使用通过 LoadLibrary 加载 DLL 时不处理的 __declspec(thread) 来声明。使用 TlsAlloc、TlsFree、TlsGetValue 和 TlsSetValue 的动态 TLS 仍可在静态或者延迟加载的 DLL 中使用。
原因PRB: Calling LoadLibrary() to Load a DLL That Has Static TLS在中有说明。
解决
解决 GetDynamicTimeZoneInformation 定位问题
于是在预处理器定义中加上
WINVER=_WIN32_WINNT_WINXP |
解决 TLS 问题
使用动态 TLS,即 TlsAlloc、TlsFree、TlsGetValue 和 TlsSetValue,替换静态 TLS。
DWORD g_tls_index; |
总结
程序在 XP 上不能运行的问题实际是由两个问题引起的,这里面牵扯了一些我不清楚的知识, 对于 Windows 下的动态库加载有了进一步理解。在查错排错的过程中一定要冷静和多思 考,这次查错经历就因为鲁莽走了不少弯路,浪费了不少时间。
Render by hexo-renderer-org with Emacs 25.0.50.1 (Org mode 8.2.10)
版权声明