博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于第三方开源库的OPC服务器开发指南(1)——OPC与DCOM
阅读量:5060 次
发布时间:2019-06-12

本文共 9342 字,大约阅读时间需要 31 分钟。

事儿太多,好多事情并不以我的意志为转移,原想沉下心好好研究、学习图像识别,继续丰富我的机器视觉库,并继续《》博文的更新,但计划没有变化快,好多项目要完成,只好耽搁下来(这一耽搁又是多半年啊,惭愧,Disappointed smile)。最近某个项目需要OPC服务器支持,于是又转战OPC战场。说实话这之前对于OPC我只是粗浅了解,知道这是基于微软的DCOM技术制定的用于工控领域的技术标准,制定并持续维护这一标准的组织被称作OPC基金会。我不知道基金会对OPC的应用推广做了多少工作,做出了多大贡献,但至少可以确定的是,这个基金会对普通开发人员相当不友好,我即使成功注册了用户,依然在它的网站上没找到OPC的支持组件“OPC Core Components Redistributable 3.0”,我估计这是他们的商业考虑,为的是多发展企业用户,好多赚钱。这么LaJi的组织,实在是让人鄙视,OPC的未来最终会被这满是铜臭味的GouPi基金会葬送。要不是目前的工控市场还被OPC把持(没人和小钱钱有仇啊),说实话我是不会把精力浪费在这么封闭的系统上。所以,我对OPC的定位就是不求甚解,只求一用。有了清晰的目标,我开始在网上满世界的找OPC开发相关的资料和源码,度娘、bing、github、gitee、CodeForge以及搭梯子google之,反正能想到的方法都想到了,但结果相当不理想。CSDN上倒是有不少OPC服务器源码可供下载,但需要积分,我没有积分,我也不想挣或者花钱买积分,因为我一直主张并坚持技术共享精神,CSDN和众多CSDNers严重违背了这一精神,特别是那些拿着别人开源的源码赚积分的WuChi小“人”们。这无疑增加了我的难度,好在我们有github,有众多的具备分享精神的程序猿们,当然还有强大的搜索引擎,最终我找齐了所有资料和源码,搞明白了OPC服务器的整个开发流程。再次鄙视OPC基金会、CSDN,感谢如下网址的主人们:

开源OPC服务器库

DCOM开发样例

上面的DCOM开发样例出现0x80080005错误时的解决方案

OPC头文件

 

本指南的样例代码请直接从github上拉取:

 

吐槽完毕,言归正传。前面我们已经说过,OPC基于微软的DCOM技术,所以要想明白如何开发OPC服务器,首先就得知道如何开发DCOM。否则,你会摔得遍体鳞伤,浪费大把时间后依然是——不得其门而入。因为这个DCOM啊,实在是太过啰嗦,开发啰嗦、部署和使用更啰嗦,个人感觉它早晚会被淘汰。关于DCOM开发样例,上面给出的链接虽然是作者2015年写的,时间不算老,但开发环境竟然是1998年发布的VC6,这个鸿沟就有点大了。现在常用的VS2010和VS2015在DCOM开发上均做了不少改进,因此有必要在这里再开一篇,简明扼要地介绍VS2010和VS2015下的DCOM开发流程,以备后查。

首先,打开VS2010或VS2015(下简称VS),”新建项目”->”Visual C++”->”ATL项目”,输入名称“iDCOMTestSrv”:

然后“确定”->”下一步”,选择“服务(EXE)”,最后点选“完成”。

接下来,添加COM对象。工程名称节点鼠标右键,点选“添加”->””:

在弹出窗口选择“ATL简单对象”:

点击“添加”后,按下图输入相关信息,然后直接点击“完成”:

在“类视图”窗口,鼠标右键点选“IArithmeticLib”,在弹出的右键菜单中选择“添加”->”添加方法”:

弹出窗口中按下图所示添加add()方法:

最后点击“完成”按钮,add()方法添加完毕。接着按照如上步骤再添加一个sub()方法:

我们的目的是熟悉DCOM的开发流程,所以不用编写复杂的函数,添加两个add()和sub()方法就行了。

转到VS的解决方案资源管理器,“源文件”节点双击“ArithmeticLib.cpp”,添加两个方法的处理代码:

