平心而论,我们从样例服务器的代码可以看出,利用LightOPC库开发OPC服务器还是比较啰嗦的,网上有人提出opc workshop库就简单很多,我千辛万苦终于找到一个05年版本的workshop库源码,忘了出处是在哪里了,依稀记得是Codeforge网站。相较于LightOPC,用这个库开发OPC服务器确实简单了很多,其对核心业务逻辑做了高度封装,使得服务器的开发流程非常清晰,这一点值得赞扬。但遗憾的是,完美的事情在这个世界上根本就不存在,经过实测,我手头上拥有的版本存在三个严重问题:

1、利用该库开发的OPC服务器无法由OPC客户端远程启动;

2、通过标准接口ValidateItems()无法获取指定变量的数据类型;

3、提供的样例服务器主处理逻辑存在重复注册的BUG,没有把服务器注册和处理逻辑分开;

好在已经有了LightOPC这碗酒垫底,这几个问题都不是问题。我的方法简单粗暴——直接上手改源码。对于第一个问题,通过分析源码发现,导致该问题的原因是注册函数在获取模块文件工作路径时,接收缓冲区的首地址错误导致的:

 int COPCServerObject::RegisterServer()
{
char np[FILENAME_MAX + 32];
printf("Registering");
GetModuleFileName(NULL, np + 1, sizeof(np) - 8); return ServerRegister(&CLSID_OPCServerEXE,
OPCServerProgID,
"OPCServer (c) Alexey Obukhov", np, 0);
}

出问题的这个注册函数在OPCServerObject.cpp文件中,不知道是什么原因让作者在获取进程工作路径时将缓冲区首地址后移了一个字节,即:

 GetModuleFileName(NULL, np + 1, sizeof(np) - 8);

至今我没参透为何要“np + 1”。事实证明,把后面加的那个“”去掉后,服后务器不仅可以远程启动了且工作也完全正常。看来这件事需要作者本人亲自解释这到底是为什么了,咱们只要能用就行了。

第2个问题更加匪夷所思,作者提供的“ValidateItems()”接口函数竟然缺少了关键的对变量类型的赋值语句:

     STDMETHOD(ValidateItems)( /*[in]*/ DWORD dwCount,
/*[in, size_is(dwCount)]*/ OPCITEMDEF * pItemArray,
/*[in]*/ BOOL bBlobUpdate,
/*[out, size_is(,dwCount)]*/ OPCITEMRESULT ** ppValidationResults,
/*[out, size_is(,dwCount)]*/ HRESULT ** ppErrors )
{
DWORD i;
HRESULT res = S_OK;
OPC_GROUP_CHECK_DELETED(); VALIDATE_ARGUMENT(pItemArray);
VALIDATE_ARGUMENT(ppValidationResults);
VALIDATE_ARGUMENT(ppErrors); *ppValidationResults = allocate_buffer<OPCITEMRESULT> ( dwCount );
*ppErrors = allocate_buffer<HRESULT> ( dwCount ); // TODO
for( i=0;i<dwCount; ++i) {
OPCHANDLE hServer = g_NameIndex[ CString(pItemArray[i].szItemID) ];
CBrowseItemsList::iterator browseIT = g_BrowseItems.find( hServer );
if( browseIT == g_BrowseItems.end() ) {
(*ppErrors)[i] = OPC_E_UNKNOWNITEMID;
res = S_FALSE;
}
}
// TODO return res;
}

上述函数在IOPCItemMgtImpl.h源文件中可以找到。其中入口参数“ppValidationResults”即被用于获取指定变量的相关信息。但奇怪的是,在这个函数里作者只是对这个变量分配了一块内存,接下来的代码并没有对其赋值。如果说我到手的源码并不完整的话,那么为何解决上述几个问题后,OPC服务器竟然工作正常,没有任何问题?要不说这个问题很是匪夷所思呢。既然咱们有源码,这个事完全可以自己解决,在这个函数增加几行代码:

     STDMETHOD(ValidateItems)( /*[in]*/ DWORD dwCount,
/*[in, size_is(dwCount)]*/ OPCITEMDEF * pItemArray,
/*[in]*/ BOOL bBlobUpdate,
/*[out, size_is(,dwCount)]*/ OPCITEMRESULT ** ppValidationResults,
/*[out, size_is(,dwCount)]*/ HRESULT ** ppErrors )
{
DWORD i;
HRESULT res = S_OK;
OPC_GROUP_CHECK_DELETED(); VALIDATE_ARGUMENT(pItemArray);
VALIDATE_ARGUMENT(ppValidationResults);
VALIDATE_ARGUMENT(ppErrors); *ppValidationResults = allocate_buffer<OPCITEMRESULT> ( dwCount );
*ppErrors = allocate_buffer<HRESULT> ( dwCount ); /// TODO
for( i=0;i<dwCount; ++i) {
OPCHANDLE hServer = g_NameIndex[ CString(pItemArray[i].szItemID) ];
CBrowseItemsList::iterator browseIT = g_BrowseItems.find( hServer );
if( browseIT == g_BrowseItems.end() ) {
(*ppErrors)[i] = OPC_E_UNKNOWNITEMID;
res = S_FALSE;
}
else
{
(*ppValidationResults)->vtCanonicalDataType = browseIT->type;
break;
}
}
// TODO return res;
}

