上篇依然是关于客户端的,现在转向服务端。本文以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项目)
感兴趣的请不吝赐教!