COM的起源与OPC的渊源

从1993年微软推出COM组件技术至今已二十多年了,新出校门的小鲜肉一般都不会接触到了,也只有那些富有情怀的老炮儿可能不时唠叨二句。也赶巧了,工控网络里的技术与时俱进的步伐总是慢些,所以在工控领域COM不会很快退场,还是有它的用武之地的,再活个二十年没问题,毕竟所有的厂商都有现成的产品支持它,也倒逼那些小鲜肉来学些老古董了。

1993年前,那还不是微软独大的时候,算得上战国时代,局域网里还是Novell的天下,互联网还没成型,OOP也没有大行其道。局域网内互联不能执行自个的私有标准,所以就有了在TCP层面上定义统一协议的需要。在80年代,当时的网络先驱如阿波罗(Apollo)、数字设备公司(DEC)联合其它家已推出了基于TCP的RPC(Remote Procedure Call),即后来的DCE(Disctributed Computing Environment) RPC,抗衡升阳(Sun Micorsystem)倡导的ONC RPC。微软90年代在DCE RCP上推出自己的RPC方案,即MS RPC和其兼容。不管是哪个厂商支持的RPC,首要的是有一个统一格式的接口,这就是IDL(Interface Definition Language)起到的规范作用。IDL按事先约定好的INTERFACE格式来定义具体的接口函数,同时拥有唯一的身份号(uuid), 然后各厂家可以跨平台具体执行。比如微软的客户端可以通过RPC来调用Linux服务端的函数,反之亦然。下面来看一下OPC DA里的IDL文件定义的接口IOPCServer(摘自opcda.idl),

[

    object,

    uuid(39c13a4d-011e-11d0-9675-0020afd8adb3),

    pointer_default(unique)

]

interface IOPCServer : IUnknown

{

    HRESULT AddGroup(

        [in, string]        LPCWSTR    szName,

        [in]                BOOL       bActive,

        [in]                DWORD      dwRequestedUpdateRate,

        [in]                OPCHANDLE  hClientGroup,

        [unique, in]        LONG*      pTimeBias,

        [unique, in]        FLOAT*     pPercentDeadband,

        [in]                DWORD      dwLCID,

        [out]               OPCHANDLE* phServerGroup,

        [out]               DWORD*     pRevisedUpdateRate,

        [in]                REFIID     riid,

        [out, iid_is(riid)] LPUNKNOWN* ppUnk

    );

    HRESULT GetErrorString( 

        [in]          HRESULT dwError,

        [in]          LCID    dwLocale,

        [out, string] LPWSTR* ppString

    );

    HRESULT GetGroupByName(

        [in, string]        LPCWSTR    szName,

        [in]                REFIID     riid,

        [out, iid_is(riid)] LPUNKNOWN* ppUnk

    );

    HRESULT GetStatus( 

        [out] OPCSERVERSTATUS** ppServerStatus

    );

    HRESULT RemoveGroup(

        [in] OPCHANDLE hServerGroup,

        [in] BOOL      bForce

    );

    HRESULT CreateGroupEnumerator(

        [in]                OPCENUMSCOPE dwScope, 

        [in]                REFIID       riid, 

        [out, iid_is(riid)] LPUNKNOWN*   ppUnk

    );

}

这个接口有唯一的身份号(uuid),同时包含了AddGroup()等接口函数。这样各厂商都可以基于此接口来开发OPC DA相关产品。如果把OPC基金会作为一个“厂商”,它的SDK中提供的样品程序就是它的产品。下面选一个它的3.0样品服务端程序中的IDL定义来看看是如何运用上面接口的(摘自OpcDa30Server.idl),

[

 uuid(ED0E3ADD-0CF5-47cd-A1AB-522E5E75D5CD),

 version(1.0)

]

library OpcDa30ServerLib

