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
相关产品推荐
相关产品推荐

