首页 技术 正文
技术 2022年11月15日
0 收藏 878 点赞 4,710 浏览 7138 个字

事儿太多,好多事情并不以我的意志为转移,原想沉下心好好研究、学习图像识别,继续丰富我的机器视觉库,并继续《机器视觉及图像处理系列》博文的更新,但计划没有变化快,好多项目要完成,只好耽搁下来(这一耽搁又是多半年啊,惭愧,基于第三方开源库的OPC服务器开发指南(1)——OPC与DCOM)。最近某个项目需要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服务器库 http://www.ipi.ac.ru/lab43/lopc-en.html

DCOM开发样例 https://blog.csdn.net/u011402642/article/details/46516559

上面的DCOM开发样例出现0x80080005错误时的解决方案 https://blog.csdn.net/oshuangyue12/article/details/88424114

OPC头文件 https://www.cnblogs.com/opcconnect/archive/2010/12/20/1911604.html

本指南的样例代码请直接从github上拉取:https://github.com/Neo-T/OPCDASrvBasedOnLightOPC

 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 STDMETHODIMP CArithmeticLib::add(int nNum1, int nNum2, int * pnResult)
{
*pnResult = nNum1 + nNum2;
return S_OK;
} STDMETHODIMP CArithmeticLib::sub(int nNum1, int nNum2, int * pnResult)
{
*pnResult = nNum1 - nNum2;
return S_OK;
}

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

val AppID = s ‘%APPID%’

增加位置如下:

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

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

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

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

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

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

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

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

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

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

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

 // iDCOMTestClient.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include <windows.h>
#include "iDCOMTestSrv_i.h"
#include "iDCOMTestSrv_i.c" int _tmain(int argc, _TCHAR* argv[])
{
CoInitialize(NULL);
{
do{
HRESULT hr; COSERVERINFO stCoServerInfo;
COAUTHINFO stCoAuthInfo;
COAUTHIDENTITY stCoAuthID;
INT nSize = strlen("192.168.xxx.xxx") * sizeof(WCHAR);
memset(&stCoServerInfo, 0, sizeof(stCoServerInfo));
stCoServerInfo.pwszName = (WCHAR *)CoTaskMemAlloc(nSize * sizeof(WCHAR));
if(!stCoServerInfo.pwszName)
{
printf("CoTaskMemAlloc()函数执行失败!\r\n");
break;
} ZeroMemory(&stCoAuthID, sizeof(COAUTHIDENTITY));
stCoAuthID.User = reinterpret_cast<USHORT *>("user");
stCoAuthID.UserLength = strlen("user");
stCoAuthID.Domain = reinterpret_cast<USHORT *>("");
stCoAuthID.DomainLength = 0;
stCoAuthID.Password = reinterpret_cast<USHORT *>("user_password");
stCoAuthID.PasswordLength = strlen("user_password");
stCoAuthID.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI; ZeroMemory(&stCoAuthInfo, sizeof(COAUTHINFO));
stCoAuthInfo.dwAuthnSvc = RPC_C_AUTHN_WINNT;
stCoAuthInfo.dwAuthzSvc = RPC_C_AUTHZ_NONE;
stCoAuthInfo.pwszServerPrincName = NULL;
stCoAuthInfo.dwAuthnLevel = RPC_C_AUTHN_LEVEL_CONNECT;
stCoAuthInfo.dwImpersonationLevel = RPC_C_IMP_LEVEL_IMPERSONATE; //* 必须是模拟登陆
stCoAuthInfo.pAuthIdentityData = &stCoAuthID;
stCoAuthInfo.dwCapabilities = EOAC_NONE; mbstowcs(stCoServerInfo.pwszName, "192.168.xxx.xxx", nSize);
stCoServerInfo.pAuthInfo = &stCoAuthInfo;
stCoServerInfo.dwReserved1 = 0;
stCoServerInfo.dwReserved2 = 0; MULTI_QI stMultiQI;
ZeroMemory(&stMultiQI, sizeof(stMultiQI));
stMultiQI.pIID = &IID_IArithmeticLib; //* 参见iDCOMTestSrv_i.c
stMultiQI.pItf = NULL; //* 初始化安全结构,模拟登录远程机器
hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_CONNECT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
if(!(SUCCEEDED(hr) || RPC_E_TOO_LATE == hr))
{
printf("CoInitializeSecurity()函数执行失败,错误码:0x%08X\r\n", hr);
break;
} //* 建立COM组件实例并按照需求获取查询接口
hr = CoCreateInstanceEx(CLSID_ArithmeticLib, //* 参见iDCOMTestSrv_i.c
NULL,
CLSCTX_REMOTE_SERVER, //* 显式的指定要连接远程机器
&stCoServerInfo,
sizeof(stMultiQI)/sizeof(MULTI_QI),
&stMultiQI); //* 无论成功与否,先释放刚才申请的内存
CoTaskMemFree(stCoServerInfo.pwszName); //* 如果CoCreateInstanceEx()执行失败
if(FAILED(hr))
{
printf("CoCreateInstanceEx()函数执行失败,错误码:0x%08X\r\n", hr);
break;
} //* 如果没有获取到DCOM组件的查询接口
if(FAILED(stMultiQI.hr))
{
printf("获取组件的查询接口失败,错误码:0x%08X\r\n", stMultiQI.hr);
break;
} //* 查询并获取组件的调用接口,获取完毕后直接释放即可
IArithmeticLib *piobjArithmetic = NULL;
stMultiQI.pItf->QueryInterface(&piobjArithmetic);
stMultiQI.pItf->Release(); //* 接收用户输入并调用远程组件获得计算结果
INT blIsRunning = TRUE;
while(blIsRunning)
{
INT nEnterFunCode;
INT nNum1, nNum2, nResult; printf("1: 加; 2: 减; 0: 退出");
scanf("%d", &nEnterFunCode); switch(nEnterFunCode)
{
case 1:
printf("请输入相加的两个整型数字(空格分开):");
scanf("%d%d", &nNum1, &nNum2);
piobjArithmetic->add(nNum1, nNum2, &nResult);
printf("[加]操作结果为:%d\r\n", nResult);
break; case 2:
printf("请输入相减的两个整型数字(空格分开):");
scanf("%d%d", &nNum1, &nNum2);
piobjArithmetic->sub(nNum1, nNum2, &nResult);
printf("[减]操作结果为:%d\r\n", nResult);
break; case 0:
default:
blIsRunning = FALSE;
break;
}
}
}while(FALSE);
}
CoUninitialize(); return 0;
}

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

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

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

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

iDCOMTestSrv.exe/RegServer/Service

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

基于第三方开源库的OPC服务器开发指南(1)——OPC与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”组,如下所示:

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

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

mmc comexp.msc

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

iDCOMTestClient.exe

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

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

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

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

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,082
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,556
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,406
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,179
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,815
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,898