{

 importlib("stdole32.tlb");

 importlib("stdole2.tlb");

    

 [

  uuid(ED0E3ADE-0CF5-47cd-A1AB-522E5E75D5CD),

        helpstring("OPC Data Access 3.00 Source Server")

 ]

 coclass OpcDa30Server

 {

   [default] interface IOPCServer;

        [source]  interface IOPCShutdown;

 };

这里它定义了一个类(coclass)来具体执行接口IOPCServer内的函数,该类有自己的名字和唯一身份号(uuid)。这些IDL文件不能直接使用,不同厂家根据自己的平台进行编译以便进一步使用。在微软平台上,MIDL.exe负责编译,产生四种文件:头文件(.h),接口文件(_i.c),代理或票根文件(_p.c),和类型库文件(.tlb)。有了这些文件C编译器才可以进一步产生dll或者exe文件,如和上面IDL相对应产生的opcproxy.dll和OpcDa30Server.exe。dll文件不能直接调用,因为它内部没有向外界宣示出口函数(如C的def文件下定义的函数),但都有内嵌的tlb。我们可以使用内嵌tlb的dll或者独立的tlb来进行注册(注册工具regsvr32.exe)。上面接口的dll会在注册表下的Interface条目下出现(搜下接口的uuid号就知道它的确切位置),exe文件会在注册表下的CLSID中出现(搜下类的uuid号就知道确切位置)。注册完后我们就可以按照COM的要求来调用它们了。tlb是跨语言的,只要是在微软的平台下就可以使用,如早期的Pascal,VB等可以使用同一tlb产生不同语言的应用。Linux平台有自己的工具来编译IDL,但没有tlb文件产生。说到这里,COM的本质可以理解为设定了一个框架,让用户不用关心底层的RPC的具体形成而可以方便地使用。

下面再看一下具体的COM调用,还是以上面的OpcDa30Server.exe为例。它是一个服务端的程序,在客户端如何调用?

#include "stdafx.h"
#include "OpcDa30Server_i.c"

#import "OpcDa30Server.tlb" no_namespace, raw_interfaces_only

int main()
{

	CoInitialize(NULL);
	IOPCServer *pIOPCServer = (IOPCServer *)malloc(sizeof(IOPCServer));

	HRESULT hr = CoCreateInstance(CLSID_OpcDa30Server, NULL, CLSCTX_INPROC_SERVER, LIBID_OpcDa30ServerLib, (LPVOID *)pIOPCServer);

	if (FAILED(hr) || (pIOPCServer == nullptr))
	{
		_com_error err(hr);
		_tprintf(L"instance %s", err.ErrorMessage());
		goto End;
	}

	tagOPCSERVERSTATUS *ppServerStatus = (tagOPCSERVERSTATUS *)malloc(sizeof(tagOPCSERVERSTATUS));;
	hr = pIOPCServer->GetStatus(&ppServerStatus);

	if (FAILED(hr) || ppServerStatus == nullptr)
	{
		_com_error err(hr);
		_tprintf(L"interface %s", err.ErrorMessage());
	}

End:

	if (ppServerStatus) 
		free(ppServerStatus);
	
	if (pIOPCServer) {
		(*pIOPCServer).Release();
		free(pIOPCServer);
	}

	CoUninitialize();

	return 0;
}

这里要说明的是,CLSID_OpcDa30Server和LIBID_OpcDa30ServerLib是定义在OpcDa30Server_i.c文件中,IOPCServer是定义在OpcDa30Server.tlb,因为OpcDa30Server.idl中进口了opcda.idl(import "opcda.idl")。LIBID_OpcDa30ServerLib是个接口ID,CLSID_OpcDa30Server是执行该接口的类ID,通过CoCreateInstance()函数调用获得了该类实例的指针pIOPCServer,从而我们可以进一步来调用该接口下的函数,比如GetStatus()。这只是一个简单的客户端样品程序,前提是你要预先知道服务端的类ID(CLSID_OpcDa30Server)。那么我们如果预先不知道类ID,又该如何调用呢?请看下篇的OPC枚举服务说明

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值