平心而论,我们从样例服务器的代码可以看出,利用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. adb devices unauthorized的解决办法

        Hi, trying to launch adb but get: daemon not running. starting it now on port * daemon started s ...

  2. java.util.Arrays,java.lang.Math,java.lang.System 类的常用方法汇总

    java.util.Arrays类是数组的工具类,一般数组常用的方法包括 二分查找:public static int  binarySearch(array[],int key),返回key的下标i ...

  3. 并发编程之CAS(二)

    更多Android架构进阶视频学习请点击:https://space.bilibili.com/474380680本篇文章将从以下几个内容来阐述CAS: [CAS原理] [CAS带来的ABA问题] 一 ...

  4. 精选 Dubbo RPC 面试题,比较全面,含答案

    精选 Dubbo RPC 面试题,比较全面,含答案 hu1991die 搜云库技术团队 搜云库技术团队 微信号 souyunku 功能介绍 专注于分享最有价值的互联网技术干货文章,内容覆盖,Java后 ...

  5. python--模块导入与执行

    定义:一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀. 一.模块注意: 1.所有的模块都应该自觉的往上写 2.调用模块的时候都是最先在本地找 3.写模块的顺序是 ...

  6. VACUUM - 垃圾收集以及可选地分析一个数据库

    SYNOPSIS VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ table ] VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANA ...

  7. Spring Boot 自定义注解,AOP 切面统一打印出入参请求日志

    其实,小哈在之前就出过一篇关于如何使用 AOP 切面统一打印请求日志的文章,那为什么还要再出一篇呢?没东西写了? 哈哈,当然不是!原因是当时的实现方案还是存在缺陷的,原因如下: 不够灵活,由于是以所有 ...

  8. vs2010用iis5作为调试服务器从而允许非本机电脑访问项目网站

    工作的时候经常遇见这2种情况 1,和设备端的同事调程序,但是他们却不能访问vs自带的web服务器 2,写好的程序在vs中运行一点问题都没有,一发布到iis就问题一大堆 后来在终于有了一个比较好的解决办 ...

  9. Replication Controller、Replica Set

    假如我们现在有一个Pod正在提供线上的服务,我们来想想一下我们可能会遇到的一些场景: 某次运营活动非常成功,网站访问量突然暴增 运行当前Pod的节点发生故障了,Pod不能正常提供服务了 第一种情况,可 ...

  10. Java之Java的文件结构(才不是叛教!)

    Java从入门到恰饭之文件结构,使用IDEA开发. 新建包 包名一般选择公司域名(https://tieba.baidu.com/)的反转 创建完成是这样的 对应的三层文件夹 我们创建一个类 pack ...