连花括号都算着其实就增加了4行代码。只是对参数“ppValidationResults”的数据类型成员“vtCanonicalDataType”进行了赋值。如此一来,“ValidateItems()”接口即可满足我们的要求了。

第3个问题就简单多了,直接修改样例服务器的“main()”函数把注册和主处理逻辑分开就可以了:

 int _tmain(int argc, _TCHAR* argv[])
{
FILE *pfFile; AllocConsole();
freopen_s(&pfFile,"conout$","w+",stdout); //打䨰开a控?制?台¬¡§ if(argc > 2)
{
printf("Usage:%s", argv[0]);
printf(" %s /r", argv[0]);
printf(" %s /u", argv[0]);
printf(" : start opc server\r\n");
printf("/r: regist opc server\r\n");
printf("/u: unregist opc server\r\n"); fclose(pfFile);
FreeConsole(); return -1;
} char str[1024] = {0}; HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); // define server object
COPCServerObject server;
// define data event receiver
dataReceiver receiver; // set server name and clsid
server.setServerProgID( _T("OPC.myTestServer") );
server.setServerCLSID( CLSID_OPCServerEXE ); // set delimeter for params name
server.SetDelimeter( "." ); if(argc == 2)
{
if(strstr(argv[1], "/r"))
{
// register server as COM/DCOM object
server.RegisterServer(); fclose(pfFile);
FreeConsole(); return 0;
}
else if(strstr(argv[1], "/u"))
{
server.UnregisterServer(); getchar(); fclose(pfFile);
FreeConsole(); return 0;
}
} // define server values tree
server.AddTag("Values.int1", VT_I4 );
server.AddTag("Values.int2", VT_I4 );
server.AddTag("Values.fltArray2", VT_ARRAY|VT_R4 );
server.AddTag("Values.fltArray2.In", VT_I4, false ); {
CAG_Clocker cl("Create 10000 tags",false); for(int i=0;i<10000;++i) {
sprintf(str,"RandomValues.int%d",i+1);
server.AddTag( str ,VT_I4 );
}
} // setup object will be received add values change
server.setDataReceiver( &receiver ); // create COM class factory and register it
server.StartServer(); printf("\t waiting return\n");
gets(str); // 等待用户任意输入,比如按个回车键,服务器才会继续执行 // write initial values to OPC params
for( double x =0.; x< 50.;x+=.1 ) {
server.WriteValue( "Values.int1", FILETIME_NULL, 192, CComVariant( sin(x) ) );
server.WriteValue( "Values.int2", FILETIME_NULL, 192, CComVariant( cos(x) ) );
Sleep(100);
} srand( (unsigned)time( NULL ) ); for(int i=0;i<10000;++i) {
sprintf(str,"RandomValues.int%d",i+1);
server.WriteValue( str , FILETIME_NULL, 192, CComVariant( rand() ) );
} printf("\t waiting return for close server \n");
gets(str); // 同样是等待用户在控制台的任意输入,服务器结束服务 server.StopServer(); CoUninitialize(); fclose(pfFile);
FreeConsole(); return 0;
}

其实解决方案就是通过控制台输入参数来区分进程启动后进入注册流程还是处理流程,同时为了调试方便,并能够让我看到客户端远程启动服务器的实际效果,我还为服务器分配了一个输出控制台(缺省情况下OPC后台启动是看不到交互窗口的),这样服务器一旦被客户端启动,输出控制台将在远程机器上弹出,我们就可以看到服务器输出的调试信息了,是不是很酷!至此三个问题解决,workshop库的样例服务器可以正常工作了。

最后,已经调整完且测试通过的workshop库VS2010的源码工程还是在我的github仓库获取:

https://github.com/Neo-T/OPCDASrvBasedOnLightOPC

