前文已经说过,OPC基于微软的DCOM技术,所以开发OPC服务器我们要做的事情就是开发一个基于DCOM的EXE文件、一个代理/存根文件,然后就是写一个OPC客户端测试一下我们的服务器了。对于第一项工作,我们需要借助一个俄罗斯大神开源的OPC开发库——LightOPC展开。不过,从LightOPC的主网站看,这个库已经许久没更新了,时间定格在了2004年:
http://www.ipi.ac.ru/lab43/lopc-en.html
虽然很久没有更新了,但这个库在64位系统大行其道的今天依然能用并且好用,“最新”版本的下载地址为:
ftp://ftp.ipi.ac.ru/pub/LightOPC/lightopc-0.888-0313.tar.gz
由于LightOPC使用了第三方的日志库unilog,我们还需要把配套版本的unilog库的源码下载下来:
ftp://ftp.ipi.ac.ru/pub/LightOPC/unilog-0.55-1227.tgz
两个源码文件分别解压,然后打开VS:“文件”->“新建”->“项目”->“Win32项目”,建立unilog的源码工程:
“确定”->“下一步”->“下一步”,然后选择建立“DLL”工程并勾选“空项目”:
工程建立完毕后,我们把刚才解压后得到的unilog的源码添加到工程中,要添加的头文件和C文件参见如下列表:
头文件 condsb.h、logger.h、lwsynch.h、rwlock.h、unilog.h
C文件 advini.c、dllmain.c、lwsynch.c、raserr.c、reglog.c、sockerr.c、ssperr.c、unilog.c
其中,logger.h并不包含在我们刚才下载的unilog源码中,还需要我们再单独下载。这也是网上资源存在的一个普遍现象,无论是Blog文章还是各种下载资源,有相当一部分存在这样那样的问题,要么是文章提供的操作步骤不完整或者讲得不清不楚,要么是提供的源码缺少某个函数,要么干脆就是提供的源码存在逻辑错误根本无法编译或者编译成功却无法正常执行,要么就像现在这种情况提供的源码缺少某个文件……等等不一而足。按理说,程序猿应该是这个世界上做事最严谨的群体之一,分享技术时至少应该内部测试无误后再发布,这应该是这个群体最基本的工作守则之一,否则你分享它干嘛呢,但结果却总是差强人意。只能说无论哪个群体,优秀的毕竟还是少数,大部分只能是最普通的那些。对于缺少的这个文件我们需要在github上找,地址如下:
你可以打开查阅这个文件的内容并复制,然后自己再手动创建一个logger.h文件把内容复制进来,这是最省事的方法。不想这样做的话你也可以clone整个工程或者使用SVN获取单个unilog目录下的所有文件。做完这项工作后我们还需要手动调整几个地方:
1、lwsynch.h文件,将头部两个#include语句改为对本地头文件的引用:
#include <rwlock.h>
#include <condsb.h>
改为
#include "rwlock.h"
#include "condsb.h"
2、unilog.h文件找到“# define UL_PUBLIC”一行,放开“UL_PUBLIC”宏定义,也就是去掉注释符号,使能该宏:
1 #ifndef UL_PUBLIC 2 # define UL_PUBLIC /*extern*/ 3 /* This silly thing is not required when we use .DEF file 4 # define UL_PUBLIC __declspec(dllexport) 5 # define UL_PUBLIC __attribute__((dllexport)) */ 6 /* This one may be useful for caller optimization 7 # define UL_PUBLIC __declspec(dllimport) */ 8 #endif /*UL_PUBLIC*/ 9 10 /* 上面是原来的代码,下面是改后的 */ 11 #ifndef UL_PUBLIC 12 #define UL_PUBLIC /*extern*/ 13 /* This silly thing is not required when we use .DEF file */ 14 #define UL_PUBLIC __declspec(dllexport) 15 /* # define UL_PUBLIC __attribute__((dllexport)) */ 16 /* This one may be useful for caller optimization 17 # define UL_PUBLIC __declspec(dllimport) */ 18 #endif /*UL_PUBLIC*/
这样做的目的是建立unilog.dll文件的导出符号文件,以备LightOPC编译之用。
3、工程属性的“附加依赖项”增加“ws2_32.lib”库文件:
4、在解决方案的根目录下建立两个文件夹“bin”、“lib”,用于存放生成的LIB和DLL文件:
5、“unilog”工程属性增加“后期生成事件”,以便VS在编译完成后自动将生成的LIB和DLL文件分别复制到刚才建立的文件夹中:
copy $(SolutionDir)$(Configuration)\$(ProjectName).dll $(SolutionDir)bin\$(ProjectName).dll
copy $(SolutionDir)$(Configuration)\$(ProjectName).lib $(SolutionDir)lib\$(ProjectName).lib
6、调整“工程属性”的“字符集”为“未设置” ,如果不调整该项,使用缺省值“使用Unicode字符集”将导致系统日志显示为乱码,无法用“Windows事件查看器”查看OPC服务器运行情况:
至此,所有工作调整完成,编译生成LIB和DLL即可,最终我们会在刚才建立的两个文件夹下找到生成的文件。接下来开始编译LightOPC库。
由于LightOPC的开发环境是VC6,所以我们不需要再单独为此建立VS工程了,只需双击LightOPC文件夹下的VC6工程文件“LightOPC.dsw”,然后用机器上已安装的VS2010或VS2015打开后任凭VS自动转换为当前版本的VS工程即可。在打开的LightOPC工程中,我们删掉工程对“unilog.lib”和“stunilog.lib”两个文件的引用:
按住“CTRL”键,鼠标左键点选两个LIB文件后,鼠标右键菜单选择“移除”后即可完成该操作。接着我们需要下载opc头文件,有两个地方可以下载,一个是gtihub:
另一个是cnblogs:https://www.cnblogs.com/opcconnect/archive/2010/12/20/1911604.html,在这里博主提供的头文件比github多了生成DCOM代理/存根文件必须的IDL文件:
http://files.cnblogs.com/opcconnect/OpcInclude.zip
其实你如果理解了DCOM开发,不需要IDL也可以。因为github下载的头文件中除了dlldata.c,其它生成DCOM代理/存根所需的文件都已经有了,而dlldata.c与我们前面的DCOM样例生成的dlldata.c几乎完全一样,我们可以直接把dlldata.c复制过来,稍微改动就可以直接使用。
下载完毕后,在VS解决方案的根目录下建立一个“opc”目录,把这些文件复制到该目录下:
然后调整LightOPC的工程属性:
最后还是增加“后期生成事件”,把生成的两个文件分别复制到我们先前建立的LIB和BIN文件夹下:
copy $(SolutionDir)$(Configuration)\lightopc.dll $(SolutionDir)bin\lightopc.dll
copy $(SolutionDir)$(Configuration)\lightopc.lib $(SolutionDir)lib\lightopc.lib
以上配置的前提是“unilog”和“opc”两个文件夹均与“LightOPC”工程在同一个解决方案根目录下,否则你必须根据你自己的路径设置调整工程属性的“包含目录”和“库目录”。以上配置完成后,直接编译即可生成相应的LIB和DLL文件。
以上完成的是VS2010下的32位DEBUG版本LightOPC库的编译,如果我们想使用VS2015编译成64位的LightOPC库,除了以上工作外还需要多做一些额外的工作。首先鼠标右键点选LightOPC的工程属性,然后选择“配置管理器”:
选择“新建”,按照下图所示进行设置,指定VS建立64位的OPC库:
由于我们指定建立64位的库,所以VS的生成输出目录中间多了一个平台目录,因此上面所有涉及目录的配置都需要在“$(SolutionDir)”和“$(Configuration)”之间增加“$(Platform)\”。比如DLL文件的输出目录改为:
“后期生成事件”的两条复制指令则改为:
copy $(SolutionDir)$(Platform)\$(Configuration)\lightopc.dll $(SolutionDir)bin\lightopc.dll
copy $(SolutionDir)$(Platform)\$(Configuration)\lightopc.lib $(SolutionDir)lib\lightopc.lib
所有与此有关的设置都按如上方式调整。最后,打开“loserv.h”文件,找到第147行如下一句:
int (*wstrncmp)(const loWchar *, const loWchar *, unsigned);
将其改为:
int (*wstrncmp)(const loWchar *, const loWchar *, size_t);
更改这一句的目的是解决编译器报“类型转换”错误的问题。至此,64位库的调整也已完毕,直接编译就能得到64位OPC库。
接下来的工作是编译LightOPC自带的OPC服务器样例,按照同样的方法我们把“samp_exe”工程添加到我们的解决方案中:
然后移除掉工程原先引入的“unilog.lib”和“lightopc.lib”两个库文件。接着按照图示配置工程属性:
1、“包含目录”和“库目录”分别为:“..\unilog;..\opc;$(IncludePath)”、“..\lib;$(LibraryPath)”;
2、“链接器”的“附加依赖项”增加“unilog.lib”和“lightopc.lib”两个库文件;
3、“链接器”的“输出文件”改为:
$(SolutionDir)$(Configuration)\lopcsamp.exe
4、“后期生成事件”增加:
copy $(SolutionDir)$(Configuration)\lopcsamp.exe $(SolutionDir)bin\lopcsamp.exe
如此即可成功编译生成“lopcsamp.exe”文件,这个文件就是OPC服务器。如果使用VS2015编译该工程的话,需要调整的工程属性与LightOPC工程一致,另外代码部分还需要修改“sample.cpp”的第42行:
unilog *log; /* logging entry */
这里声明的这个变量产生了重定义冲突,需要将其名称改为任意其它名称而不是现在的“log”,比如“logg”,然后我们把所有使用该变量的语句都调整为改后的名称再编译即可成功。
到这里我们已经完成了EXE组件的开发,接下来就是得到OPC的代理/存根文件了。继续在刚才的解决方案下,新建一个“Win32项目”,项目名称为“OPCDASrvProxyStupGenerator”,随便选择一个项目类型,建立一个“空项目”,然后我们把“opc”目录下的“opcda.idl”文件添加到工程中,鼠标右键选择该文件,右键菜单选择“编译”,即可生成建立OPC代理/存根所需的源文件:
接下来就是继续在VS的解决方案中建立OPC代理/存根动态库工程了,依然是“Win32项目”,项目名称为“OPCDASrvProxyStub”,项目类型为“DLL”、“空项目”。工程建立完毕,我们接着把刚才生成的那几个文件添加到工程中:
opcda_h.h
dlldata.c
opcda_i.c
opcda_p.c
同时,我们把先前编写iDCOMTestSrv时VS帮我们生成的“iDCOMTestSrvps.def”复制到当前工程目录下,并将其重命名为“OPCDASrvProxyStub.def”,然后将其添加到工程中:
接着配置工程属性:
1、“C/C++”->“预处理器定义”改为“WIN32;_DEBUG;REGISTER_PROXY_DLL;%(PreprocessorDefinitions)”,在这里重点是“REGISTER_PROXY_DLL”宏,它显式地告诉VS要生成代理/存根DLL文件,而不是普通的DLL;
2、“链接器”的“附加依赖项”改为“rpcns4.lib;rpcrt4.lib;%(AdditionalDependencies)”,添加的两个库文件为RPC支持库,这是远程访问DCOM组件必须要用到的库;
3、“链接器”的“模块定义文件”录入“OPCDASrvProxyStub.def”;
4、“后期生成事件”增加:
copy $(SolutionDir)$(Configuration)\$(ProjectName).dll $(SolutionDir)bin\$(ProjectName).dll
配置完成即可编译成功,顺利得到OPC的代理/存根文件。至此我们已经成功得到OPC样例服务器相关的所有文件,接下来就是将LightOPC库提供的测试服务器部署到远程机器上了。首先把OPC服务器相关的程序文件复制到远程机器上:
lopcsamp.exe、OPCDASrvProxyStub.dll、lightopc.dll、unilog.dll
然后注册OPC服务和代理/存根文件。在OPC服务器文件所在的目录下以管理员身份打开控制台,先后输入如下两条指令进行注册:
lopcsamp.exe/r
c:\Windows\system32\regsvr32.exe OPCDASrvProxyStub.dll
在这里还要再次提醒,依然是32位库和64位库的问题,如果你生成的是32位的OPC服务器组件,那么注册代理/存根文件时,要把上面的“c:\Windows\system32”换成“c:\Windows\SysWOW64”才可。我注册的是VS2015版本的64位Release库,所以使用的是64位版本的“regsvr32.exe”。“lopcsamp.exe”文件的注册不需要区分这个问题。
两条指令的执行结果如下图所示:
至此LightOPC提供的样例服务器注册成功。该服务器的名字我们可以在源码中找到,在VS的“解决方案资源管理器”一栏,打开“exe_sample”工程的源文件“sample.cpp”,在“WinMain()”函数之上定义了该样例服务器的名称:
1 …… …… 2 3 /*************************************************************************** 4 EXE-specefic stuff 5 ***************************************************************************/ 6 7 const char eClsidName[] = "LightOPC Sample server (exe)"; //* OPC服务器名称 8 const char eProgID[] = "OPC.LightOPC-exe"; //* OPC在注册表中的注册ID 9 10 HMODULE server_module = 0; 11 static int mymine(HINSTANCE hInstance, int argc, char *argv[]); 12 13 extern "C" 14 int APIENTRY WinMain(HINSTANCE hInstance, 15 HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) 16 { 17 static char *argv[3] = { "fake.exe", NULL, NULL }; 18 argv[1] = lpCmdLine; 19 return mymine(hInstance, 2, argv); 20 } 21 22 extern "C" int main(int argc, char *argv[]) 23 { 24 return mymine(GetModuleHandle(NULL), argc, argv); 25 } 26 27 …… ……
知道了名称,我们就可以打开“组件服务”管理器配置该组件的相关属性了。控制台输入:mmc comexp.msc(32位库则是“mmc comexp.msc /32”),打开“组件服务”管理器,找到“LightOPC Sample server (exe)”,鼠标右键打开组件“属性”窗口,按如下图示进行配置:
其它未涉及的全部采用“默认”即可。关于组件服务的其它设置选项以及如何分配用户,请参考该指南第一篇《基于第三方开源库的OPC服务器开发指南(1)——OPC与DCOM》。至此,服务器部署完毕。接下来就需要我们编写一个OPC客户端测试一下这个服务器了,详细说明请阅《基于第三方开源库的OPC服务器开发指南(3)——OPC客户端》。
本文源码下载地址: