基于第三方开源库的OPC服务器开发指南(4)——后记:与另一个开源库opc workshop库相关的问题
平心而论,我们从样例服务器的代码可以看出,利用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库相关的问题的更多相关文章
- 基于第三方开源库的OPC服务器开发指南(3)——OPC客户端
本篇将讲解如何编写一个OPC客户端程序测试我们在前文<基于第三方开源库的OPC服务器开发指南(2)——LightOPC的编译及部署>一篇建立的服务器.本指南的目的是熟悉OPC服务器的开发流 ...
- 基于第三方开源库的OPC服务器开发指南(2)——LightOPC的编译及部署
前文已经说过,OPC基于微软的DCOM技术,所以开发OPC服务器我们要做的事情就是开发一个基于DCOM的EXE文件.一个代理/存根文件,然后就是写一个OPC客户端测试一下我们的服务器了.对于第一项工作 ...
- 基于第三方开源库的OPC服务器开发指南(1)——OPC与DCOM
事儿太多,好多事情并不以我的意志为转移,原想沉下心好好研究.学习图像识别,继续丰富我的机器视觉库,并继续<机器视觉及图像处理系列>博文的更新,但计划没有变化快,好多项目要完成,只好耽搁下来 ...
- OPC服务器开发浅谈 — 服务器模型(转)
这里主要讨论的是OPC Data Access 2.0服务器的开发,在掌握了这个最常用的OPC服务器开发之后,对其它类型的OPC服务器,如A&E.HDA等就可以触类旁通了. 一个OPC服务器的 ...
- KeyboardJS 开发指南 - 与 Three.js 配合使用的捕捉键盘组合键库
KeyboardJS 开发指南 - 与 Three.js 配合使用的捕捉键盘组合键库 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非 ...
- 基于soap 的 python web services 服务开发指南
文章大纲 序言 相关概念 SOA web services SOAP WSDL UDDI 环境搭建 我们使用 python 3.6 这个较新python 版本 服务端开发 客户端开发 suds-jur ...
- 现代前端库开发指南系列(二):使用 webpack 构建一个库
前言 在前文中,我说过本系列文章的受众是在现代前端体系下能够熟练编写业务代码的同学,因此本文在介绍 webpack 配置时,仅提及构建一个库所特有的配置,其余配置请参考 webpack 官方文档. 输 ...
- C#.Net平台与OPC服务器通讯
最近,我们Ndolls工作室承接了山大某个自动化控制项目,主要做了一套工控信息化系统,其中有一个功能模块是将系统管理的一部分数据参数发送至OPC服务器,由OPC服务器接收数据后执行相应工控操作.第一次 ...
- 基于mui的H5套壳APP开发web框架分享
前言 创建一个main主页面,只有主页面有头部.尾部,中间内容嵌入iframe内容子页面,如果在当前页面进行跳转操作,也是在iframe中进行跳转,而如果点击尾部按钮切换模块.页面,那就切换ifram ...
随机推荐
- json 报错415 400
JS操作JSON总结 $(function(){ $.ajax({ method: 'post', url: '/starMOOC/forum/getSectionList', dataType: ...
- 机器学习技法笔记:Homework #5 特征变换&Soft-Margin SVM相关习题
原文地址:https://www.jianshu.com/p/6bf801bdc644 特征变换 问题描述 程序实现 # coding: utf-8 import numpy as np from c ...
- CSS:Stacking Context
通常情况下,HTML页面可以被认为是二维的,因为文本,图像和其他元素被排列在页面上而不重叠.在这种情况下,只有一个渲染进程,所有元素都知道其他元素所占用的空间.z-index属性可让你在渲染内容时调整 ...
- MonkeyTalk使用方法
1.简单介绍 MonkeyTalk软件测试工具由两部分构成:MonkeyTalk IDE 和 MonkeyTalk Agents MonkeyTalk IDE是Eclipse平台的工具,工能是:对iO ...
- thinkphp 标签扩展
标签库加载直线电机参数 模板中加载标签库,预加载自定义标签库,扩展内置标签库的加载 请参考:http://document.thinkphp.cn/manual_3_2.html#taglib 自定义 ...
- 【Codeforces Round #429 (Div. 2) B】 Godsend
[Link]:http://codeforces.com/contest/841/problem/B [Description] 两个人轮流对一个数组玩游戏,第一个人可以把连续的一段为奇数的拿走,第二 ...
- Delphi获取句柄
Delphi获取句柄发布时间:2011-06-16转载文章请标明出处: http://code.01yun.com/asp0dm/asp0net0dm/20110616/55395.html查找另外一 ...
- LUOGU P1501 [国家集训队]Tree II (lct)
传送门 解题思路 \(lct\),比较模板的一道题,路径加和乘的维护标记与线段树\(2\)差不多,然后剩下就没啥了.但调了我将近一下午.. 代码 #include<iostream> #i ...
- [JZOJ 100025] 棋盘
题意:求剩余面积. 首先吐槽题号:究竟\(JZOJ\)有多少未公开的题目... 思路: 简单的一批啊... 不知道为啥上午不过下午就过了?? 难道是海螺姑娘光顾我?? 多说了都是灵异故事... 其实就 ...
- Codeforces703D-Mishka and Interesting sum-离线树状数组
(有任何问题欢迎留言或私聊 && 欢迎交流讨论哦 题意:传送门 原题目描述在最下面. 询问一个区间内出现次数为偶数次的数字的异或和. 思路: 先求出区间异或前缀和,其实就是出现次 ...