基于第三方开源库的OPC服务器开发指南(4)——后记:与另一个开源库opc workshop库相关的问题的更多相关文章

  1. 基于第三方开源库的OPC服务器开发指南(3)——OPC客户端

    本篇将讲解如何编写一个OPC客户端程序测试我们在前文<基于第三方开源库的OPC服务器开发指南(2)——LightOPC的编译及部署>一篇建立的服务器.本指南的目的是熟悉OPC服务器的开发流 ...

  2. 基于第三方开源库的OPC服务器开发指南(2)——LightOPC的编译及部署

    前文已经说过,OPC基于微软的DCOM技术,所以开发OPC服务器我们要做的事情就是开发一个基于DCOM的EXE文件.一个代理/存根文件,然后就是写一个OPC客户端测试一下我们的服务器了.对于第一项工作 ...

  3. 基于第三方开源库的OPC服务器开发指南(1)——OPC与DCOM

    事儿太多,好多事情并不以我的意志为转移,原想沉下心好好研究.学习图像识别,继续丰富我的机器视觉库,并继续<机器视觉及图像处理系列>博文的更新,但计划没有变化快,好多项目要完成,只好耽搁下来 ...

  4. OPC服务器开发浅谈 — 服务器模型(转)

    这里主要讨论的是OPC Data Access 2.0服务器的开发,在掌握了这个最常用的OPC服务器开发之后,对其它类型的OPC服务器,如A&E.HDA等就可以触类旁通了. 一个OPC服务器的 ...

  5. KeyboardJS 开发指南 - 与 Three.js 配合使用的捕捉键盘组合键库

    KeyboardJS 开发指南 - 与 Three.js 配合使用的捕捉键盘组合键库 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非 ...

  6. 基于soap 的 python web services 服务开发指南

    文章大纲 序言 相关概念 SOA web services SOAP WSDL UDDI 环境搭建 我们使用 python 3.6 这个较新python 版本 服务端开发 客户端开发 suds-jur ...

  7. 现代前端库开发指南系列(二):使用 webpack 构建一个库

    前言 在前文中,我说过本系列文章的受众是在现代前端体系下能够熟练编写业务代码的同学,因此本文在介绍 webpack 配置时,仅提及构建一个库所特有的配置,其余配置请参考 webpack 官方文档. 输 ...

  8. C#.Net平台与OPC服务器通讯

    最近,我们Ndolls工作室承接了山大某个自动化控制项目,主要做了一套工控信息化系统,其中有一个功能模块是将系统管理的一部分数据参数发送至OPC服务器,由OPC服务器接收数据后执行相应工控操作.第一次 ...

  9. 基于mui的H5套壳APP开发web框架分享

    前言 创建一个main主页面,只有主页面有头部.尾部,中间内容嵌入iframe内容子页面,如果在当前页面进行跳转操作,也是在iframe中进行跳转,而如果点击尾部按钮切换模块.页面,那就切换ifram ...

随机推荐

  1. 使用Netfilter进行数据包分析

    #include <linux/init.h>#include <linux/module.h>#include <linux/skbuff.h>#include ...

  2. std::locale与boost::locale的学习

    1. 什么是facet, locale facet ['fæsɪt]的原意,是宝石切割出来的一个平面. locale[ləʊˈkæl],表示本地化, locale the container that ...

  3. flex上下、左右居中

    tip:1)flex是用于div布局用的,作用于一级子元素(父元素写样式,作用于子元素) 2)弹性盒模型 3)英文解释justify-content: 对齐内容(内容一般写在主轴上)align-ite ...

  4. Invalidate() InvalidateRect() 与 UpdateWindow()

    按引:Invalidate在消息队列中加入一条WM_PAINT消息,其无效区为整个客户区.而UpdateWindow直接发送一个WM_PAINT消息,其无效区范围就是消息队列中WM_PAINT消息(最 ...

  5. linux 服务器修改密码

    登录服务器后直接输入命令行passwd root 然后输入两次新密码就行

  6. OMG that's another blog!

    目录 1.Beginning 2.then 1.Beginning we'v learnt how to ask file from our own computer and tried to bui ...

  7. Mac OS X终端的常用操作命令(UNIX指令)

    用了十多年windows,终于换了个高配Mac,俗话说 无论前端还是后端最终还是走向了linux,无论是换了多少台PC最终都会走向Mac.不学习命令行用什么Mac? 干就完了~ pwd 显示现在的文件 ...

  8. Linux程序设计学习笔记(独乐乐版)

    在Android的开发过程中经常会遇到Linux相关的问题,为了更彻底的了解Linux准备整点没用的,找到一本 <Linux程序设计>开始系统的学习. 期间记录下自认为重要的内容,本以为是 ...

  9. Tk1上搭建turtlebot环境

    sudo apt-get install ros-indigo-turtlebot ros-indigo-turtlebot-apps ros-indigo-turtlebot-interaction ...

  10. volatile的使用及其原理

    1. volatile的作用 相比Sychronized(重量级锁,对系统性能影响较大),volatile提供了另一种解决 可见性和有序性 ???问题的方案.对于原子性,需要强调一点,也是大家容易误解 ...