OPC服务端软件分析

亡梦爱人 提交于 2020-01-01 14:24:21

上篇依然是关于客户端的,现在转向服务端。本文以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的开发过程写成课件,和博文相比更加深入,完整,实战性更强。当然,要花更多时间,也需要大家捧场,先看有无需求(请留言呦),计划如下:

课件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项目)

以上只是初步设想,请大家不吝赐教!

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!