1 STDMETHODIMP CArithmeticLib::add(int nNum1, int nNum2, int * pnResult)  2 {  3 	*pnResult = nNum1 + nNum2;  4 	return S_OK;  5 }  6   7   8 STDMETHODIMP CArithmeticLib::sub(int nNum1, int nNum2, int * pnResult)  9 { 10 	*pnResult = nNum1 - nNum2; 11 	return S_OK; 12 }

接下来我们还需要调整一个地方,在左侧的“解决方案资源管理器”窗口,找到“iDCOMTestSrv”工程的“资源文件”->“ArithmeticLib.rgs”,双击打开,然后在这个文件增加如下一句:

val AppID = s '%APPID%'

增加位置如下:

这一句解决客户端连接DCOM组件服务器时报0x80080005错误的问题。

接着编译,如果不出意外,VS2015编译应该能够成功,VS2010则不一定,因为VS2010相对VS2015多做了一步:

如果你有系统管理员权限,那么编译完成后这个注册是能够成功的,如果不是则失败。由于我们是把DCOM部署到其它机器远程执行,不在本机注册,所以这里可以删掉。不过,这一步倒是告诉我们,DCOM需要注册才能使用。

接下来我们需要生成代理/存根文件,以用于远程访问DCOM组件。相对VC6,新版本的VS帮我们自动建立了代理/存根文件工程,工程需要的相关文件,是VS通过对应的IDL文件编译生成的,我们在刚才编译DCOM时VS已经帮我们生成了相关文件并添加到对应工程下:

在编译生成代理/存根文件之前,我们需要更改一下该工程的链接器设置,见下图:

把“注册输出”一项改为“”,这样VS就不会在编译完成后顺带手帮我们在本机上注册该动态库了。虽然,VS这样设计对直接调试来说比较省事,但这样也掩盖了技术细节,所以还是禁止VS这种越俎代庖的行为更好。接下来鼠标右键点选“解决方案资源管理器”中的“iDCOMTestSrvPS”,点击“生成”,如无意外,我们将生成iDCOMTestSrv的代理/存根文件——“iDCOMTestSrvPS.dll”。

接下来我们还需要编写使用这个DCOM组件的客户端,看看效果如何。继续在VS中新建一个“Win32控制台应用程序”,在打开的源文件“iDCOMTestClient.cpp”中添加如下代码:

