OPC DA 客户端 ItemMgt 不正确的功能错误

问题描述

我在 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 无关,只是没有阅读好的文档。