OPC服务端软件分析

上篇依然是关于客户端的,现在转向服务端。本文以OPC基金会发布的OPC DA 3.00 Sample Server为分析对象,分析它的结构,为自己程序切入找准方向。

在开始之前需要分享一个知识点,即COM是如何被创建的。在客户端使用CoCreateInstance后,服务端是怎样帮你创立一个实例的?这就是IClassFactory的职责,所有COM的调用都是通过它的接口来获得一个具体的执行它接口类的实例。

OPC服务端的IClassFactory执行类是定义在COpcClassFactory.h中,

我们需要仔细审视下其中的CreateInstance函数,具体见下,

// CreateInstance
    STDMETHODIMP CreateInstance(
        IUnknown* pUnkOuter,
        REFIID    riid,
        void**    ppvObject)
    {
        *ppvObject = NULL;

        // aggregation is not supported.
        if (pUnkOuter != NULL)
        {
            return CLASS_E_NOAGGREGATION;
        }

        OPC_ASSERT(m_pClassInfo != NULL && m_pClassInfo->pfnCreateInstance != NULL);

        // create instance - adds one reference.
        IUnknown* ipUnknown = NULL;

		HRESULT hResult = m_pClassInfo->pfnCreateInstance(&ipUnknown, m_pClassInfo->pClsid);
        
        if (FAILED(hResult))
        {
            return hResult;
        }

        // query desired interface - adds another reference.
        hResult = ipUnknown->QueryInterface(riid, ppvObject);

        if (FAILED(hResult))
        {
            ipUnknown->Release();
            return hResult;
        }

        // release one reference.
        ipUnknown->Release();
        return S_OK;
    }

在这里最主要的就是这个函数指针m_pClassInfo->pfnCreateInstance,它负责创建一个OPC服务端的实例并返回。它的定义也很简单,在COpcClassFactory.h中,只要满足(IUnknown**, const CLSID*)形式的函数就行了。

pfnCreateInstance又是定义在TOpcClassTableEntry中的,我们需要看下这个函数指针是怎样定义的,见如下二图,

从这里我们可以清晰过了看到函数指针pfnCreateInstance实际是指向了COpcDa30Server::CreateInstance,鉴定完毕。

COpcDa30Server::CreateInstance又具体干了些什么?见如下二图,

我们看到在COpcDa30Server::CreateInstance中对COpcDa30Server(new xClass())进行了实例创建,返回了它的IID_IUnknown实例指针给COpcClassFactory中的m_pClassInfo->pfnCreateInstance,然后再通过QueryInterface从IID_IUnknown实例指针获得指向CLSID位置的指针返回给客户端,至此服务端创建任务完毕。

这只是初步的创建,还需要弄懂程序是如何和模拟装置连接上的,这样大家如果开发个自己的OPC服务器软件,也可以直接找到切入点来修改。

其实答案已在上图中体现,我再重复一下,

static HRESULT __stdcall CreateInstance(IUnknown** ippUnknown, const CLSID* pClsid) \
{ \
    if (ippUnknown == NULL) return E_POINTER; \
    *ippUnknown = NULL; \
\
    xClass* pObject = new xClass(); \
\
	pObject->m_pClsid = pClsid; \
\
    HRESULT hResult = pObject->FinalConstruct(); \
\
    if (FAILED(hResult)) \
    { \
       pObject->Release(); \
       return hResult; \
    } \

这里pObject->FinalConstruct中的pObject是COpcDa30Server,看一下相应的call stack,

 

Call stack清晰地显示出这么一个顺序,即COpcClassFactory::CreateInstance->COpcDa30Server::CreateInstance->COpcDa30Server::FinalConstruct->Initialize,直到产生新的COpcDaDevice实例。所以如果你要开发自己的PLC、DCS之类的OPC服务端,这里就是一个很好的切入点。

一般服务端是作为一个独立的exe可执行程序的,调试起来比较麻烦,因为它有自己的address space。研究过程中发现该样本程序也可以作为一个独立的dll来编译,这点让我喜出望外,我可以在客户端里直接调用服务端的dll。当然要做些改动,加上相应预编译宏,留出出口函数等等就可编译成dll文件,见上图在我的客户端Opc3Tool的地址空间里就可以调用OpcDa30Server.dll,感觉很爽。

勒紧安全带,下篇即将进入OPC的包深度解析。

题外话-——目前已写了十一篇与OPC有关的博文,就是想把OPC的开发过程写成课件,和博文相比更加深入,完整,实战性更强。计划如下:

课件1:COM入门(COM介绍,COM的内部调用过程,COM作为exe和dll的创建过程及区别,最后是三个完整的VC++2017项目实现 - COM作为exe的项目,COM作为dll的项目和COM作为客户端的项目)

课件2:OPC IDL文件分析(用OPC的Common和DA的IDL文件作为范本,分析它的内容帮助大家能读懂,也了解RPC是怎样利用IDL的,最重要的是通过IDL文件掌握相关的COM内存管理原则,这是写好COM程序的基础)

课件3:OPC DA服务端分析(类似于本博文,但更细致透彻,提供二个完整的VC++2017项目 - 一个是exe项目,另一个是dll项目)

课件4:OPC DA客户端分析(提供完整的原创DA全功能的客户端程序,作为一个完整的VC++2017项目)

课件5:OPC HDA服务端分析(分析OPC HDA服务端的设计,帮助读者快速理解其架构,找准自己HDA服务端程序的最佳切入点)

课件6:OPC HDA客户端分析(提供完整的原创HDA客户端程序,使读者快速上手,作为一个完整的VC++2017项目)

课件7:OPC AE服务端分析(分析OPC AE服务端的设计,快速理解其架构,找准自己AE服务端程序的最佳切入点)

课件8:OPC AE客户端分析(提供完整原创的AE客户端程序,使读者快速上手,作为一个完整的VC++2017项目)

感兴趣的请不吝赐教!

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值