From 6fdee2607a9874acd6c64cba68e866dbf774248a Mon Sep 17 00:00:00 2001 From: Ketut Kumajaya Date: Fri, 1 Mar 2024 14:19:29 +0700 Subject: [PATCH 1/5] OPC DA: Switch to OPCClientToolKit with 64bit support Modified version of OPCClientToolKit as FORTE sub project: https://github.com/kumajaya/OPC-Client-X64/tree/master --- src/com/opc/CMakeLists.txt | 13 ++++--- src/com/opc/opcconnectionimpl.cpp | 60 ++++++++++++++++++++----------- src/com/opc/opcconnectionimpl.h | 7 ++-- 3 files changed, 51 insertions(+), 29 deletions(-) diff --git a/src/com/opc/CMakeLists.txt b/src/com/opc/CMakeLists.txt index 31e98364..0fb351f7 100644 --- a/src/com/opc/CMakeLists.txt +++ b/src/com/opc/CMakeLists.txt @@ -1,5 +1,5 @@ #******************************************************************************* -# Copyright (c) 2012, 2021 AIT, ACIN, fortiss GmbH, Hit robot group +# Copyright (c) 2012, 2024 AIT, ACIN, fortiss GmbH, Hit robot group, Samator Indo Gas # This program and the accompanying materials are made available under the # terms of the Eclipse Public License 2.0 which is available at # http://www.eclipse.org/legal/epl-2.0. @@ -9,6 +9,7 @@ # Contributors: # Filip Andren, Alois Zoitl - initial API and implementation and/or initial documentation # Tibalt Zhao - Ease the workload to compile OPC DA +# Ketut Kumajaya - set OPCClientToolKit as an external sub project # *******************************************************************************/ ############################################################################# # OPC Com Layer @@ -31,9 +32,11 @@ if(FORTE_COM_OPC) forte_add_include_directories( ${FORTE_COM_OPC_BOOST_ROOT} ) - forte_add_include_directories( ${FORTE_COM_OPC_LIB_ROOT}/include ) - forte_add_link_directories( ${FORTE_COM_OPC_LIB_ROOT}/lib ) - - forte_add_link_library( OPCClientToolKit.lib ) + forte_add_include_directories( ${FORTE_COM_OPC_LIB_ROOT} ) + if(EXISTS ${FORTE_COM_OPC_LIB_ROOT}/CMakeLists.txt) + add_subdirectory( ${FORTE_COM_OPC_LIB_ROOT} lib/OPCClientToolKit ) + else() + message(SEND_ERROR "FORTE_COM_OPC_LIB_ROOT not set or a compatible OPCClientToolKit does not exist") + endif() endif(FORTE_COM_OPC) endif("${FORTE_ARCHITECTURE}" STREQUAL "Win32") \ No newline at end of file diff --git a/src/com/opc/opcconnectionimpl.cpp b/src/com/opc/opcconnectionimpl.cpp index ced58152..6c272cb4 100644 --- a/src/com/opc/opcconnectionimpl.cpp +++ b/src/com/opc/opcconnectionimpl.cpp @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2022 AIT, ACIN, HIT robot group + * Copyright (c) 2012, 2024 AIT, ACIN, HIT robot group, Samator Indo Gas * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. @@ -9,6 +9,7 @@ * Contributors: * Filip Andren, Alois Zoitl - initial API and implementation and/or initial documentation * Tibalt Zhao - add the list of items instead of add item one by one + * Ketut Kumajaya - switch to OPCClientToolKit with 64bit support *******************************************************************************/ #include "opcconnectionimpl.h" #include "../../arch/devlog.h" @@ -61,11 +62,11 @@ bool COpcConnectionImpl::connect(const char* paGroupName){ COPCClient::init(); - mOpcHost = COPCClient::makeHost(mHost); + mOpcHost = COPCClient::makeHost(COPCHost::LPCSTR2WS(mHost)); - mOpcServer = mOpcHost->connectDAServer(mServerName); + mOpcServer = mOpcHost->connectDAServer(COPCHost::LPCSTR2WS(mServerName)); } catch (OPCException &e){ - DEVLOG_ERROR("connect OPC server failed:%s[%s]\n",(LPCTSTR)(e.reasonString()),paGroupName); + DEVLOG_ERROR("connect OPC server failed:%s[%s]\n",COPCHost::WS2LPCTSTR(e.reasonString()),paGroupName); return false; } DEVLOG_INFO("successfully connect OPC server in COpcConnectionImpl[%s]\n",paGroupName); @@ -100,18 +101,17 @@ void COpcConnectionImpl::addItemList(const char* paGroupName, std::vectoraddItem(itemName, true); - //pa_pNewItem->setIsActive(true); - mOpcItems[itemGroup->getName()].push_back(newItem); + mOpcItems[itemGroup->getName().c_str()].push_back(newItem); } catch (OPCException &e) { - DEVLOG_ERROR("addItem failed with exception:%s[%s:%s]\n", (LPCTSTR)(e.reasonString()), + DEVLOG_ERROR("addItem failed with exception:%s[%s:%s]\n", COPCHost::WS2LPCTSTR(e.reasonString()), groupName,paReadItems[i].c_str()); - if(strcmp((LPCTSTR)(e.reasonString()),"Failed to add item") != 0){ + if(strcmp(COPCHost::WS2LPCTSTR(e.reasonString()),"Failed to add item") != 0){ //pa_pNewItem->setIsActive(false); this->disconnect(); mConnected = false; @@ -201,12 +201,12 @@ int COpcConnectionImpl::sendItemData(const char *paGroupName, const char *paItem if(it != mOpcItems.end()){ lItems = it->second; for(size_t i = 0; i < lItems.size(); i++){ - if(0 == strcmp((LPCTSTR)(lItems[i]->getName()), paItemName)){ + if(0 == strcmp(COPCHost::WS2LPCTSTR(lItems[i]->getName()), paItemName)){ try{ lItems[i]->writeSync(paVar); } catch (OPCException &e){ - DEVLOG_ERROR("opcitem writesync failed with exception:%s[%s:%s]\n", (LPCTSTR)(e.reasonString()), writeGrpName, paItemName); + DEVLOG_ERROR("opcitem writesync failed with exception:%s[%s:%s]\n", COPCHost::WS2LPCTSTR(e.reasonString()), writeGrpName, paItemName); rtn = -1; break; } @@ -224,15 +224,33 @@ int COpcConnectionImpl::sendItemData(const char *paGroupName, const char *paItem return 0; } -void COpcConnectionImpl::OnDataChange(COPCGroup & paGroup, CAtlMap & paChanges){ +void COpcConnectionImpl::OnDataChange(COPCGroup & paGroup, COPCItemDataMap & paChanges){ TItemDataList itemList; for(POSITION pos = paChanges.GetStartPosition(); pos != nullptr;){ - OPCItemData *itemData = paChanges.GetValueAt(pos); - COPCItem *item = paChanges.GetNextKey(pos); - itemList.push_back(new SOpcItemData((LPCTSTR) (item->getName()), (Variant) itemData->vDataValue)); + OPCHANDLE handle = paChanges.GetKeyAt(pos); + OPCItemData *data = paChanges.GetNextValue(pos); + if (data) { + const COPCItem *item = data->item(); + if (item) { + itemList.push_back(new SOpcItemData(COPCHost::WS2LPCTSTR(item->getName()), (Variant) data->vDataValue)); + } + } } - const char *c_groupName = (const char*) paGroup.getName(); + const wchar_t *input = paGroup.getName().c_str(); + // Count required buffer size (plus one for null-terminator). + size_t size = (wcslen(input) + 1) * sizeof(wchar_t); + char *buffer = new char[size]; + #ifdef __STDC_LIB_EXT1__ + // wcstombs_s is only guaranteed to be available if __STDC_LIB_EXT1__ is defined + size_t convertedSize; + std::wcstombs_s(&convertedSize, buffer, size, input, size); + #else + std::wcstombs(buffer, input, size); + #endif + const char *c_groupName = (const char*) buffer; + // Free allocated memory: + delete buffer; int position = 0; const char * subStrRead = strstr(c_groupName, "_read"); @@ -264,12 +282,12 @@ COPCGroup* COpcConnectionImpl::getOpcGroup(const char* paGroupName, bool paIfRea strcpy(groupName, paGroupName); strcat(groupName, "_read"); try{ - (*it)->mOpcGroupRead = retGroup = mOpcServer->makeGroup(groupName, true, (*it)->mReqUpdateRate, (*it)->mRevisedUpdateRate, (*it)->mDeadBand); - (*it)->mOpcGroupRead->enableAsynch(*this); + (*it)->mOpcGroupRead = retGroup = mOpcServer->makeGroup(COPCHost::LPCSTR2WS(groupName), true, (*it)->mReqUpdateRate, (*it)->mRevisedUpdateRate, (*it)->mDeadBand); + (*it)->mOpcGroupRead->enableAsync(this); (*it)->mReadGroupAdded = true; } catch (OPCException &e){ // TODO - DEVLOG_ERROR("exception in make opc group[%s]:%s\n",groupName,(LPCTSTR)(e.reasonString())); + DEVLOG_ERROR("exception in make opc group[%s]:%s\n",groupName,COPCHost::WS2LPCTSTR(e.reasonString())); (*it)->mOpcGroupRead = nullptr; retGroup = nullptr; } @@ -283,11 +301,11 @@ COPCGroup* COpcConnectionImpl::getOpcGroup(const char* paGroupName, bool paIfRea strcpy(groupName, paGroupName); strcat(groupName, "_write"); try{ - (*it)->mOpcGroupWrite = retGroup = mOpcServer->makeGroup(groupName, true, (*it)->mReqUpdateRate, (*it)->mRevisedUpdateRate, (*it)->mDeadBand); + (*it)->mOpcGroupWrite = retGroup = mOpcServer->makeGroup(COPCHost::LPCSTR2WS(groupName), true, (*it)->mReqUpdateRate, (*it)->mRevisedUpdateRate, (*it)->mDeadBand); (*it)->mWriteGroupAdded = true; } catch (OPCException &e){ // TODO - DEVLOG_ERROR("exception in make opc group[%s]:%s\n",groupName,(LPCTSTR)(e.reasonString())); + DEVLOG_ERROR("exception in make opc group[%s]:%s\n",groupName,COPCHost::WS2LPCTSTR(e.reasonString())); (*it)->mOpcGroupWrite = nullptr; (*it)->mOpcGroupWrite = nullptr; retGroup = nullptr; diff --git a/src/com/opc/opcconnectionimpl.h b/src/com/opc/opcconnectionimpl.h index 4bce8e1e..b67f8656 100644 --- a/src/com/opc/opcconnectionimpl.h +++ b/src/com/opc/opcconnectionimpl.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2022 AIT, fortiss GmbH, HIT robot group + * Copyright (c) 2012, 2024 AIT, fortiss GmbH, HIT robot group, Samator Indo Gas * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. @@ -9,6 +9,7 @@ * Contributors: * Filip Andren, Alois Zoitl - initial API and implementation and/or initial documentation * Tibalt Zhao - add the list of items instead of add item one by one + * Ketut Kumajaya - switch to OPCClientToolKit with 64bit support *******************************************************************************/ #ifndef OPCCONNECTIONIMPL_H_ #define OPCCONNECTIONIMPL_H_ @@ -23,7 +24,7 @@ class COpcConnection; -class COpcConnectionImpl : public IAsynchDataCallback{ +class COpcConnectionImpl : public IAsyncDataCallback{ public: COpcConnectionImpl(const char *paHost, const char *paServerName, COpcConnection* paOpcConn); ~COpcConnectionImpl(); @@ -47,7 +48,7 @@ class COpcConnectionImpl : public IAsynchDataCallback{ bool isConnected(); - virtual void COpcConnectionImpl::OnDataChange(COPCGroup &paGroup, CAtlMap &paChanges); + virtual void COpcConnectionImpl::OnDataChange(COPCGroup &paGroup, COPCItemDataMap &paChanges); private: From 6ba4ee4c52d6320ac7f784b1dc91327221048b20 Mon Sep 17 00:00:00 2001 From: Ketut Kumajaya Date: Sat, 2 Mar 2024 21:12:08 +0700 Subject: [PATCH 2/5] OPC DA: Update installation instructions --- src/com/opc/readme.txt | 42 ++++-------------------------------------- 1 file changed, 4 insertions(+), 38 deletions(-) diff --git a/src/com/opc/readme.txt b/src/com/opc/readme.txt index 5d73fdef..2a2b7f9c 100644 --- a/src/com/opc/readme.txt +++ b/src/com/opc/readme.txt @@ -1,45 +1,11 @@ Installation Instructions The OPC com layer requires the following packages - - OPC Client library release 0.4 (http://sourceforge.net/projects/opcclient/) + - OPC Client library (https://github.com/kumajaya/OPC-Client-X64.git) - Boost Lexical Cast (http://www.boost.org) -Before OPC Client is compiled the function init() in OPCClient.cpp must be changed from: -void COPCClient::init() -{ - HRESULT result = CoInitialize(NULL); - if (FAILED(result)) - { - throw OPCException("CoInitialize failed"); - } - - CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL); - - result = CoGetMalloc(MEMCTX_TASK, &iMalloc); - if (FAILED(result)) - { - throw OPCException("CoGetMalloc failed"); - } -} - -to: -void COPCClient::init() -{ - CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL); - - HRESULT result = CoGetMalloc(MEMCTX_TASK, &iMalloc); - if (FAILED(result)) - { - throw OPCException("CoGetMalloc failed"); - } -} - - -Once the OPC Client library is compiled performe the steps below: -1. Place OPC Client library in the following folder structure: - /include - all headers should be placed here - /lib - OPCClientToolkit.lib -2. Choose for FORTE_COM_OPC_LIB_ROOT in CMake -3. Choose Boost root folder for FORTE_COM_OPC_BOOST_ROOT +Once the two libraries above are available, performe the steps below: +1. Choose /OPCClientToolKit for FORTE_COM_OPC_LIB_ROOT in CMake +2. Choose Boost root folder for FORTE_COM_OPC_BOOST_ROOT (the lexical_cast.hpp header must be available in /boost) Parameter Documentation (all values are required) From 2814412c6820b78ee9178bc9be2dc42bc406f2a6 Mon Sep 17 00:00:00 2001 From: Ketut Kumajaya Date: Sun, 3 Mar 2024 15:57:43 +0700 Subject: [PATCH 3/5] OPC DA: Dynamically link OPCClientToolKit to comply LGPL license --- src/com/opc/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/com/opc/CMakeLists.txt b/src/com/opc/CMakeLists.txt index 0fb351f7..8fa8fd65 100644 --- a/src/com/opc/CMakeLists.txt +++ b/src/com/opc/CMakeLists.txt @@ -34,9 +34,12 @@ if(FORTE_COM_OPC) forte_add_include_directories( ${FORTE_COM_OPC_LIB_ROOT} ) if(EXISTS ${FORTE_COM_OPC_LIB_ROOT}/CMakeLists.txt) - add_subdirectory( ${FORTE_COM_OPC_LIB_ROOT} lib/OPCClientToolKit ) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + add_subdirectory( ${FORTE_COM_OPC_LIB_ROOT} OPCClientToolKit ) else() message(SEND_ERROR "FORTE_COM_OPC_LIB_ROOT not set or a compatible OPCClientToolKit does not exist") endif() + forte_add_link_library( OPCClientToolKit ) + install(TARGETS OPCClientToolKit RUNTIME DESTINATION bin) endif(FORTE_COM_OPC) endif("${FORTE_ARCHITECTURE}" STREQUAL "Win32") \ No newline at end of file From db4b8d99db965c950782a242b594bfe1bfa45b4a Mon Sep 17 00:00:00 2001 From: Ketut Kumajaya Date: Sun, 3 Mar 2024 23:13:40 +0700 Subject: [PATCH 4/5] OPC DA: Parse OPC data item with lower overhead Upstream WS2LPCTSTR is buggy because on the initial 64bit port I can write but cannot read the OPC data values until I fix it. This local WS2LPCTSTR method is safe as it will always allocate the exact ammount of space needed for the conversion, resulting parsing OPC data item faster. --- src/com/opc/opcconnectionimpl.cpp | 37 +++++++++---------------------- src/com/opc/opcconnectionimpl.h | 18 +++++++++++++++ 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/com/opc/opcconnectionimpl.cpp b/src/com/opc/opcconnectionimpl.cpp index 6c272cb4..61c13817 100644 --- a/src/com/opc/opcconnectionimpl.cpp +++ b/src/com/opc/opcconnectionimpl.cpp @@ -66,7 +66,7 @@ bool COpcConnectionImpl::connect(const char* paGroupName){ mOpcServer = mOpcHost->connectDAServer(COPCHost::LPCSTR2WS(mServerName)); } catch (OPCException &e){ - DEVLOG_ERROR("connect OPC server failed:%s[%s]\n",COPCHost::WS2LPCTSTR(e.reasonString()),paGroupName); + DEVLOG_ERROR("connect OPC server failed:%s[%s]\n",WS2LPCTSTR(e.reasonString()),paGroupName); return false; } DEVLOG_INFO("successfully connect OPC server in COpcConnectionImpl[%s]\n",paGroupName); @@ -109,9 +109,9 @@ void COpcConnectionImpl::addItemList(const char* paGroupName, std::vectorsetIsActive(false); this->disconnect(); mConnected = false; @@ -201,12 +201,12 @@ int COpcConnectionImpl::sendItemData(const char *paGroupName, const char *paItem if(it != mOpcItems.end()){ lItems = it->second; for(size_t i = 0; i < lItems.size(); i++){ - if(0 == strcmp(COPCHost::WS2LPCTSTR(lItems[i]->getName()), paItemName)){ + if(0 == strcmp(WS2LPCTSTR(lItems[i]->getName()), paItemName)){ try{ lItems[i]->writeSync(paVar); } catch (OPCException &e){ - DEVLOG_ERROR("opcitem writesync failed with exception:%s[%s:%s]\n", COPCHost::WS2LPCTSTR(e.reasonString()), writeGrpName, paItemName); + DEVLOG_ERROR("opcitem writesync failed with exception:%s[%s:%s]\n", WS2LPCTSTR(e.reasonString()), writeGrpName, paItemName); rtn = -1; break; } @@ -226,31 +226,16 @@ int COpcConnectionImpl::sendItemData(const char *paGroupName, const char *paItem void COpcConnectionImpl::OnDataChange(COPCGroup & paGroup, COPCItemDataMap & paChanges){ TItemDataList itemList; - for(POSITION pos = paChanges.GetStartPosition(); pos != nullptr;){ - OPCHANDLE handle = paChanges.GetKeyAt(pos); + POSITION pos = paChanges.GetStartPosition(); + while(pos){ OPCItemData *data = paChanges.GetNextValue(pos); if (data) { const COPCItem *item = data->item(); - if (item) { - itemList.push_back(new SOpcItemData(COPCHost::WS2LPCTSTR(item->getName()), (Variant) data->vDataValue)); - } + itemList.push_back(new SOpcItemData(WS2LPCTSTR(item->getName()), (Variant) data->vDataValue)); } } - const wchar_t *input = paGroup.getName().c_str(); - // Count required buffer size (plus one for null-terminator). - size_t size = (wcslen(input) + 1) * sizeof(wchar_t); - char *buffer = new char[size]; - #ifdef __STDC_LIB_EXT1__ - // wcstombs_s is only guaranteed to be available if __STDC_LIB_EXT1__ is defined - size_t convertedSize; - std::wcstombs_s(&convertedSize, buffer, size, input, size); - #else - std::wcstombs(buffer, input, size); - #endif - const char *c_groupName = (const char*) buffer; - // Free allocated memory: - delete buffer; + const char *c_groupName = WS2LPCTSTR(paGroup.getName()); int position = 0; const char * subStrRead = strstr(c_groupName, "_read"); @@ -287,7 +272,7 @@ COPCGroup* COpcConnectionImpl::getOpcGroup(const char* paGroupName, bool paIfRea (*it)->mReadGroupAdded = true; } catch (OPCException &e){ // TODO - DEVLOG_ERROR("exception in make opc group[%s]:%s\n",groupName,COPCHost::WS2LPCTSTR(e.reasonString())); + DEVLOG_ERROR("exception in make opc group[%s]:%s\n",groupName,WS2LPCTSTR(e.reasonString())); (*it)->mOpcGroupRead = nullptr; retGroup = nullptr; } @@ -305,7 +290,7 @@ COPCGroup* COpcConnectionImpl::getOpcGroup(const char* paGroupName, bool paIfRea (*it)->mWriteGroupAdded = true; } catch (OPCException &e){ // TODO - DEVLOG_ERROR("exception in make opc group[%s]:%s\n",groupName,COPCHost::WS2LPCTSTR(e.reasonString())); + DEVLOG_ERROR("exception in make opc group[%s]:%s\n",groupName,WS2LPCTSTR(e.reasonString())); (*it)->mOpcGroupWrite = nullptr; (*it)->mOpcGroupWrite = nullptr; retGroup = nullptr; diff --git a/src/com/opc/opcconnectionimpl.h b/src/com/opc/opcconnectionimpl.h index b67f8656..70160630 100644 --- a/src/com/opc/opcconnectionimpl.h +++ b/src/com/opc/opcconnectionimpl.h @@ -52,6 +52,24 @@ class COpcConnectionImpl : public IAsyncDataCallback{ private: + static const char* WS2LPCTSTR(const std::wstring &wstr) { + const wchar_t *input = wstr.c_str(); + // Count required buffer size (plus one for null-terminator). + size_t size = (wcslen(input) + 1) * sizeof(wchar_t); + char *buffer = new char[size]; + #ifdef __STDC_LIB_EXT1__ + // wcstombs_s is only guaranteed to be available if __STDC_LIB_EXT1__ is defined + size_t convertedSize; + std::wcstombs_s(&convertedSize, buffer, size, input, size); + #else + std::wcstombs(buffer, input, size); + #endif + const char *ret = (const char*) buffer; + // Free allocated memory: + delete buffer; + return ret; + } + COPCGroup* getOpcGroup(const char* paGroupName, bool paIfRead); void clearGroup(); From bb53e7da5b866889462cc2e57b1c5943980593f5 Mon Sep 17 00:00:00 2001 From: Ketut Kumajaya Date: Tue, 5 Mar 2024 23:47:45 +0700 Subject: [PATCH 5/5] OPC DA: Completely disconnect from the server when disconnecting --- src/com/opc/opcconnectionimpl.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/com/opc/opcconnectionimpl.cpp b/src/com/opc/opcconnectionimpl.cpp index 61c13817..1c0450e3 100644 --- a/src/com/opc/opcconnectionimpl.cpp +++ b/src/com/opc/opcconnectionimpl.cpp @@ -43,6 +43,16 @@ void COpcConnectionImpl::disconnect(){//const char* paGroupName){ COPCClient::stop(); mConnected = false; this->clearGroup(); + + if (mOpcHost) { + delete mOpcHost; + mOpcHost = nullptr; + } + + if (mOpcServer) { + delete mOpcServer; + mOpcServer = nullptr; + } } }