平心而论,我们从样例服务器的代码可以看出,利用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

最新文章

  1. github如何查看提交历史呢
  2. django基础篇
  3. int.class 与 Integer.class
  4. July 27th, Week 31st Wednesday, 2016
  5. iOS自学之NSOperation、NSOperationQueue、Background
  6. [React] Creating a Stateless Functional Component
  7. Java中的属性与字段的区别
  8. python 基础学习3-函数
  9. zookeeper的安装及集群配置
  10. JDBC连接数据库程序
  11. App会取代网站吗?
  12. C# -- 使用Parallel并行执行任务
  13. java.lang包【Object类】
  14. 使用CORS方式跨域
  15. json 异常
  16. Spark学习笔记——在集群上运行Spark
  17. 【转载一】Grafana –美观、强大的可视化监控指标展示工具
  18. influxDB 0.9 C# 读写类
  19. Django 浏览器打开警告Not Found: /favicon.ico (转)
  20. 使用 properties 配置文件装配 bean 的方式

热门文章

  1. 拾遗:关于“尾递归”- tail recursion
  2. vue富文本vue-quill-editor
  3. redis 配置文件aof配置
  4. 20140729 while((*pa++=*pb++)!=&#39;\0&#39;) 合并数组代码 C++类型转换关键字 封装 多态 继承
  5. 5、如何快速找到多个字典中的公共键(key) 6 如何让字典保持有序 7 如何实现用户的历史记录功能(最多n条)
  6. (转)Java中Image的水平翻转、缩放与自由旋转操作
  7. (PASS)java中nextInt()函数
  8. Java 的信号灯
  9. python操作redis数据
  10. 基于 CI 1.7.x 的 项目使用新版本CI的文件缓存类库