SpiderMonkey是Mozilla著名的JS引擎; NSPR是netscape portable runtime. SpiderMonkey默认不是线程安全的, 如果要让他线程安全, 必须依赖NSPR. 关于SpiderMonkey和NSPR的编译, 可以看这里. 接下去的内容仅针对windows环境. 在NSPR编译好后, 会生成动态库和静态库, 静态库以_s结尾, 链接动态库没问题. 链接静态库时, 会有些小麻烦, 或许Mozilla就没打算让用户静态链接NSPR. 让我们做些小修改吧, 首先找到prtypes.h文件, 定位到

#if defined(_NSPR_BUILD_)
#define NSPR_API(__type) PR_EXPORT(__type)
#define NSPR_DATA_API(__type) PR_EXPORT_DATA(__type)
#else
#define NSPR_API(__type) PR_IMPORT(__type)
#define NSPR_DATA_API(__type) PR_IMPORT_DATA(__type)
#endif

将它修改为

#ifdef _NSPR_STATIC_
#define NSPR_API(__type) __type
#define NSPR_DATA_API(__type) __type
#undef PR_EXTERN
#define PR_EXTERN(__type) extern __type
#undef PR_IMPLEMENT
#define PR_IMPLEMENT(__type) __type
#undef PR_EXTERN_DATA
#define PR_EXTERN_DATA(__type) extern __type
#undef PR_IMPLEMENT_DATA
#define PR_IMPLEMENT_DATA(__type) __type
#else
#if defined(_NSPR_BUILD_)
#define NSPR_API(__type) PR_EXPORT(__type)
#define NSPR_DATA_API(__type) PR_EXPORT_DATA(__type)
#else
#define NSPR_API(__type) PR_IMPORT(__type)
#define NSPR_DATA_API(__type) PR_IMPORT_DATA(__type)
#endif
#endif

这里我们引入了一个_NSPRSTATIC的宏, 用来控制生成静态库时的导出标志. 然后找到autoconf.mk文件, 定位到”OS_CFLAGS = …”, 在后面加上-D_NSPRSTATIC. 然后再编译NSPR. 完成后, 我们还得修改下SpiderMonkey中的jslock.h文件, 找到

#include <nspr/pratom.h>
#include <nspr/prlock.h>
#include <nspr/prcvar.h>
#include <nspr/prthread.h>

在这段的前面加上 #define _NSPRSTATIC . 做完以上步骤, 应该可以正常连接NSPR静态库了. 程序应该也能正常运行.

如果真这么简单的话, 也就不值得写篇文章了. 这里存在一个很大的问题, 内存泄漏! NSPR大量使用了线程本地存储(TLS), 在线程结束时, 会做清理工作, NSPR通过DllMain得知线程结束. 当我们把NSPR静态链入我们的程序时, DllMain再也没有机会被调用了, 于是就内存泄漏了. 通过反复创建包含JS_BeginRequest/JS_EndRequest操作的线程, 可以暴露内存泄漏.

下面将利用TLS回调, 完美的解决这个问题. 这里只讲实现, 不讲原理. 对原理感兴趣的可以搜索”PIMAGE_TLS_CALLBACK”获取技术细节.

首先, 找到NSPR中的ntdllmn.c文件, 可以看到里面定义了DllMain函数, 在DllMain函数下方加上一个我们的函数

void NTAPI nsprTlsRoutine(LPVOID hinstDLL, DWORD fdwReason, LPVOID lpvReserved){
  DllMain((HINSTANCE)hinstDLL, fdwReason, lpvReserved);
}

接着再次编译NSPR. 然后在引用SpiderMonkey和NSPR的项目中, 加入以下代码

extern "C" void NTAPI nsprTlsRoutine(LPVOID hinstDLL, DWORD fdwReason, LPVOID lpvReserved);
#pragma comment(linker, "/INCLUDE:__tls_used")
#pragma data_seg(".CRT$XLY")
PIMAGE_TLS_CALLBACK tlsCallback = nsprTlsRoutine;
#pragma data_seg()

为了验证nsprTlsRoutine是否被正常调用, 可以暂时把它替换成一个相同原型的函数, 用来输出trace. 如果你使用VC2005以上版本, 应该没问题, 2005以下版本我没测试过, 不知道是否可行. 最后, 还有一个值得注意的地方, 在以Release模式编译时, tlsCallback变量由于没有被引用而可能被优化掉, 可以在执行路径上加上 void * dummy = tlsCallback; 来强制引用.

补充一下: 看了boost.thread库的中的TLS相关实现(thread_specific_ptr), 发现boost也是类似的方法.