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

VC++ DLL中通过JNI管理多个_com_ptr_t实例引用的问题

问题分析与解决方案

核心问题点

  • JNIEnv*不能作为线程唯一标识:JNIEnv与线程绑定,但同一Java线程在 detach 后重新 attach VM时,JNIEnv*地址可能变化,用它作为Map的Key会导致无法匹配到正确的COM对象。
  • COM线程初始化不完整:每个调用COM接口的线程都必须执行CoInitialize/CoInitializeEx,且线程退出时要调用CoUninitialize。你仅在openConnection中初始化了COM,但其他JNI调用线程可能未初始化,线程退出也未清理COM环境。
  • 全局Map线程不安全:多Java线程并发访问stateMap时,无锁保护会引发竞争条件,导致Map结构损坏或读取到无效指针。
  • 错误的Map访问方式:使用stateMap[env_addresss]时,若Key不存在会自动插入空的ICOMDriverPtr,后续调用必然触发"Invalid pointer"错误。

修正步骤

1. 替换线程标识:用Native线程ID替代JNIEnv*

Windows下用GetCurrentThreadId()获取固定的线程ID,比JNIEnv*更可靠:

#include <Windows.h>
#include <mutex>

// 改用Native线程ID作为Key,添加线程安全锁
std::unordered_map<DWORD, ICOMDriverPtr> stateMap;
std::mutex stateMapMutex;

JNIEXPORT jboolean JNICALL Java_openConnection(JNIEnv *env, jobject jobj){
    DWORD threadId = GetCurrentThreadId();
  
    // 初始化COM,优先使用多线程单元模型
    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if (FAILED(hr) && hr != S_FALSE) { // S_FALSE表示线程已初始化过COM
        // 错误处理
        return JNI_FALSE;
    }

    try
    {
        ICOMDriverPtr spDriver;
        hr = spDriver.CreateInstance(__uuidof(COMDriver));
        if (FAILED(hr)) {
            CoUninitialize();
            return JNI_FALSE;
        }

        // 执行驱动初始化操作...

        // 加锁后修改全局Map
        std::lock_guard<std::mutex> lock(stateMapMutex);
        stateMap[threadId] = spDriver;
    } 
    catch (...) { 
        // 异常处理
        CoUninitialize();
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

2. 正确访问Map:用find而非[]运算符

在其他JNI调用中,先检查Key是否存在,再获取COM指针:

JNIEXPORT void JNICALL Java_sendCommand(JNIEnv *env, jobject jobj){
    DWORD threadId = GetCurrentThreadId();
    ICOMDriverPtr spDriver;

    {
        std::lock_guard<std::mutex> lock(stateMapMutex);
        auto it = stateMap.find(threadId);
        if (it == stateMap.end()) {
            // 未找到对应驱动实例,错误处理
            return;
        }
        spDriver = it->second; // _com_ptr_t赋值自动AddRef,安全可靠
    }

    if (spDriver) {
        // 调用COM驱动接口
        spDriver->SendCommand(...);
    }
}

3. 处理COM线程清理

当Java线程关闭连接时,移除Map中的实例并清理COM环境:

JNIEXPORT void JNICALL Java_closeConnection(JNIEnv *env, jobject jobj){
    DWORD threadId = GetCurrentThreadId();

    {
        std::lock_guard<std::mutex> lock(stateMapMutex);
        stateMap.erase(threadId);
    }

    CoUninitialize();
}

4. 注意COM Apartment模型

如果COMDriver是单线程单元(STA)类型,不能跨线程访问,需确保线程的COM Apartment与对象兼容。若为多线程环境,优先使用COINIT_MULTITHREADED初始化,同时确认驱动COM对象支持多线程访问(可查看驱动文档或注册表中的ThreadingModel项)。

关于_com_ptr_t的说明

你不需要手动调用Detach,_com_ptr_t的赋值运算符会自动执行AddRef,存储在Map中的实例与局部变量实例相互独立,局部变量销毁时自动Release,但Map中的实例仍有效。之前的问题完全是线程标识、线程安全和COM初始化的问题,与_com_ptr_t的赋值逻辑无关。

内容的提问来源于stack exchange,提问作者Ricardo Pedrosa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 15:05:21