Kotlin Native中调用CreateThread创建线程未执行目标函数且GetLastError返回0的问题
Kotlin Native中调用CreateThread创建线程未执行目标函数且GetLastError返回0的问题
看起来你踩了Windows DLL开发里一个经典的坑!别慌,咱们来一步步搞清楚问题出在哪,怎么解决。
首先,你的代码里CreateThread返回了有效的句柄,GetLastError也返回0,但线程函数DataCollectorEntry完全没执行——核心原因是:Windows系统严格禁止在DllMain中创建或启动线程!
为什么会这样?
当DLL被加载触发DLL_PROCESS_ATTACH时,系统会持有一个进程级别的「加载锁(Loader Lock)」,DllMain就是在这个锁的保护下运行的。如果你在DllMain里创建新线程,这个新线程会试图获取加载锁才能继续执行(因为它可能需要加载依赖的DLL资源),但此时加载锁已经被当前执行DllMain的线程攥在手里了——这就形成了死锁:新线程挂起等锁,当前线程卡在DllMain里没释放锁,结果就是你的线程函数永远没机会跑起来。
怎么解决?
最稳妥的方案是把线程创建逻辑移出DllMain,用外部触发或者延迟初始化的方式启动线程,这里给你两种可行的实现:
方案1:导出初始化函数让宿主主动调用
新增一个导出函数,让加载DLL的宿主进程在加载完成后主动调用它,在这个函数里创建线程:
@OptIn(ExperimentalNativeApi::class, ExperimentalForeignApi::class) @CName("InitDataCollector") fun InitDataCollector(): Boolean { val threadFunc = staticCFunction(::DataCollectorEntry) functionThread = CreateThread(null, 0u, threadFunc, NULL, 0u, null) if (functionThread == null) { OutputDebugStringA("Failed to create thread, error: ${GetLastError()}") return false } OutputDebugStringA("Thread created successfully, handle: ${functionThread?.toLong()}") return true }
然后修改DllMain,只做基础的模块句柄记录,不碰线程创建:
@OptIn(ExperimentalNativeApi::class, ExperimentalForeignApi::class) @CName("DllMain") fun DllMain(hModule: HMODULE, ul_reason_for_call: DWORD, lpReserved: LPVOID): Boolean { when (ul_reason_for_call.toInt()) { DLL_PROCESS_ATTACH -> { instanceHandleModule = hModule OutputDebugStringA("DllMain Attached") // 这里只做最基础的初始化,不创建线程 } DLL_PROCESS_DETACH -> { // 记得关闭线程句柄,避免资源泄漏 functionThread?.let { CloseHandle(it) functionThread = null } OutputDebugStringA("DllMain Detached") } } return true }
方案2:用APC队列延迟执行(可选)
如果必须要DLL加载后自动启动线程,可以用QueueUserAPC把线程创建任务放到APC队列,等DllMain执行完毕、加载锁释放后再执行。不过这个方法相对复杂,不如方案1直观可靠,这里给个简化示例:
// 先定义一个APC回调函数 @OptIn(ExperimentalNativeApi::class, ExperimentalForeignApi::class) fun ThreadInitApc(lpParameter: ULONG_PTR) { val threadFunc = staticCFunction(::DataCollectorEntry) functionThread = CreateThread(null, 0u, threadFunc, NULL, 0u, null) } @OptIn(ExperimentalNativeApi::class, ExperimentalForeignApi::class) @CName("DllMain") fun DllMain(hModule: HMODULE, ul_reason_for_call: DWORD, lpReserved: LPVOID): Boolean { when (ul_reason_for_call.toInt()) { DLL_PROCESS_ATTACH -> { instanceHandleModule = hModule OutputDebugStringA("DllMain Attached") // 将APC任务放到当前线程的队列,等DllMain执行完后触发 QueueUserAPC(staticCFunction(::ThreadInitApc), GetCurrentThread(), 0u) } // ... 其他分支不变 } return true }
额外注意事项
- 永远记住:
DllMain是个受限环境,除了不能创建线程,还不能调用LoadLibrary、CreateProcess这类可能触发DLL加载/卸载的API,否则都可能导致死锁或异常。 - 一定要在
DLL_PROCESS_DETACH中关闭线程句柄,避免资源泄漏。 - 你的
globalValueInternal用了@ThreadLocal,这个是正确的,能保证线程间的变量隔离,继续保持就好。
备注:内容来源于stack exchange,提问作者Ahmed Jihad
相关产品推荐
相关产品推荐

