You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.20 10:18:02