上篇谈了OPC和COM的渊源,现在继续谈下如何找到服务器。
今天要说的是在每个服务器上必装的一个来自OPC基金会的应用,OpcEnum.exe。当一个客户端程序要和服务器沟通时第一步就是要问,“嘿,你那有我要的东西吗?有装OPC服务器吗?”。这个程序就负责回答这个问题。
看一下OPCEnum.exe中IDL定义的唯一一个类OpcServerList。它执行了opccomn.idl中的两个接口,IOpcServerList2和IOpcServerList。
[
uuid(13486D43-4821-11D2-A494-3CB306C10000),
version(1.1),
helpstring("OpcEnum 1.1 Type Library")
]
library OpcEnumLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
[
uuid(13486D51-4821-11D2-A494-3CB306C10000),
helpstring("OpcEnum Class")
]
coclass OpcServerList
{
[default] interface IOpcServerList2;
interface IOpcServerList;
};
};
在这个类的具体执行中,这二个接口都被照顾到了,见OpcSeLst.cpp如下(相关非全部),
class ATL_NO_VTABLE COpcServerList :
public CComObjectRootEx,
public CComCoClass,
public IOPCServerList,
public IOPCServerList2
{
// IOpcServerList
public:
STDMETHOD(EnumClassesOfCategories)(
/*[in]*/ ULONG cImplemented,
/*[in,size_is(cImplemented)]*/ CATID rgcatidImpl[],
/*[in]*/ ULONG cRequired,
/*[in,size_is(cRequired)]*/ CATID rgcatidReq[],
/*[out]*/ IEnumCLSID** ppenumClsid);
// IOpcServerList2
public:
STDMETHOD(EnumClassesOfCategories)(
/*[in]*/ ULONG cImplemented,
/*[in,size_is(cImplemented)]*/ CATID rgcatidImpl[],
/*[in]*/ ULONG cRequired,
/*[in,size_is(cRequired)]*/ CATID rgcatidReq[],
/*[out]*/ IOPCEnumGUID** ppenumClsid);
至此便明白了,只要能获得OpcServerList的实例,必然有IOpcServerList2和IOpcServerList的接口,下面只要根据需要调用其中一个EnumClassesOfCategories就行了。那么话又说回来,EnumClassesOfCategories是如何帮我找到OPC服务器的呢?继续看,相关非全部的程序,
STDMETHODIMP COpcServerList::EnumClassesOfCategories2(
/*[in]*/ ULONG cImplemented,
/*[in,size_is(cImplemented)]*/ CATID rgcatidImpl[],
/*[in]*/ ULONG cRequired,
/*[in,size_is(cRequired)]*/ CATID rgcatidReq[],
/*[out]*/ IEnumCLSID** ppenumClsid)
{
ICatInformationPtr pCatInfo;
HRESULT hr = pCatInfo.CreateInstance(CLSID_StdComponentCategoriesMgr);
return pCatInfo->EnumClassesOfCategories(cImplemented, rgcatidImpl,
cRequired, rgcatidReq, ppenumClsid);
}
看到这里就明白了,先获得个微软COM提供的关于CLSID_StdComponentCategoriesMgr的实例,然后根据输入的CATID返回一个枚举实例,你所要的OPC服务器实例尽在该枚举中了。注意CATID可以是多个,如基金会定义的CATID_OPCDAServer10/20/30(相应的uuid:{63D5F430-CFE4-11D1-B2C8-0060083BA1FB}, {63D5F432-CFE4-11D1-B2C8-0060083BA1FB} 和{CC603642-66D7-48F1-B69A-B625E73652D7})。
下面看下Matrikon的实例,很明显地,上述三个CATID都出现了,也就是说该服务器注册时注册了三个CATID,也说是表明本程序执行了基金会定义的DA规范1.0到3.0的接口函数。
这里还发现了另外二个CATID,{7DE5B060-E089-11D2-A5E6-000086339399}和{58E13251-AC87-11D1-84D5-00608CB8A7E9}。这是谁的CATID?查一下IDL文件,在opc_ae.idl中找到如下的,
//==============================================================================
// Category ID declaration (defined as an interface to ensure they show up in the typelib).
[uuid(58E13251-AC87-11d1-84D5-00608CB8A7E9)] interface OPCEventServerCATID : IUnknown {}
cpp_quote("#define CATID_OPCAEServer10 IID_OPCEventServerCATID")
在opchda.idl中找到了,
//==============================================================================
// Category ID declarations (defined as interfaces to ensure they show up in the typelib).
[uuid(7DE5B060-E089-11d2-A5E6-000086339399)] interface CATID_OPCHDAServer10 : IUnknown {}
cpp_quote("#define CATID_OPCHDAServer10 IID_CATID_OPCHDAServer10")
至此五个CATID都清楚了,也就是Matrikon实现了DA,HDA和AE的规范,如下图所见和我们的分析一样,遗憾的是它没有执行security方面的IDL,是要你出银子的。这方面可以找我进一步咨询也行。
最后来点干货,看看是如何用到我刚才的分析,
HRESULT enumServerList(wchar_t hostname[], MULTI_QI MultiQI[], DWORD count)
{
// initialize server security info here if required.
COSERVERINFO ServerInfo = { NULL };
ServerInfo.pwszName = hostname;
HRESULT hr = CoCreateInstanceEx(CLSID_OpcServerList, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER, &ServerInfo, count, MultiQI);
if (FAILED(hr))
{
OLECHAR *bstrGuid;
StringFromCLSID(CLSID_OpcServerList, &bstrGuid);
_com_error err(hr);
LPCTSTR errMsg = err.ErrorMessage();
_tprintf(_T("enumServerList failed: %s for interface %S\n"), errMsg, bstrGuid);
CoTaskMemFree(bstrGuid);
}
return hr;
}
这段主要就是获得一个在服务器端的关于CLSID_OpcServerList的实例,CLSID_OpcServerList是定义在OpcEnum_i.c中的,
MIDL_DEFINE_GUID(CLSID, CLSID_OpcServerList,0x13486D51,0x4821,0x11D2,0xA4,0x94,0x3C,0xB3,0x06,0xC1,0x00,0x00);
数值和OpcEnum.idl中一模一样,说白了就是要获得一个OpcServerList的实例,从OpcEnum.exe中。
[
uuid(13486D51-4821-11D2-A494-3CB306C10000),
helpstring("OpcEnum Class")
]
coclass OpcServerList
下篇给出个自产的用C和COM写的获取OPC服务器的示例,望有所收获!