下载源代码 /*@**#---2003-10-29 21:33:44 (tulip)---#**@ #include "interface.h"*/ #include "MathCOM.h"//新增加的,以替换上面的东东 class CMath : public ISimpleMath, public IAdvancedMath { private: ULONG m_cRef; private: int calcFactorial(int nOp); int calcFabonacci(int nOp); public: CMath(); //IUnknown Method STDMETHOD(QueryInterface)(REFIID riid, void **ppv); STDMETHOD_(ULONG, AddRef)(); STDMETHOD_(ULONG, Release)(); // ISimpleMath Method STDMETHOD (Add)(int nOp1, int nOp2,int * pret); STDMETHOD (Subtract)(int nOp1, int nOp2,int *pret); STDMETHOD (Multiply)(int nOp1, int nOp2,int *pret); STDMETHOD (Divide)(int nOp1, int nOp2,int * pret); // IAdvancedMath Method STDMETHOD (Factorial)(int nOp,int *pret); STDMETHOD (Fabonacci)(int nOp,int *pret); };2) Math.cpp文件 /*@**#---2003-10-29 21:32:35 (tulip)---#**@ #include "math.h" STDMETHODIMP CMath::QueryInterface(REFIID riid, void **ppv) {// 这里这是实现dynamic_cast的功能,但由于dynamic_cast与编译器相关。 if(riid == IID_ISimpleMath) *ppv = static_cast<ISimpleMath *>(this); else if(riid == IID_IAdvancedMath) *ppv = static_cast<IAdvancedMath *>(this); else if(riid == IID_IUnknown) *ppv = static_cast<ISimpleMath *>(this); else { *ppv = 0; return E_NOINTERFACE; } //这里要这样是因为引用计数是针对组件的 reinterpret_cast<IUnknown *>(*ppv)->AddRef(); return S_OK; } STDMETHODIMP_(ULONG) CMath::AddRef() { return ++m_cRef; } STDMETHODIMP_(ULONG) CMath::Release() { // 使用临时变量把修改后的引用计数值缓存起来 ULONG res = --m_cRef; // 因为在对象已经销毁后再引用这个对象的数据将是非法的 if(res == 0) delete this; return res; } STDMETHODIMP CMath::Add(int nOp1, int nOp2,int * pret) { *pret=nOp1+nOp2; return S_OK; } STDMETHODIMP CMath::Subtract(int nOp1, int nOp2,int * pret) { *pret= nOp1 - nOp2; return S_OK; } STDMETHODIMP CMath::Multiply(int nOp1, int nOp2,int * pret) { *pret=nOp1 * nOp2; return S_OK; } STDMETHODIMP CMath::Divide(int nOp1, int nOp2,int * pret) { *pret= nOp1 / nOp2; return S_OK; } int CMath::calcFactorial(int nOp) { if(nOp <= 1) return 1; return nOp * calcFactorial(nOp - 1); } STDMETHODIMP CMath::Factorial(int nOp,int * pret) { *pret=calcFactorial(nOp); return S_OK; } int CMath::calcFabonacci(int nOp) { if(nOp <= 1) return 1; return calcFabonacci(nOp - 1) + calcFabonacci(nOp - 2); } STDMETHODIMP CMath::Fabonacci(int nOp,int * pret) { *pret=calcFabonacci(nOp); return S_OK; } CMath::CMath() { m_cRef=0; } 1.2 COM组件调入大致过程 创建过程示意图如下: 1) COM库初始化 使用CoInitialize COM API函数(客户端)使组件加入套间,关于套间的概念我们以后再讲。(客户端) 2)激活COM(客户端) 3) 通过注册表项将对应的DLL调入COM库中(COM运行环境) 4) 调用COM组件内的DllGetClassObject()导出函数(COM组件) 5)通过类厂返回类厂的接口指针(如果客户端是使用CoCreateInstance创建组件时不需要缓存类厂接口指针,则此步在COM运行环境中完成不返回给客户端,同时马上进入第6步。与此相反,客户端会缓存组件类厂接口指针。此方法当客户需要创建多个实例时很有用。)(COM库) 6)通过类厂接口指针调用CreateInstance创建组件实例。 1.3 DllGetClassObject()实现 在MathCOM.cpp里加入下列语句 #include "math.h" #include "MathCOM_i.c" 并将MathCOM.cpp里的DllGetClassObject()修改成如下: /********************************************************************* * Function Declare : DllGetClassObject * Explain : * Parameters : * REFCLSID rclsid -- * REFIID riid -- * void **ppv -- * Return : * STDAPI -- * Author : tulip * Time : 2003-10-29 22:03:53 *********************************************************************/ STDAPI DllGetClassObject(REFCLSID rclsid ,REFIID riid,void **ppv) { if(rclsid == CLSID_MathCom) { return pm_math->QueryInterface(riid,ppv); } return CLASS_E_CLASSNOTAVAILABLE; }1.4 客户端 接下来我们写个客户端程序对此COM进行测试。 新建空的名为TestMathCOM一个win32 Console工程加入到MathCOM workspace中。 在TestMathCOM工程里添加一个名为main.cpp的文件,此文件的内容如下: //main.cpp文件 #include <windows.h> #include "../MathCOM.h"//这里请注意路径 #include "../MathCOM_i.c"//这里请注意路径 #include <iostream> using namespace std; void main(void) { //初始化COM库 HRESULT hr=::CoInitialize(0); ISimpleMath * pSimpleMath=NULL; IAdvancedMath * pAdvancedMath=NULL; int nReturnValue=0; hr=::CoGetClassObject(CLSID_MATHCOM, CLSCTX_INPROC, NULL, IID_ISimpleMath, (void **)&pSimpleMath); if(SUCCEEDED(hr)) { hr=pSimpleMath->Add(10,4,&nReturnValue); if(SUCCEEDED(hr)) cout << "10 + 4 = " <<nReturnValue<< endl; nReturnValue=0; } // 查询对象实现的接口IAdvancedMath hr=pSimpleMath->QueryInterface(IID_IAdvancedMath, (void **)&pAdvancedMath); if(SUCCEEDED(hr)) { hr=pAdvancedMath->Fabonacci(10,&nReturnValue); if(SUCCEEDED(hr)) cout << "10 Fabonacci is " << nReturnValue << endl; } pAdvancedMath->Release(); pSimpleMath->Release(); ::CoUninitialize(); ::system("pause"); return ; }关于如何调试dll请参阅附录A 1.5 小结 到现在我们应该有2个工程和8个文件,具体如下
在此部分中我们已经完成一个可以实用的接近于完整COM组件( 没有完整的地方是还没有实现DllCanUnloadNow() )。我们完成了此COM组件的客户端。 如果你已经创建COM实例的话,你可能会发现在此部分的客户端并不是用CoCreateInstance()来创建COM实例,那是因为我们还没有在此COM组件里实现IClassFactory接口(此接口在下一部分实现)。 通过这个例子,我希望大家明白以下几点: 1) DllGetClassObject()的作用(此函数的详细说明请参阅<<COM本质论>>p86) ,请参看COM组件调入大致过程这一节,同时也请将断点打在DllGetClassObject()函数上,仔细看看他的实现(在没有实现IClassFactory接口的情况下)和他的传入参数。 2) 为什么在这个客户端程序里不使用CoCreateInstance()来创建COM实例而使用CoGetClassObject()来创建COM实例。你可以试着用CoCreateInstance()来创建Cmath,看看DllGetClassObject()的第一参数是什么? 3) 实现IClassFactory接口不是必需的,但应该说是必要的(如何实现请看下一章) 4) 应掌握DllRegisterServer()和DllUnregisterServer()的实现。 5) 客户端在调用COM组件时需要那几个文件(只要由idl文件产生的两个文件) 二、类厂 2.1 回顾 在上节里,我们创建组件实例及得到接口指针的过程如下:首先在客户端的用户调用COM API CoCreateInstance,这个函数调用另一个COM API CoGetClassObject获得组件的类厂接口指针,此时COM库会加载组件DLL(EXE的处理方式稍有不同,我们会在以后的章节中讲解)中的导出函数DllGetClassObject(),获得类厂接口指针后马上调用类厂的方法CreateInstance创建对象实例并通过组件对象的QueryInterface()得到用需的接口指针。 此前我们所实现的并不是真正完整的COM组件,现在我们来实现一个真正的COM组件,实现组件创建机制的核心:类厂 - 创建组件对象的对象。COM为通用化以及在管理上统一和方便,COM规范要求所有标准COM组件都应实现IClassFactory接口(有关IClassFactory的功能各个方法的作用请参阅MSND/Welcome to the msnd library/msnd resource/selected online columns/Dr.GUI online/Dr. GUI on Components, COM, and ATL/part 5) 2.2 增加IClassFactory的实现
2.2.1 MathCOM.cpp #include "math.h" #include "MathCOM_i.c" #include "MathFactory.h" // // 服务器锁, 如果标志为S_OK,就可以卸载当前组件的服务器, // 关于引用计数及服务器跟COM对象的关系,后绪章节再讲解 LONG g_cObjectAndLocks=0; //standard self-registration table const char * g_RegTable[][3]={…………………. ………………………… STDAPI DllGetClassObject(REFCLSID rclsid ,REFIID riid,void **ppv) { if(rclsid==CLSID_MATHCOM) { CMathFactory *pFactory = new CMathFactory; // 创建类厂对象 if(pFactory == NULL) return E_OUTOFMEMORY; /// // 一般情况下COM希望用户只查询IClassFactory, // IClassFactory2及IUnknow HRESULT hr = pFactory->QueryInterface(iid, ppv); return hr; } return CLASS_E_CLASSNOTAVAILABLE; } STDAPI DllCanUnloadNow(void) { return (g_cObjectAndLocks==0)?S_OK:E_FAIL; }2.2.2 MathFactory.h // MathFactory.h: interface for the CMathFactory class. // // #ifndef MATHFACTORY_H #define MATHFACTORY_H #include <unknwn.h> class CMathFactory :public IClassFactory { public: CMathFactory():m_cRef(0){} //IUnknow Method STDMETHODIMP QueryInterface(REFIID,void**); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); //IClassFactory Method STDMETHODIMP CreateInstance(IUnknown * ,REFIID ,void **); STDMETHODIMP LockServer(BOOL fLock); protected: LONG m_cRef; }; #endif2.2.3 MathFactory.cpp // MathFactory.cpp: implementation of the CMathFactory class. // // #include "math.h" #include "MathFactory.h" extern LONG g_cObjectAndLocks; // // Construction/Destruction // STDMETHODIMP_(ULONG) CMathFactory::AddRef(void) { return InterlockedIncrement(&m_cRef); } STDMETHODIMP_(ULONG) CMathFactory::Release(void) { return ::InterlockedDecrement(&m_cRef); } STDMETHODIMP CMathFactory::QueryInterface(REFIID riid,void ** ppv) { *ppv=NULL; if(riid==IID_IUnknown||riid==IID_IClassFactory) { *ppv=static_cast<IClassFactory *>(this); reinterpret_cast<IUnknown*>(*ppv)->AddRef(); return S_OK; } else return (*ppv=0),E_NOINTERFACE; } STDMETHODIMP CMathFactory::CreateInstance(IUnknown * pUnkOuter,REFIID riid,void ** ppv) { *ppv=NULL; // 现在不支持聚合 if(pUnkOuter!=NULL) return CLASS_E_NOAGGREGATION; CMath * pMath=new CMath; if(pMath==NULL) return E_OUTOFMEMORY; HRESULT hr=pMath->QueryInterface(riid,ppv); if(FAILED(hr)) delete pMath; return hr; } STDMETHODIMP CMathFactory::LockServer(BOOL fLock) { if(fLock) ::InterlockedIncrement(&g_cObjectAndLocks); else ::InterlockedDecrement(&g_cObjectAndLocks); return NOERROR; }2.2.4 main.cpp #include <windows.h> #include "../MathCOM.h" #include "../MathCOM_i.c" #include <iostream> using namespace std; void main(void) { //初始化COM库 HRESULT hr=::CoInitialize(0); ISimpleMath * pSimpleMath=NULL; IAdvancedMath * pAdvancedMath=NULL; int nReturnValue=0; //如果还想用CoCreateInstance()得到接口指针,请如下使用先传一个IClassFactory接口 /*************** 这里请注意 *****************/ // 方法一 hr = ::CoCreateInstance(CLSID_MATHCOM, CLSCTX_INPROC, NULL, IID_ISimpleMath, (void*)&pSimpleMath); /// // 这个方法的好处是不需要太多的代码, 让COM来处理真正的类厂创建对象的过程. // 同时这个函数的改进版CoCreateInstanceEx在分布式对象应用中可以一次取回多 // 个接口指针. 避免过多的在网络上浪费时间关于这个函数的用法我也会在以后的 // 章节中讲解. 您也可以参考MSDN或相关书藉 // 方法二 IClassFactory * pClassFactory=NULL; // 类厂接口指针 // 获取对象的类厂接口指针 hr=::CoGetClassObject(CLSID_MATHCOM, CLSCTX_INPROC, NULL, IID_IClassFactory, (void**)&pClassFactory); // 真正创建对象 hr = pClassFactory->CreateInstance(NULL,IID_ISimpleMath,(void**)&pSimpleMath); // 此方法的好处在于可以一次创建多个对象, 不需要 // 下面测试 IDispatch 接口 if(SUCCEEDED(hr)) { hr=pSimpleMath->Add(10,4,&nReturnValue); if(SUCCEEDED(hr)) cout << "10 + 4 = " <<nReturnValue<< endl; nReturnValue=0; } //查询对象实现的接口IAdvancedMath hr=pSimpleMath->QueryInterface(IID_IAdvancedMath, (void **)&pAdvancedMath); if(SUCCEEDED(hr)) { hr=pAdvancedMath->Fabonacci(10,&nReturnValue); if(SUCCEEDED(hr)) cout << "10 Fabonacci is " << nReturnValue << endl; } pAdvancedMath->Release(); pSimpleMath->Release(); ::CoUninitialize(); return ; }2.3 小结 在此部分我们实现了IClassFactory接口,可以说实现了一个真正的COM对象了,我们可以在C++中使用一般的COM客户端方法创建对象,执行方法等。但是不知道用户有没有发现,COM的目标是语言无关,像VB中不支持指针,脚本是解释执行(但是COM需要在编译期间绑定接口调用)。怎么办呢?有没有办法实现呢?方法当然是有的。接下来的一节我不为大家讲解为不支持指针的语言而提供支持的特殊接口IDispatch - 派发接口(也称调度接口)。为上述语言提供一个效率上稍差但灵活性更高的解决方法。接下来这一章让我们实现IDispatch接口,以便在脚本环境中使用我们的COM组件。 三、实现派发接口 为了在脚本语言环境中使用COM组件,COM规范规定要在脚本语言环境使用的COM必须实现IDispatch接口,此时的COM接口被称为派发接口。还记得我们在讲IDL的时个描述接口的内容吗?当时我们并没有设置这个关于接口的说明:custom和dual。前者是自定义接口,也就是从IUnknown派生的接口。后者是从IDispatch派生的接口,当然IDispatch也必须从IUnknown派生。只有当此接口从IDispatch派生时,此COM组件可在不支持指针及脚本语言环境下使用。现在让我们的组件也支持双接口。 3.1 IDispatch接口 IDispatch接口共有四个方法,其中只有比较重要(Invoke)。其它方法我们暂时用不到,就没有实现。如果读者有兴趣自己可以参考相关资料来实现一下。并不是很难。像GetIDsOfName等可以交给ITypeInfo等接口处理。讲到这里可能就复杂了。我们以后再为读者讲解。我们的MathCOM组件也只实现了Invoke方法,其他方法都返回E_NOTIMPL表示没有实现。 关于IDispatch接口请参阅MSND文档或<<COM本质论>>p295页 3.2 支持派发接口的MathCOM组件
3.2.1修改MathCOM.idl // MathCOM.idl : IDL source for MathCOM.dll // // This file will be processed by the MIDL tool to // produce the type library (MathCOM.tlb) and marshalling code. import "oaidl.idl"; import "ocidl.idl"; [ uuid(FAEAE6B7-67BE-42a4-A318-3256781E945A), helpstring("ISimpleMath Interface"), object, dual, // 这个标识我们前面没遇到过。 pointer_default(unique) ] interface ISimpleMath : IDispatch { [id(1)] HRESULT Add([in]int nOp1,[in]int nOp2,[out,retval]int * pret); [id(2)] HRESULT Subtract([in]int nOp1,[in]int nOp2,[out,retval]int * pret); [id(3)] HRESULT Multiply([in]int nOp1,[in]int nOp2,[out,retval] int * pret); [id(4)] HRESULT Divide([in]int nOp1,[in]int nOp2,[out,retval]int * pret); }; [ uuid(01147C39-9DA0-4f7f-B525-D129745AAD1E), helpstring("IAdvancedMath Interface"), object, dual, pointer_default(unique) ] interface IAdvancedMath : IDispatch { [id(1)] HRESULT Factorial([in]int nOp1,[out,retval]int * pret); [id(2)] HRESULT Fabonacci([in]int nOp1,[out,retval]int * pret); }; [ uuid(CA3B37EA-E44A-49b8-9729-6E9222CAE844), version(1.0), helpstring("MATHCOM 1.0 Type Library") ] library MATHCOMLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); [ uuid(3BCFE27E-C88D-453C-8C94-F5F7B97E7841), helpstring("MATHCOM Class") ] coclass MATHCOM { [default] interface ISimpleMath; interface IAdvancedMath; }; };3.2.2 修改math.h文件 在math.h增加IDispatch接口的四个方法声明,并对三个进行简单的实现。修改后的math.h见下 //前面相同 CMath(); //IUnknown Method STDMETHOD(QueryInterface)(REFIID riid, void **ppv); STDMETHOD_(ULONG, AddRef)(); STDMETHOD_(ULONG, Release)(); //IDispatch Method STDMETHOD(GetTypeInfoCount)(UINT * pit){ return E_NOTIMPL;} STDMETHOD(GetTypeInfo)(UINT it,LCID lcid,ITypeInfo **ppti){ return E_NOTIMPL; } STDMETHOD(GetIDsOfNames)(REFIID riid, OLECHAR ** pNames, UINT nNames, LCID lcid, DISPID * pdispids){ return E_NOTIMPL; } STDMETHOD(Invoke)(DISPID id, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pd, VARIANT * pVarResult, EXCEPINFO * pe, UINT *pu); // ISimpleMath Method STDMETHOD (Add)(int nOp1, int nOp2,int * pret); STDMETHOD (Subtract)(int nOp1, int nOp2,int *pret); STDMETHOD (Multiply)(int nOp1, int nOp2,int *pret); STDMETHOD (Divide)(int nOp1, int nOp2,int * pret); //后面相同3.2.3 修改math.cpp文件 增加IDispatch接口中的Invoke方法的实现。修改后的math.cpp文件见下 //前面相同 CMath::CMath() { m_cRef=0; } /********************************************************************* * Function Declare : CMath::Invoke * Explain : IDispatch接口的Invoke方法 * Parameters : * DISPID id -- 方法的调度ID(请与idl中序号相比较) * REFIID riid -- 接口IID * LCID lcid -- 语言ID * WORD wFlags -- * DISPPARAMS * pd -- 传入参数结构(具体结构请参阅MSDN或本质论p294) * VARIANT * pVarResult -- 出参(VARIANT请参阅MSDN文档或<<深入解析ATL>>p56 * EXCEPINFO * pe -- 异常(一般为NULL) * UINT * pu -- * Return : * STDMETHODIMP -- * Author : tulip * Time : 2003-10-30 15:56:37 *********************************************************************/ STDMETHODIMP CMath::Invoke(DISPID id, REFIID riid, LCID lcid,WORD wFlags, DISPPARAMS * pd, VARIANT * pVarResult, EXCEPINFO * pe, UINT * pu) { if(riid==IID_ISimpleMath) { if(1==id) return Add(pd->rgvarg[0].intVal,pd->rgvarg[1].intVal,&pVarResult->intVal); else if (2==id) return Subtract(pd->rgvarg[0].intVal,pd->rgvarg[1].intVal,&pVarResult->intVal); else if (3==id) return Multiply(pd->rgvarg[0].intVal,pd->rgvarg[1].intVal,&pVarResult->intVal); else if (4==id) return Divide(pd->rgvarg[0].intVal,pd->rgvarg[1].intVal,&pVarResult->intVal); else return E_FAIL; } else if (riid==IID_IAdvancedMath) { if(1 == id) return Factorial(pd->rgvarg[0].intVal,&pVarResult->intVal); else if (2 == id) return Fabonacci(pd->rgvarg[0].intVal,&pVarResult->intVal); else return E_FAIL; } else return E_NOINTERFACE; } 在修改后上述三个文件,请重新编译,(如果你在上次注册前已经移动过过工程,请重新注册) 3.2.4 修改main.cpp文件 修改main.cpp文件的目的是通过IDispatch接口中的Invoke方法来测试我们的组件。 修改后的main.cpp文件见下 #include <windows.h> #include "../MathCOM.h" #include "../MathCOM_i.c" #include <iostream> using namespace std; #include <atlbase.h> void main(void) { / // 初始化COM库,这里的真正含义是把COM加入适当的套间, // 关于套间的概念及其它相关概念会在后绪文章中讲解。 HRESULT hr = ::CoInitialize(NULL); ISimpleMath * pSimpleMath=NULL; IAdvancedMath * pAdvancedMath=NULL; IDispatch * pDispatch=NULL; // 派发接口指针 int nReturnValue=0; hr = ::CoCreateInstance(CLSID_MATHCOM, CLSCTX_INPROC, NULL, IID_ISimpleMath, (void*)&pSimpleMath); / // 下面测试IDispatch接口 // pSimpleMath->QueryInterface(IID_IDispatch,(void**)&pDispatch); if(SUCCEEDED(hr)) { cout<<"下面进行IDispatch接口"<<endl; CComVariant varResult; CComVariant varResult2; CComVariant *pvars=new CComVariant[2]; CComVariant *pvars2=new CComVariant[1]; varResult.Clear(); pvars[1]=4; pvars[0]=10; DISPPARAMS disp = { pvars, NULL, 2, 0 }; hr=pDispatch->Invoke(0x1, IID_ISimpleMath, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, &varResult, NULL, NULL); if(SUCCEEDED(hr)) cout << "10 + 4 = " <<varResult.intVal<< endl; varResult2.Clear(); pvars2[0]=10; DISPPARAMS disp2={pvars2,NULL,1,0}; hr=pDispatch->Invoke(0x2, IID_IAdvancedMath, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, &varResult2, NULL, NULL); if(SUCCEEDED(hr)) cout << "10 Fabonacci is " <<varResult2.intVal << endl; cout<<"IDispatch接口测试完毕"<<endl; ::system("pause"); } /* *IDispatch接口测试完毕 */ if(SUCCEEDED(hr)) { hr=pSimpleMath->Add(10,4,&nReturnValue); if(SUCCEEDED(hr)) cout << "10 + 4 = " <<nReturnValue<< endl; nReturnValue=0; } // 查询对象实现的接口IAdvancedMath hr=pSimpleMath->QueryInterface(IID_IAdvancedMath, (void **)&pAdvancedMath); if(SUCCEEDED(hr)) { hr=pAdvancedMath->Fabonacci(10,&nReturnValue); if(SUCCEEDED(hr)) cout << "10 Fabonacci is " << nReturnValue << endl; } pAdvancedMath->Release(); pSimpleMath->Release(); pDispatch->Release(); ::CoUninitialize(); return ; } 四 总结 真的好高兴啊,终于要写总结了。 我们再回顾一下,我们到底干了些什么,我们实现了类厂,实现了一个真正的COM对象,同时为了在不支持指针及脚本环境中使用COM我们又实现了IDispatch派发接口。至于,为什么要实现IDispach,IDispatch是怎么实现的,也就是早期捆绑和晚期捆绑的概念。我们将在下一章中讲解。希望大家看到这里不至于没方向了。不过里面尚有不少你们看到但不知道什么意义,不急。慢慢来,我会在以后各章详细讲述这些概念。现在我想让你们知道一个COM组件大致的创建过程,对COM有一定宏观上的认识。毕竟我的水平有限,没有大师级讲解透彻。我只是将我对COM的一点认识与大家共享一下。希望大家一起进步。 在第一部分,我想大家了解COM的好处。接下来第二部分,我们看到COM核心结构图(接下来所有的例子都是以图1.3为中心的)。在第三部分,我step by step 讲如何实现一个完整的COM组件。 COM原理比较简单,但在实现时比较灵活,这就造成了大多数人感到难以掌握,其实只要抓住COM的本质,其他的东西都比较简单。如果你了解了”**”这个东东,那你懂大部分了。如果你完全搞明白了什么是接口类,实现类,类对象以及他们之间的关系,那你又前进了一大步。 最后希望大家在学习COM的道路能够感受到快乐。以后有空我们一起交流,哈哈!! “COM IS LOVE”。 五 参考资料 |
COM技术初探(三)-- 一个真正的COM
最新推荐文章于 2020-05-11 14:52:38 发布