问题描述
我在 Qt5 中为 C++ COM API 创建了一个简单的 OPC DA 客户端。 客户端连接到远程服务器,获取 OPCServer 指针,使用 Itemmgt 接口创建新的 OPC 组,但在我尝试向组中添加项目时失败。
错误信息是:功能不正确。 就我所见,IUnkNown:: QueryInterface 适用于此 pItemmgt,但 ValidateItems、CreateEnumerator 和 AddItems 调用导致相同的 Incorrect 函数错误。 OPC 服务器是 QMS220Simulator (Quadera)。 知道可能是什么问题吗? 这是我第一次尝试编写 DCOM 客户端,这段代码可能有很多很多问题。 qms220.h 文件包含 QMS220Simulator 的 CLSID。 重现问题的最短代码是这样的:
#include "opcda.h"
#include "qms220.h"
#include <QApplication>
#include <QDebug>
#include <comdef.h>
static void showStatus(const QString &message,HRESULT code);
IOPCServer *pOPCServer = nullptr;
IOPCItemmgt *pItemmgt = nullptr;
OPCHANDLE serverGroupHandle;
bool initializeCOM()
{
HRESULT hr = CoInitializeEx(nullptr,COINIT_APARTMENTTHREADED);
if (Failed(hr)) {
showStatus("COM initialization Failed!",hr);
return false;
}
hr = CoInitializeSecurity(
NULL,//security descriptor
-1,//COM authentication
NULL,//authentication services
NULL,//reserved
RPC_C_AUTHN_LEVEL_DEFAULT,//default authentication
RPC_C_IMP_LEVEL_IMPERSONATE,//default impersonation
NULL,//authentication info
EOAC_NONE,//additional capabilities
NULL //reserved
);
if (hr == RPC_E_TOO_LATE) {
showStatus("RPC initalization is too late,ignoring...",hr);
} else {
if (Failed(hr)) {
showStatus("CoInitializeSecurity",hr);
return false;
}
}
return true;
}
void deinitializeCOM()
{
CoUninitialize();
}
static const int INTERFACE_COUNT = 1;
bool connectToServer(const QString &address)
{
_bstr_t serverName = address.toStdString().c_str();
COSERVERINFO cs;
memset(&cs,sizeof(cs));
cs.pwszName = serverName;
MULTI_QI qi[INTERFACE_COUNT];
memset(qi,sizeof(qi));
qi[0].pIID = &IID_IOPCServer;
HRESULT hr = CoCreateInstanceEx(
CLSID_QMG220SIMDA,NULL,CLSCTX_SERVER,&cs,INTERFACE_COUNT,qi
);
if (Failed(hr)) {
showStatus("CoCreateInstanceEx",hr);
return false;
}
pOPCServer = (IOPCServer*)(qi[0].pItf);
return true;
}
void disconnectFromServer()
{
if (pOPCServer != nullptr) {
pOPCServer->Release();
pOPCServer = nullptr;
}
}
void showOPCStatus(const QString &message,HRESULT hr)
{
if (pOPCServer != nullptr) {
LPWSTR buffer = nullptr;
HRESULT hr2 = pOPCServer->GetErrorString(hr,LOCALE_SYstem_DEFAULT,&buffer);
if (hr2 != S_OK) {
qDebug() << message << QString(": HRESULT: 0x%1").arg(hr,8,16,QChar('0'));
} else {
qDebug() << message << QString(": ") << QString::fromWChararray(buffer);
CoTaskMemFree(buffer);
}
} else {
qDebug() << message << QString(": HRESULT: 0x%1").arg(hr,QChar('0'));
}
}
static const LPCWSTR MIDGROUPNAME = L"mid";
bool createMIDGroup()
{
if (pOPCServer == nullptr) return false;
OPCHANDLE clientGroupHandle = 1;
DWORD revisedUpdaterate;
HRESULT hr = pOPCServer->AddGroup(
MIDGROUPNAME,FALSE,//active
0,// requestedUpdaterate
clientGroupHandle,//timebias
NULL,//percentDeadBand,//lcid
&serverGroupHandle,&revisedUpdaterate,IID_IOPCItemmgt,(LPUNKNowN *)(&pItemmgt)
);
showOPCStatus("OPCServer::AddGroup",hr);
if (hr != S_OK) return false;
qDebug() << "The server group handle is: " << QString("0x%1").arg(serverGroupHandle,4,16);
qDebug() << "The revised update rate is: " << revisedUpdaterate;
#define ITEM_ID L"Hardware.Modules.Analyser.SI220.SimulationMode"
QString accesspath("");
QString itemId("Hardware.Modules.Analyser.SI220.SimulationMode");
wchar_t accesspathBuffer[1024];
wchar_t itemIdBuffer[1024];
accesspath.toWChararray(accesspathBuffer);
itemId.toWChararray(itemIdBuffer);
static const int ITEM_COUNT = 1;
OPCITEMDEF ItemArray[ITEM_COUNT] =
{{
/*szAccesspath*/ accesspathBuffer,/*szItemID*/ itemIdBuffer,/*bActive*/ FALSE,/*hClient*/ 1,/*dwBlobSize*/ 0,/*pBlob*/ NULL,/*vtRequestedDataType*/ VT_UI1,/*wReserved*/0
}};
OPCITEMRESULT *itemResults = nullptr;
HRESULT *errors = nullptr;
hr = pItemmgt->AddItems(ITEM_COUNT,ItemArray,&itemResults,&errors);
bool Failed = false;
if (hr != S_OK) {
Failed = true;
}
showOPCStatus("createMidGroup/AddItems ",hr);
for(DWORD k=0;k<ITEM_COUNT;k++) {
showOPCStatus(QString("createMidGroup/AddItems[%1]").arg(k),errors[k]);
if (errors[k] != S_OK) {
Failed = true;
}
CoTaskMemFree(itemResults[k].pBlob);
}
CoTaskMemFree(itemResults);
CoTaskMemFree(errors);
return !Failed;
}
void removeMIDGroup()
{
if (pOPCServer != nullptr) {
if (pItemmgt != nullptr) {
pItemmgt->Release();
pItemmgt = nullptr;
}
HRESULT hr = pOPCServer->RemoveGroup(serverGroupHandle,false);
if (hr != S_OK) {
showStatus("deleteMIDGroup",hr);
}
}
}
int main(int argc,char *argv[])
{
Q_UNUSED(argc)
Q_UNUSED(argv)
if (!initializeCOM()) return -1;
if (connectToServer(QString("192.168.12.106"))) {
if (createMIDGroup()) {
removeMIDGroup();
}
disconnectFromServer();
}
deinitializeCOM();
return 0;
}
static void showStatus(const QString &message,HRESULT code)
{
_com_error error(code);
qDebug() << message + QString(": " ) + QString::fromWChararray(error.ErrorMessage());
}
解决方法
所以,根据 Qt 文档:https://doc.qt.io/qt-5/qstring.html#toWCharArray toWCharArray 创建一个 NOT NULL CHAR TERMINATED unicode 字符串。 所以更好的用法是: int size = itemId.toWCharArray(itemIdBuffer); itemIdBuffer[size] = L'\0'; accessPathBuffer 也是如此。 将未终止的组名发送到 OPC 服务器可能不是一个好主意。 好消息是,CreateGroupEnumerator 发送回与接收到的相同的未终止组名称。 所以问题与 COM 或 OPC 无关,只是没有阅读好的文档。