1 // iDCOMTestClient.cpp : 定义控制台应用程序的入口点。  2 //  3   4 #include "stdafx.h"  5 #include 
6 #include "iDCOMTestSrv_i.h" 7 #include "iDCOMTestSrv_i.c" 8 9 int _tmain(int argc, _TCHAR* argv[]) 10 { 11 CoInitialize(NULL); 12 { 13 do{ 14 HRESULT hr; 15 16 COSERVERINFO stCoServerInfo; 17 COAUTHINFO stCoAuthInfo; 18 COAUTHIDENTITY stCoAuthID; 19 INT nSize = strlen("192.168.xxx.xxx") * sizeof(WCHAR); 20 memset(&stCoServerInfo, 0, sizeof(stCoServerInfo)); 21 stCoServerInfo.pwszName = (WCHAR *)CoTaskMemAlloc(nSize * sizeof(WCHAR)); 22 if(!stCoServerInfo.pwszName) 23 { 24 printf("CoTaskMemAlloc()函数执行失败!\r\n"); 25 break; 26 } 27 28 ZeroMemory(&stCoAuthID, sizeof(COAUTHIDENTITY)); 29 stCoAuthID.User = reinterpret_cast
("user"); 30 stCoAuthID.UserLength = strlen("user"); 31 stCoAuthID.Domain = reinterpret_cast
(""); 32 stCoAuthID.DomainLength = 0; 33 stCoAuthID.Password = reinterpret_cast
("user_password"); 34 stCoAuthID.PasswordLength = strlen("user_password"); 35 stCoAuthID.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI; 36 37 ZeroMemory(&stCoAuthInfo, sizeof(COAUTHINFO)); 38 stCoAuthInfo.dwAuthnSvc = RPC_C_AUTHN_WINNT; 39 stCoAuthInfo.dwAuthzSvc = RPC_C_AUTHZ_NONE; 40 stCoAuthInfo.pwszServerPrincName = NULL; 41 stCoAuthInfo.dwAuthnLevel = RPC_C_AUTHN_LEVEL_CONNECT; 42 stCoAuthInfo.dwImpersonationLevel = RPC_C_IMP_LEVEL_IMPERSONATE; //* 必须是模拟登陆 43 stCoAuthInfo.pAuthIdentityData = &stCoAuthID; 44 stCoAuthInfo.dwCapabilities = EOAC_NONE; 45 46 mbstowcs(stCoServerInfo.pwszName, "192.168.xxx.xxx", nSize); 47 stCoServerInfo.pAuthInfo = &stCoAuthInfo; 48 stCoServerInfo.dwReserved1 = 0; 49 stCoServerInfo.dwReserved2 = 0; 50 51 MULTI_QI stMultiQI; 52 ZeroMemory(&stMultiQI, sizeof(stMultiQI)); 53 stMultiQI.pIID = &IID_IArithmeticLib; //* 参见iDCOMTestSrv_i.c 54 stMultiQI.pItf = NULL; 55 56 //* 初始化安全结构,模拟登录远程机器 57 hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_CONNECT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL); 58 if(!(SUCCEEDED(hr) || RPC_E_TOO_LATE == hr)) 59 { 60 printf("CoInitializeSecurity()函数执行失败,错误码:0x%08X\r\n", hr); 61 break; 62 } 63 64 //* 建立COM组件实例并按照需求获取查询接口 65 hr = CoCreateInstanceEx(CLSID_ArithmeticLib, //* 参见iDCOMTestSrv_i.c 66 NULL, 67 CLSCTX_REMOTE_SERVER, //* 显式的指定要连接远程机器 68 &stCoServerInfo, 69 sizeof(stMultiQI)/sizeof(MULTI_QI), 70 &stMultiQI); 71 72 //* 无论成功与否,先释放刚才申请的内存 73 CoTaskMemFree(stCoServerInfo.pwszName); 74 75 //* 如果CoCreateInstanceEx()执行失败 76 if(FAILED(hr)) 77 { 78 printf("CoCreateInstanceEx()函数执行失败,错误码:0x%08X\r\n", hr); 79 break; 80 } 81 82 //* 如果没有获取到DCOM组件的查询接口 83 if(FAILED(stMultiQI.hr)) 84 { 85 printf("获取组件的查询接口失败,错误码:0x%08X\r\n", stMultiQI.hr); 86 break; 87 } 88 89 //* 查询并获取组件的调用接口,获取完毕后直接释放即可 90 IArithmeticLib *piobjArithmetic = NULL; 91 stMultiQI.pItf->QueryInterface(&piobjArithmetic); 92 stMultiQI.pItf->Release(); 93 94 //* 接收用户输入并调用远程组件获得计算结果 95 INT blIsRunning = TRUE; 96 while(blIsRunning) 97 { 98 INT nEnterFunCode; 99 INT nNum1, nNum2, nResult;100 101 printf("1: 加; 2: 减; 0: 退出");102 scanf("%d", &nEnterFunCode);103 104 switch(nEnterFunCode)105 {106 case 1:107 printf("请输入相加的两个整型数字(空格分开):");108 scanf("%d%d", &nNum1, &nNum2);109 piobjArithmetic->add(nNum1, nNum2, &nResult);110 printf("[加]操作结果为:%d\r\n", nResult);111 break;112 113 case 2:114 printf("请输入相减的两个整型数字(空格分开):");115 scanf("%d%d", &nNum1, &nNum2);116 piobjArithmetic->sub(nNum1, nNum2, &nResult);117 printf("[减]操作结果为:%d\r\n", nResult);118 break;119 120 case 0:121 default:122 blIsRunning = FALSE;123 break;124 }125 }126 }while(FALSE);127 }128 CoUninitialize();129 130 return 0;131 }

代码很简单,关键地方都添加了注释,这里不再作过多说明。重点说一下“#include”进来的两个文件“iDCOMTestSrv_i.h”和“iDCOMTestSrv_i.c”,这两个文件与代理/存根工程使用的文件是同一个,所以我们没必要再将其单独添加到这个测试客户端工程中来,只需在工程属性中把这两个文件所在的目录包含进来即可:

设置完成后直接编译、生成EXE文件。

接下来就是部署工作了,这块工作是最麻烦的。首先我们把刚才生成的“iDCOMTestSrvPS.dll”、“iDCOMTestSrv.exe”两个文件复制到另外一台机器的某个目录下,然后在这个目录下以管理员身份打开控制台,输入如下指令:

iDCOMTestSrv.exe/RegServer/Service

如果你的权限没问题,这一步将很顺利。此时我们可以打开“服务管理器”看到我们注册的DCOM服务已经被添加进来了:

服务的启动类型为“手动”,尚未启动,这个不用管它,一旦客户端成功连接,OS会为我们启动它的。

接着我们注册代理/存根文件,如果你编译的是32位的DCOM,请使用如下指令注册:

c:\windows\SysWOW64\regsvr32.exe iDCOMTestSrvPS.dll

如果是是64位DCOM则输入如下指令:

c:\windows\System32\regsvr32.exe iDCOMTestSrvPS.dll

只有如此才能正确注册32位和64位DCOM。

接着我们在DCOM客户端所在的机器注册代理/存根文件,注册指令与上同。

注册完毕,我们再把工作焦点转移到DCOM服务器。首先我们添加一个DCOM用户“user”,设定一个密码(不能是空密码),然后让其隶属于“Distributed COM Users”组,如下所示:

用户添加完毕,接着控制台输入如下指令:

mmc comexp.msc

如果是32位的DCOM组件,请在上述指令后再增加“ /32”,注意别漏了前面的空格。

在打开的“组件服务”窗口找到“我的电脑”节点,然后鼠标右键选择“属性”,在打开的窗口首先设置“默认属性”:

接着“默认协议”选择“面向连接的TCP/IP”:

然后是“COM安全”,为刚才添加的“user”用户分配权限:

访问权限”之“编辑限制”的“user”权限:

访问权限”之“编辑默认值”的“user”权限:

启动和激活权限”之“编辑默认值”的“user”权限:

 

DCOM组件的缺省配置完成,我们还需要再继续配置iDCOMTestSrv组件的权限。在下图红框所示位置,鼠标右键点选“ArithmeticLib class”:

右键菜单选择“属性”,按照下图所示设置相关权限:

至此,所有配置完成,DCOM所在的机器重启,无需登录,稍等一段时间待RPC服务被OS启动,然后在客户端所在机器打开控制台,输入如下指令:

iDCOMTestClient.exe

如果上面的操作完全无误的话,你会看到如下界面:

至此,我们对DCOM的开发已经完全了解,接下来就是在开源库基础上开发OPC服务器了。

github上有该例子的完整代码(链接见本文顶部开始部分),分别由VS2010和VS2015建立,VS2010是32位的DEBUG版本,编译通过,但未实际部署测试,VS2015则是64位的Release版本,已在两台机器上按照上述步骤测试通过。

posted on
2019-05-06 19:42 阅读(
...) 评论(
...)

转载于:https://www.cnblogs.com/neo-T/p/OPCSrvExample-1.html

你可能感兴趣的文章
[leetcode] 1. Two Sum
查看>>
iOS 日常工作之常用宏定义大全
查看>>
PHP的SQL注入技术实现以及预防措施
查看>>
软件目录结构规范
查看>>
mysqladmin
查看>>
解决 No Entity Framework provider found for the ADO.NET provider
查看>>
Android 自定义View (三) 圆环交替 等待效果
查看>>
设置虚拟机虚拟机中fedora上网配置-bridge连接方式(图解)
查看>>
HEVC播放器出炉,迅雷看看支持H.265
查看>>
[置顶] Android仿人人客户端(v5.7.1)——人人授权访问界面
查看>>
Eclipse 调试的时候Tomcat报错启动不了
查看>>
【安卓5】高级控件——拖动条SeekBar
查看>>
ES6内置方法find 和 filter的区别在哪
查看>>
Android入门之文件系统操作(二)文件操作相关指令
查看>>
Android实现 ScrollView + ListView无滚动条滚动
查看>>
java学习笔记之String类
查看>>
UVA 11082 Matrix Decompressing 矩阵解压(最大流,经典)
查看>>
jdk从1.8降到jdk1.7失败
查看>>
一些关于IO流的问题
查看>>
mongo备份操作
查看>>