COM组件的编写

本文提供一个完全用C++实现的进程内(DLL)COM服务器,不要ATL或MFC提供任何支持。用这种方式编写COM对象可以让你深入地洞察到COM处 理进程内服务器的方法以及COM是如何创建类工厂的。利用本文提供的这个简单框架你可以实现很基本的COM组件,如外壳扩展(Shell Extensions)等。如果你在使用过程中发现了任何问题,请将它反馈到vckbase@public.hk.hi.cn。
        以下是用本文所说的方式编写自己的COM对象要经过的步骤:
第一步:写一个头文件,这个头文件包含以下内容:
1、 包含文件comdef.h:

[cpp] view plaincopyprint?
  1. #include <comdef.h>

2、 定义COM服务器的GUID。

[cpp] view plaincopyprint?
  1. _declspec(selectany) GUID CLSID_Mine = { 0xdc186800,0x657f,0x11d4,{0xb0, 0xb5, 0x0, 0x50, 0xba, 0xbf, 0xc9, 0x4}};

3、 给出接口的IID以及这个接口要实现的方法定义。到时客户端会用到这个接口的IID和接口的方法。

[cpp] view plaincopyprint?
  1. interface __declspec(uuid("F614FB00-6702-11d4-B0B7-0050BABFC904")) ImyInterface : public IUnknown
  2. {
  3. STDMETHOD(Square)(long *pVal)PURE;
  4. STDMETHOD(Cube)(long *pVal)PURE;
  5. };

客户端使用此接口:

[cpp] view plaincopyprint?
  1. HRESULT hr;
  2. ImyInterface *pmine=(0);
  3. hr = CoCreateInstance(CLSID_Mine, // COM 服务器的CLSID
  4. NULL, //不支持聚合
  5. CLSCTX_INPROC_SERVER, // 是个DLL
  6. __uuidof(ImyInterface), // 接口的IID
  7. (void**)&pmine);

还有一种方法可以从注册表中获得COM对象的CLSID,就是调用CLSIDFromProgId()函数,不过必须把组件的ProgId传递给这个函数。

第二步:必须为所定义的接口提供实现,本文用的方法是创建一个从接口继承的新类:

[cpp] view plaincopyprint?
  1. //
  2. // 这个类实现单接口ImyInterface ...
  3. //
  4. class CmyInterface : public CComBase<> ,public InterfaceImpl<ImyInterface>
  5. {
  6. public:
  7. CmyInterface();
  8. virtual ~CmyInterface();
  9. // 我们必须要为QueryInterface 编写代码
  10. STDMETHOD(QueryInterface)(REFIID riid,LPVOID *ppv);
  11. // ImyInterface 接口方法
  12. STDMETHOD(Square)(long *pVal);
  13. STDMETHOD(Cube)(long *pVal);
  14. };

模版类InterfaceImpl<>提供接口引用计数的实现。在此我们可以用多接口继承,那样就能在一个COM组件中实现多个接口。

第三步:在完成这个对象之前,我们还要编写Queryinterface和两个接口方法:

[cpp] view plaincopyprint?
  1. STDMETHODIMP CmyInterface::QueryInterface(REFIID riid,LPVOID *ppv)
  2. {
  3. *ppv = NULL;
  4. if(IsEqualIID(riid,IID_IUnknown) IsEqualIID(riid,__uuidof(ImyInterface)))
  5. {
  6. // 因为我们从ImyInterface继承,所以要进行强制类型转换
  7. *ppv = (ImyInterface *) this;
  8. _AddRef(); // 这个方法从某个基类继承而来
  9. return S_OK;
  10. }
  11. return E_NOINTERFACE;
  12. }
  13. STDMETHODIMP CmyInterface::Square(long *pVal)
  14. {
  15. long value = *pVal;
  16. *pVal = value * value;
  17. return S_OK;
  18. }
  19. STDMETHODIMP CmyInterface::Cube(long *pVal)
  20. {
  21. long value = *pVal;
  22. *pVal = value * value * value;
  23. return S_OK;
  24. }

注意这里使用了__uuidof(ImyInterface)来获取接口的IID,这是因为我们已经在第一步中将这个接口关联到了某个uuid。

最后一步:

COM组件的DLLs必须输出一个叫DllGetClassObject的函数。由这个函数为CmyInterface创建类工厂并返回一个对它的引用。然后我们调用CoCreateInstance为进程内COM创建类工厂,接着调用DllGetClassObject。

这个类工厂有一个方法是CreateInstance,由这个方法创建对象并返回对它的引用。

[cpp] view plaincopyprint?
  1. STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppvOut)
  2. {
  3. *ppvOut = NULL;
  4. if (IsEqualIID(rclsid, CLSID_Mine))
  5. {
  6. // 为CmyInterface类声明类工厂
  7. CClassFactory<CmyInterface>
  8. *pcf = new CClassFactory<CmyInterface>;
  9. return pcf->QueryInterface(riid,ppvOut);
  10. }
  11. return CLASS_E_CLASSNOTAVAILABLE;
  12. }

在此我们要检查所请求的CLSID是不是CLSID_Mine,如果不是则返回一个错误代码。
       你可能会问在哪里创建实际的CmyInterface类对象,实际上这是由CClassFactory的模板实例来处理的。以下是CClassFatory的实现:

[cpp] view plaincopyprint?
  1. // CSingleCreator 用于单实例类工厂,这个类为多个CreateObject请求返回相同的对象指针..
  2. template<class comObj>
  3. class CSingleCreator
  4. {
  5. protected:
  6. CSingleCreator():m_pObj(0) {};
  7. comObj *CreateObject()
  8. {
  9. if(!m_pObj)
  10. {
  11. m_pObj = new comObj;
  12. }
  13. return m_pObj;
  14. }
  15. comObj * m_pObj;
  16. };
  17. // CMultiCreator 用于常用类工厂,这个类为每一个CreateObject请求返回新的对象指针..
  18. template<class comObj>
  19. class CMultiCreator
  20. {
  21. protected:
  22. CMultiCreator():m_pObj(0) {};
  23. comObj *CreateObject()
  24. {
  25. return new comObj;
  26. }
  27. comObj * m_pObj;
  28. };
  29. //ClassFactory类实现
  30. // MultiCreator是缺省的类工厂创建者
  31. //这个类实现了接口IclasFactory......
  32. class CClassFactory : public CComBase<>,
  33. public InterfaceImpl<IClassFactory>,
  34. public creatorClass
  35. {
  36. public:
  37. CClassFactory() {};
  38. virtual ~CClassFactory() {};
  39. STDMETHOD(QueryInterface)(REFIID riid,LPVOID *ppv)
  40. {
  41. *ppv = NULL;
  42. if(IsEqualIID(riid,IID_IUnknown) || IsEqualIID(riid,IID_IClassFactory))
  43. {
  44. *ppv = (IClassFactory *) this;
  45. _AddRef();
  46. return S_OK;
  47. }
  48. return E_NOINTERFACE;
  49. }
  50. STDMETHODIMP CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, LPVOID *ppvObj)
  51. {
  52. *ppvObj = NULL;
  53. if (pUnkOuter)
  54. return CLASS_E_NOAGGREGATION;
  55. m_pObj = CreateObject(); // m_pObj 在creatorClass中定义
  56. if (!m_pObj)
  57. return E_OUTOFMEMORY;
  58. HRESULT hr = m_pObj->QueryInterface(riid, ppvObj);
  59. if(hr != S_OK)
  60. {
  61. delete m_pObj;
  62. }
  63. return hr;
  64. }
  65. STDMETHODIMP LockServer(BOOL)
  66. {
  67. return S_OK;    // 未实现
  68. }
  69. };

COM调用CreateInstance创建请求的对象,参数riid指的是所请求的接口IID,如果这个对象支持这个接口,则增加它的引用计数并返回对自身的引用。

关于代码:本文所提出的方法是如何用纯粹的C++编写COM组件的一个大概念。很多方面的细节都省略了。从本文的文字和代码中可以看出用纯C++编写COM组件需要做些什么工作,如果你要用这种方法编写COM组件的话,这些代码只能是抛砖引玉,具体的实现可以在此基础上往下做.......。

COM组件的使用方法

Requirement:
1.创建myCom.dll,该COM只有一个组件,两个接口:

IGetRes--方法Hello(),
IGetResEx--方法HelloEx()

2.在工程中导入组件或类型库

[cpp] view plaincopyprint?
  1. #import "组件所在目录\myCom.dll" no_namespace

[cpp] view plaincopyprint?
  1. #import "类型库所在目录\myCom.tlb"
  2. using namespace MYCOM;

--Method 1-------------------------------------------------------

[cpp] view plaincopyprint?
  1. CoInitialize(NULL);
  2. CLSID clsid;
  3. CLSIDFromProgID(OLESTR("myCom.GetRes"),&clsid);
  4. CComPtr<IGetRes> pGetRes;//智能指针
  5. pGetRes.CoCreateInstance(clsid);
  6. pGetRes->Hello();
  7. pGetRes.Release();//小心哦!!请看最后的“注意”
  8. CoUninitialize();

--Method 2---------------------------------------------------------

[cpp] view plaincopyprint?
  1. CoInitialize(NULL);
  2. CLSID clsid;
  3. HRESULT hr=CLSIDFromProgID(OLESTR("myCom.GetRes"),&clsid);
  4. IGetRes *ptr;
  5. hr=CoCreateInstance(clsid,NULL,CLSCTX_INPROC_SERVER,
  6. __uuidof(IGetRes),(LPVOID*)&ptr);
  7. ptr->Hello();
  8. CoUninitialize();

--Method 3--------------------------------------------------------

[cpp] view plaincopyprint?
  1. CoInitialize(NULL);
  2. HRESULT hr;
  3. CLSID clsid;
  4. hr=CLSIDFromProgID(OLESTR("myCom.GetRes"),&clsid);
  5. IGetRes* ptr;
  6. IGetResEx* ptrEx;
  7. //使用CoCreateClassObject创建一个组件(特别是mutilThreads)的多个对象的时候,效率更高.
  8. IClassFactory* p_classfactory;
  9. hr=CoGetClassObject(clsid,CLSCTX_INPROC_SERVER,
  10. NULL,IID_IClassFactory,
  11. (LPVOID*)&p_classfactory);
  12. p_classfactory->CreateInstance(NULL,__uuidof(IGetRes),(LPVOID*)&ptr);
  13. p_classfactory->CreateInstance(NULL,__uuidof(IGetResEx),(LPVOID*)&ptrEx);
  14. ptr->Hello();
  15. ptrEx->HelloEx();
  16. CoUninitialize();

--Method 4--------------------------------------------------------
       直接从dll中得到DllGetClassObject,接着生成类对象及类实例(这方法可以
使组件不用在注册表里注册,这是最原始的方法,但这样做没什么意义,至少失去了COM
对用户的透明性),不推荐使用.

[cpp] view plaincopyprint?
  1. typedef HRESULT (__stdcall * pfnHello)(REFCLSID,REFIID,void**);
  2. pfnHello fnHello= NULL;
  3. HINSTANCE hdllInst = LoadLibrary("组件所在目录\myCom.dll");
  4. fnHello=(pfnHello)GetProcAddress(hdllInst,"DllGetClassObject");
  5. if (fnHello != 0)
  6. {
  7. IClassFactory* pcf = NULL;
  8. HRESULT hr=(fnHello)(CLSID_GetRes,IID_IClassFactory,(void**)&pcf);
  9. if (SUCCEEDED(hr) && (pcf != NULL))
  10. {
  11. IGetRes* pGetRes = NULL;
  12. hr = pcf->CreateInstance(NULL, IID_IFoo, (void**)&pGetRes);
  13. if (SUCCEEDED(hr) && (pFoo != NULL))
  14. {
  15. pGetRes->Hello();
  16. pGetRes->Release();
  17. }
  18. pcf->Release();
  19. }
  20. }
  21. FreeLibrary(hdllInst);

--Method 5-------------------------------------------------------
       通过ClassWizard利用类型库生成包装类,不过前提是com组件的接口必须是派生自IDispatch,具体方法:
       调出添加类向导(.NET中),选择类型库中MFC类,打开,选择"文件",选择"myCom.dll"或"myCom.tlb",接下来会出来该myCom中的所有接口,选择你想生成的接口包装类后,向导会自动生成相应的.h文件.这样你就可以在你的MFC中像使用普通类那样使用组件了.

[cpp] view plaincopyprint?
  1. CoInitialize(NULL);
  2. CGetRes getRest;
  3. if (getRest.CreateDispatch("myCom.GetRes") != 0)
  4. {
  5. getRest.Hello();
  6. getRest.ReleaseDispatch();
  7. }
  8. CoUninitialize();

--注意--------------------------------------------------------------
       COM中的智能指针实际上是重载了->的类,目的是为了简化引用记数,几不需要程序员显示的调用AddRef()和Release(),但是为什么我们在Method 1中pGetRes.Release(),问题在与,我们的智能指针pGetRes生命周期的结束是在CoUninitialize()之后,CoInitialize所开的套间在CoUninitialize()后已经被关闭,而pGetRes此时发生析构,导致了程序的崩溃,解决这个问题的另一个方法是

[cpp] view plaincopyprint?
  1. CoInitialize(NULL);
  2. CLSID clsid;
  3. CLSIDFromProgID(OLESTR("myCom.GetRes"),&clsid);
  4. {
  5. CComPtr<IGetRes> pGetRes;//智能指针
  6. pGetRes.CoCreateInstance(clsid);
  7. pGetRes->Hello();
  8. }
  9. CoUninitialize();

--------------------------------------------------------------------

以上就是COM的5种方法,当然具体怎么使用还是在于程序的环境,加以琢磨....

http://www.yesky.com/dev/382/2019882.shtml

========================================================文二===================================================

本文提供一个完全用C++实现的进程内(DLL)COM服务器,不要ATL或MFC提供任何支持。用这种方式编写COM对象可以让你深入地洞察到COM处理进程内服务器的方法以及COM是如何创建类工厂的。利用本文提供的这个简单框架你可以实现很基本的COM组件,如外壳扩展(Shell Extensions)等。

以下是用本文所说的方式编写自己的COM对象要经过的步骤:

第一步:写一个头文件,这个头文件包含以下内容:

1、 包含文件comdef.h:#include <comdef.h>。

2、 定义COM服务器的GUID。

  1. _declspec(selectany) GUID CLSID_Mine = { 0xdc186800,
  2. 0x657f,
  3. 0x11d4,
  4. {0xb0, 0xb5, 0x0, 0x50, 0xba, 0xbf, 0xc9, 0x4}
  5. };

3、 给出接口的IID以及这个接口要实现的方法定义。到时客户端会用到这个接口的IID和接口的方法。

  1. interface __declspec(uuid("F614FB00-6702-11d4-B0B7-0050BABFC904")) ImyInterface : public IUnknown
  2. {
  3. STDMETHOD(Square)(long *pVal)PURE;
  4. STDMETHOD(Cube)(long *pVal)PURE;
  5. };

客户端使用此接口:

  1. HRESULT hr;
  2. ImyInterface *pmine=(0);
  3. hr = CoCreateInstance(CLSID_Mine, // COM 服务器的CLSID
  4. NULL, //不支持聚合
  5. CLSCTX_INPROC_SERVER, // 是个DLL
  6. __uuidof(ImyInterface), // 接口的IID
  7. (void**)&pmine
  8. );

还有一种方法可以从注册表中获得COM对象的CLSID,就是调用CLSIDFromProgId()函数,不过必须把组件的ProgId传递给这个函数。

第二步:必须为所定义的接口提供实现,本文用的方法是创建一个从接口继承的新类:

  1. // 这个类实现单接口ImyInterface ...
  2. //
  3. //
  4. class CmyInterface : public CComBase<> ,
  5. public InterfaceImpl<ImyInterface>
  6. {
  7. public:
  8. CmyInterface();
  9. virtual ~CmyInterface();
  10. // 我们必须要为QueryInterface 编写代码
  11. STDMETHOD(QueryInterface)(REFIID riid,LPVOID *ppv);
  12. // ImyInterface 接口方法
  13. STDMETHOD(Square)(long *pVal);
  14. STDMETHOD(Cube)(long *pVal);
  15. };

模版类InterfaceImpl<>提供接口引用计数的实现。在此我们可以用多接口继承,那样就能在一个COM组件中实现多个接口。

第三步:在完成这个对象之前,我们还要编写Queryinterface和两个接口方法:

  1. STDMETHODIMP CmyInterface::QueryInterface(REFIID riid,LPVOID *ppv)
  2. {
  3. *ppv = NULL;
  4. if(IsEqualIID(riid,IID_IUnknown) || IsEqualIID(riid,__uuidof(ImyInterface)))
  5. {
  6. // 因为我们从ImyInterface继承,所以要进行强制类型转换
  7. *ppv = (ImyInterface *) this;
  8. _AddRef(); // 这个方法从某个基类继承而来
  9. return S_OK;
  10. }
  11. return E_NOINTERFACE;
  12. }
  13. STDMETHODIMP CmyInterface::Square(long *pVal)
  14. {
  15. long value = *pVal;
  16. *pVal = value * value;
  17. return S_OK;
  18. }
  19. STDMETHODIMP CmyInterface::Cube(long *pVal)
  20. {
  21. long value = *pVal;
  22. *pVal = value * value * value;
  23. return S_OK;
  24. }

注意这里使用了__uuidof(ImyInterface)来获取接口的IID,这是因为我们已经在第一步中将这个接口关联到了某个uuid。

最后一步:COM 组件的DLLs必须输出一个叫DllGetClassObject的函数。由这个函数为CmyInterface创建类工厂并返回一个对它的引用。然后我们调用CoCreateInstance为进程内COM创建类工厂,接着调用DllGetClassObject。这个类工厂有一个方法是CreateInstance,由这个方法创建对象并返回对它的引用。

  1. STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppvOut)
  2. {
  3. *ppvOut = NULL;
  4. if (IsEqualIID(rclsid, CLSID_Mine))
  5. {
  6. // 为CmyInterface类声明类工厂
  7. CClassFactory<CmyInterface>
  8. *pcf = new CClassFactory<CmyInterface>;
  9. return pcf->QueryInterface(riid,ppvOut);
  10. }
  11. return CLASS_E_CLASSNOTAVAILABLE;
  12. }

在此我们要检查所请求的CLSID是不是CLSID_Mine,如果不是则返回一个错误代码。

你可能会问在哪里创建实际的CmyInterface类对象,实际上这是由CClassFactory<CmyInterface>的模板实例来处理的。以下是CClassFatory的实现:

  1. // CSingleCreator 用于单实例类工厂,这个类为多个CreateObject请求返回相同的对象指针..
  2. template<class comObj>
  3. class CSingleCreator
  4. {
  5. protected:
  6. CSingleCreator():m_pObj(0) {};
  7. comObj *CreateObject()
  8. {
  9. if(!m_pObj)
  10. {
  11. m_pObj = new comObj;
  12. }
  13. return m_pObj;
  14. }
  15. comObj * m_pObj;
  16. };
  17. // CMultiCreator 用于常用类工厂,这个类为每一个CreateObject请求返回新的对象指针..
  18. template<class comObj>
  19. class CMultiCreator
  20. {
  21. protected:
  22. CMultiCreator():m_pObj(0) {};
  23. comObj *CreateObject()
  24. {
  25. return new comObj;
  26. }
  27. comObj * m_pObj;
  28. };
  29. //ClassFactory类实现
  30. // MultiCreator是缺省的类工厂创建者
  31. //这个类实现了接口IclasFactory......
  32. class CClassFactory : public CComBase<>,
  33. public InterfaceImpl<IClassFactory>,
  34. public creatorClass
  35. {
  36. public:
  37. CClassFactory() {};
  38. virtual ~CClassFactory() {};
  39. STDMETHOD(QueryInterface)(REFIID riid,LPVOID *ppv)
  40. {
  41. *ppv = NULL;
  42. if(IsEqualIID(riid,IID_IUnknown) || IsEqualIID(riid,IID_IClassFactory))
  43. {
  44. *ppv = (IClassFactory *) this;
  45. _AddRef();
  46. return S_OK;
  47. }
  48. return E_NOINTERFACE;
  49. }
  50. STDMETHODIMP CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, LPVOID *ppvObj)
  51. {
  52. *ppvObj = NULL;
  53. if (pUnkOuter)
  54. return CLASS_E_NOAGGREGATION;
  55. m_pObj = CreateObject(); // m_pObj 在creatorClass中定义
  56. if (!m_pObj)
  57. return E_OUTOFMEMORY;
  58. HRESULT hr = m_pObj->QueryInterface(riid, ppvObj);
  59. if(hr != S_OK)
  60. {
  61. delete m_pObj;
  62. }
  63. return hr;
  64. }
  65. STDMETHODIMP LockServer(BOOL) { return S_OK; } // 未实现
  66. };

COM调用CreateInstance创建请求的对象,参数riid指的是所请求的接口IID,如果这个对象支持这个接口,则增加它的引用计数并返回对自身的引用。

关于代码:本文所提出的方法是如何用纯粹的C++编写COM组件的一个大概念。很多方面的细节都省略了。从本文的文字和代码中可以看出用纯C++编写COM组件需要做些什么工作,如果你要用这种方法编写COM组件的话,这些代码只能是抛砖引玉,具体的实现可以在此基础上往下做.

==================================================文三===========================================================================

最近在学习COM,自己实现了一个小Demo,拿出来和大家分享一下。求各种批评。

我实现的这个组件向外提供了一个接口ICompTest,里面只有一个函数helloworld(),功能为返回一个整数89。

实现了自注册功能。下面贴出代码,希望对刚开始学习COM的朋友有所帮助。

首先看一下工程结构,编译环境为vs 2008

CompTest工程是服务端工程,是一个dll,CtrlTest是客户端工程,是一个控制台工程。

下面通过客户端的运行逻辑来讲述整个运行流程,先看一下CtrlTest.cpp文件

[cpp] view plaincopy
  1. /**
  2. *  @file CtrlTest.cpp
  3. *  @author LiWang112358
  4. *  @date 2012/3/17
  5. *  @version 1.0
  6. *  @brief COM客户端代码
  7. */
  8. #include <iostream>
  9. #include "ICompTest.h"
  10. using namespace std;
  11. int main()
  12. {
  13. CoInitialize(NULL);     //初始化COM库,使用默认的内存分配器
  14. IUnknown* pUnknown = NULL;
  15. GUID CLSID_CompTestClass;
  16. HRESULT hResult = CLSIDFromProgID(L"COMCTL.CompTest", &CLSID_CompTestClass);    //获取ProgID为COMCTL.CompTest组建的CLSID
  17. if (S_OK != hResult){
  18. printf("Can't find CLSID!\n");
  19. return -1;
  20. }
  21. else{
  22. LPOLESTR szCLSID;
  23. StringFromCLSID(CLSID_CompTestClass, &szCLSID);     //将其转化为字符串形式用来输出
  24. wprintf(L"find CLSID \"%s\"\n",szCLSID);
  25. CoTaskMemFree(szCLSID);     //调用COM库的内存释放
  26. }
  27. //用此CLSID创建一个COM对象并获取IUnknown接口
  28. hResult = CoCreateInstance(CLSID_CompTestClass, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown,(void **)&pUnknown);
  29. if (S_OK != hResult || NULL == pUnknown){
  30. printf("Create Object Failed!\n");
  31. return -1;
  32. }
  33. ICompTest* pCompTest = NULL;
  34. hResult = pUnknown->QueryInterface(IID_ICompTest, (void**)&pCompTest);//通过此结构查询我们自己的ICompTest接口
  35. cout << pCompTest->HelloWorld() << endl;//调用我们自己接口中的函数
  36. pCompTest->Release();    //释放自己的接口
  37. pUnknown->Release(); //释放IUnknown接口
  38. CoUninitialize();       //COM库反初始化
  39. return 0;
  40. }

这是客户程序的主逻辑,主要就是通过COM库创建CompTestClass对象,这个对象在这里是不可见的,这里只能拿到ICompTest接口,通过该接口调用函数HelloWorld。

下面看一下接口的声明ICompTest.h文件,这个文件是客户端和服务端都要有的。

[cpp] view plaincopy
  1. /**
  2. *  @file ICompTest.h
  3. *  @author LiWang112358
  4. *  @date 2012/3/17
  5. *  @version 1.0
  6. *  @brief ICompTest接口的声明这个文件是服务端和客户端共有的
  7. */
  8. #ifndef ICOMPTEST_H
  9. #define ICOMPTEST_H
  10. #include <unknwn.h>
  11. // {81A80687-6CC4-4996-8DD2-F058907FDCA8}
  12. static const GUID IID_ICompTest =
  13. { 0x81a80687, 0x6cc4, 0x4996, { 0x8d, 0xd2, 0xf0, 0x58, 0x90, 0x7f, 0xdc, 0xa8 } };
  14. class ICompTest :
  15. public IUnknown
  16. {
  17. public:
  18. virtual int _stdcall HelloWorld() = 0;
  19. };
  20. #endif

这个文件中有一个GUID IID_ICompTest ,用于查询接口ICompTest 。

我们可以在 CtrlTest.cpp文件中看到CLSIDFromProgID和CoCreateInstance这两个函数,

第一个函数是要通过一个名字"COMCTL.CompTest"拿到一个CLSID,这个过程需要CLSID信息。

第二个函数是要通过这个CLSID找到我们的组件(dll),并加载这个dll,然后创建COM对象,这个过程需要dll的路径信息。

这些信息都被放在注册表中,是这个组件自注册的时候由DllRegisterServer函数写入的。

我们可以先看一下注册之后注册表中的内容

其中COMCTL.CompTest是在键HKEY_CLASSES_ROOT下,{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}是在键HKEY_CLASSES_ROOT\CLSID下。

下面我们看一下这个dll的注册过程。

用“regsvr32.exe dll路径”对dll进行注册,实际上regsvr32只是调用了dll中的DllRegisterServer引出函数。

下面我们看一下DllRegisterServer函数的实现,这个函数在CompTest.cpp中。

[cpp] view plaincopy
  1. int myReg(LPCWSTR lpPath)   //将本组件的信息写入注册表,包括CLSID、所在路径lpPath、ProgID
  2. {
  3. HKEY thk, tclsidk;
  4. //打开键HKEY_CLASSES_ROOT\CLSID,创建新键为CompTestClass的CLSID,
  5. //在该键下创建键InprocServer32,并将本组件(dll)所在路径lpPath写为该键的默认值
  6. if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"CLSID", &thk)){
  7. if (ERROR_SUCCESS == RegCreateKey(thk, L"{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}", &tclsidk)){
  8. HKEY tinps32k, tprogidk;
  9. if (ERROR_SUCCESS == RegCreateKey(tclsidk, L"InprocServer32", &tinps32k)){
  10. if (ERROR_SUCCESS == RegSetValue(tinps32k, NULL, REG_SZ, lpPath, wcslen(lpPath) * 2)){
  11. }
  12. RegCloseKey(tinps32k);
  13. }
  14. RegCloseKey(tclsidk);
  15. }
  16. RegCloseKey(thk);
  17. }
  18. //在键HKEY_CLASSES_ROOT下创建新键为COMCTL.CompTest,
  19. //在该键下创建子键,并将CompTestClass的CLSID写为该键的默认值
  20. if (ERROR_SUCCESS == RegCreateKey(HKEY_CLASSES_ROOT, L"COMCTL.CompTest", &thk)){
  21. if (ERROR_SUCCESS == RegCreateKey(thk, L"CLSID", &tclsidk)){
  22. if (ERROR_SUCCESS == RegSetValue(tclsidk,
  23. NULL,
  24. REG_SZ,
  25. L"{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}",
  26. wcslen(L"{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}") * 2)){
  27. }
  28. }
  29. }
  30. //这样的话一个客户端程序如果想要使用本组件,首先可以以COMCTL.CompTest为参数调用CLSIDFromProgID函数
  31. //来获取CompTestClass的CLSID,再以这个CLSID为参数调用CoCreateInstance创建COM对象
  32. return 0;
  33. }
  34. extern "C" HRESULT _stdcall DllRegisterServer()
  35. {
  36. WCHAR szModule[1024];
  37. DWORD dwResult = GetModuleFileName(g_hModule, szModule, 1024); //获取本组件(dll)所在路径
  38. if (0 == dwResult){
  39. return -1;
  40. }
  41. MessageBox(NULL, szModule, L"", MB_OK);
  42. myReg(szModule);//将路径等信息写入注册表
  43. return 0;
  44. }

用“regsvr32.exe dll路径 -u”对dll进行反注册,同样,实际上regsvr32只是调用了dll中的DllUnregisterServer引出函数。

下面我们来看一下DllUnregisterServer函数的实现,这个函数在CompTest.cpp中。

[cpp] view plaincopy
  1. int myDelKey(HKEY hk, LPCWSTR lp)
  2. {
  3. if (ERROR_SUCCESS == RegDeleteKey(hk, lp)){
  4. }
  5. return 0;
  6. }
  7. int myDel() //删除注册时写入注册表的信息
  8. {
  9. HKEY thk;
  10. if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"CLSID", &thk)){
  11. myDelKey(thk, L"{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}\\InprocServer32");
  12. myDelKey(thk, L"{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}");
  13. RegCloseKey(thk);
  14. }
  15. if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"COMCTL.CompTest", &thk)){
  16. myDelKey(thk, L"CLSID");
  17. }
  18. myDelKey(HKEY_CLASSES_ROOT, L"COMCTL.CompTest");
  19. return 0;
  20. }
  21. extern "C" HRESULT _stdcall DllUnregisterServer()
  22. {
  23. myDel();//删除注册时写入注册表的信息
  24. return 0;
  25. }

我们继续分析客户端的代码CoCreateInstance(CLSID_CompTestClass, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown,(void **)&pUnknown);

这个函数是要调用CoGetClassObject函数,来获取CompTestClass的类厂,以此创建CompTestClass对象并获取IUnknown接口。其中,CoGetClassObject函数

实际上是调用了CompTest.cpp中的又一个引出函数DllGetClassObject来获取IClassFactory接口的。最终CoCreateInstance会调用IClassFactory接口的CreateInstance

函数去创建COM对象。

下面我们看一下DllGetClassObject函数的实现

[cpp] view plaincopy
  1. extern "C" HRESULT _stdcall DllGetClassObject(__in REFCLSID rclsid, __in REFIID riid, LPVOID FAR* ppv)//用于创建类厂并返回所需接口,由CoGetClassObject函数调用
  2. {
  3. if (CLSID_CompTestClass == rclsid){
  4. CompTestFactory* pFactory = new CompTestFactory();//创建类厂对象
  5. if (NULL == pFactory){
  6. return E_OUTOFMEMORY;
  7. }
  8. HRESULT result = pFactory->QueryInterface(riid, ppv);//获取所需接口
  9. return result;
  10. }
  11. else{
  12. return CLASS_E_CLASSNOTAVAILABLE;
  13. }
  14. }

接下来我们看一下组件中的最后一个引出函数DllCanUnloadNow,这样dll中的所有引出函数就都出现了

[cpp] view plaincopy
  1. extern "C" HRESULT _stdcall DllCanUnloadNow()//用于判断是否可以卸载本组建, 由CoFreeUnusedLibraries函数调用
  2. {
  3. if (0 == g_num){//如果对象个数为0,则可以卸载
  4. return S_OK;
  5. }
  6. else{
  7. return S_FALSE;
  8. }
  9. }

其中ULONG g_num表示组件中CompTestClass对象的个数,用于判断是否可以卸载本组建,如值为0则可以卸载

下面我们看一下CompTest.cpp的全貌

[cpp] view plaincopy
  1. /**
  2. *  @file CompTest.cpp
  3. *  @author LiWang112358
  4. *  @date 2012/3/17
  5. *  @version 1.0
  6. *  @brief 四个重要的引出函数的实现,
  7. *  @      分别是DllRegisterServer,用于注册本dll,由regsvr32.exe调用
  8. *  @            DllUnregisterServer, 用于反注册本dll,由regsvr32.exe -u 调用
  9. *  @            DllCanUnloadNow,用于判断是否可以卸载本组建, 由CoFreeUnusedLibraries函数调用
  10. *  @            DllGetClassObject,用于创建类厂并返回所需接口,由CoGetClassObject函数调用
  11. */
  12. #include <iostream>
  13. #include <windows.h>
  14. #include "factory.h"
  15. #include "CompTestClass.h"
  16. using namespace std;
  17. HMODULE g_hModule;  //dll进程实例句柄
  18. ULONG g_num;        //组件中CompTestClass对象的个数,用于判断是否可以卸载本组建,如值为0则可以卸载
  19. int myReg(LPCWSTR lpPath)   //将本组件的信息写入注册表,包括CLSID、所在路径lpPath、ProgID
  20. {
  21. HKEY thk, tclsidk;
  22. //打开键HKEY_CLASSES_ROOT\CLSID,创建新键为CompTestClass的CLSID,
  23. //在该键下创建键InprocServer32,并将本组件(dll)所在路径lpPath写为该键的默认值
  24. if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"CLSID", &thk)){
  25. if (ERROR_SUCCESS == RegCreateKey(thk, L"{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}", &tclsidk)){
  26. HKEY tinps32k, tprogidk;
  27. if (ERROR_SUCCESS == RegCreateKey(tclsidk, L"InprocServer32", &tinps32k)){
  28. if (ERROR_SUCCESS == RegSetValue(tinps32k, NULL, REG_SZ, lpPath, wcslen(lpPath) * 2)){
  29. }
  30. RegCloseKey(tinps32k);
  31. }
  32. RegCloseKey(tclsidk);
  33. }
  34. RegCloseKey(thk);
  35. }
  36. //在键HKEY_CLASSES_ROOT下创建新键为COMCTL.CompTest,
  37. //在该键下创建子键,并将CompTestClass的CLSID写为该键的默认值
  38. if (ERROR_SUCCESS == RegCreateKey(HKEY_CLASSES_ROOT, L"COMCTL.CompTest", &thk)){
  39. if (ERROR_SUCCESS == RegCreateKey(thk, L"CLSID", &tclsidk)){
  40. if (ERROR_SUCCESS == RegSetValue(tclsidk,
  41. NULL,
  42. REG_SZ,
  43. L"{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}",
  44. wcslen(L"{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}") * 2)){
  45. }
  46. }
  47. }
  48. //这样的话一个客户端程序如果想要使用本组件,首先可以以COMCTL.CompTest为参数调用CLSIDFromProgID函数
  49. //来获取CompTestClass的CLSID,再以这个CLSID为参数调用CoCreateInstance创建COM对象
  50. return 0;
  51. }
  52. extern "C" HRESULT _stdcall DllRegisterServer()
  53. {
  54. WCHAR szModule[1024];
  55. DWORD dwResult = GetModuleFileName(g_hModule, szModule, 1024); //获取本组件(dll)所在路径
  56. if (0 == dwResult){
  57. return -1;
  58. }
  59. MessageBox(NULL, szModule, L"", MB_OK);
  60. myReg(szModule);//将路径等信息写入注册表
  61. return 0;
  62. }
  63. int myDelKey(HKEY hk, LPCWSTR lp)
  64. {
  65. if (ERROR_SUCCESS == RegDeleteKey(hk, lp)){
  66. }
  67. return 0;
  68. }
  69. int myDel() //删除注册时写入注册表的信息
  70. {
  71. HKEY thk;
  72. if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"CLSID", &thk)){
  73. myDelKey(thk, L"{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}\\InprocServer32");
  74. myDelKey(thk, L"{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}");
  75. RegCloseKey(thk);
  76. }
  77. if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"COMCTL.CompTest", &thk)){
  78. myDelKey(thk, L"CLSID");
  79. }
  80. myDelKey(HKEY_CLASSES_ROOT, L"COMCTL.CompTest");
  81. return 0;
  82. }
  83. extern "C" HRESULT _stdcall DllUnregisterServer()
  84. {
  85. myDel();//删除注册时写入注册表的信息
  86. return 0;
  87. }
  88. extern "C" HRESULT _stdcall DllCanUnloadNow()//用于判断是否可以卸载本组建, 由CoFreeUnusedLibraries函数调用
  89. {
  90. if (0 == g_num){//如果对象个数为0,则可以卸载
  91. return S_OK;
  92. }
  93. else{
  94. return S_FALSE;
  95. }
  96. }
  97. extern "C" HRESULT _stdcall DllGetClassObject(__in REFCLSID rclsid, __in REFIID riid, LPVOID FAR* ppv)//用于创建类厂并返回所需接口,由CoGetClassObject函数调用
  98. {
  99. if (CLSID_CompTestClass == rclsid){
  100. CompTestFactory* pFactory = new CompTestFactory();//创建类厂对象
  101. if (NULL == pFactory){
  102. return E_OUTOFMEMORY;
  103. }
  104. HRESULT result = pFactory->QueryInterface(riid, ppv);//获取所需接口
  105. return result;
  106. }
  107. else{
  108. return CLASS_E_CLASSNOTAVAILABLE;
  109. }
  110. }
  111. BOOL APIENTRY DllMain( HMODULE hModule,
  112. DWORD  ul_reason_for_call,
  113. LPVOID lpReserved
  114. )
  115. {
  116. g_hModule = hModule;//获取进程实例句柄,用于获取本组件(dll)路径
  117. switch (ul_reason_for_call)
  118. {
  119. case DLL_PROCESS_ATTACH:
  120. case DLL_THREAD_ATTACH:
  121. case DLL_THREAD_DETACH:
  122. case DLL_PROCESS_DETACH:
  123. break;
  124. }
  125. return TRUE;
  126. }

下面是.def文件mydef.def用于声明dll的引出函数

[cpp] view plaincopy
  1. LIBRARY "CompTest"
  2. EXPORTS
  3. DllCanUnloadNow
  4. DllGetClassObject
  5. DllUnregisterServer
  6. DllRegisterServer

下面是剩下的文件,有些许注释,语焉不详之处,望海涵。

CompTestClass.h

[cpp] view plaincopy
  1. /**
  2. *  @file CompTestClass.h
  3. *  @author LiWang112358
  4. *  @date 2012/3/17
  5. *  @version 1.0
  6. *  @brief CompTestClass对象的声明,这个类要实现ICompTest接口
  7. */
  8. #ifndef COMPTESTCLASS_H
  9. #define COMPTESTCLASS_H
  10. #include "ICompTest.h"
  11. // {9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}
  12. static const GUID CLSID_CompTestClass =
  13. { 0x9ca9dbe8, 0xc0b1, 0x42c9, { 0xb6, 0xc7, 0x85, 0x6b, 0xe5, 0x75, 0x68, 0x55 } };
  14. class CompTestClass :
  15. public ICompTest
  16. {
  17. public:
  18. CompTestClass();
  19. ~CompTestClass();
  20. //要实现IUnknown接口
  21. virtual HRESULT _stdcall QueryInterface(const IID& riid, void** ppvObject);
  22. virtual ULONG _stdcall AddRef();
  23. virtual ULONG _stdcall Release();
  24. //要实现ICompTest接口
  25. virtual int _stdcall HelloWorld();
  26. protected:
  27. ULONG m_Ref;
  28. };
  29. #endif

CompTestClass.cpp

[cpp] view plaincopy
  1. /**
  2. *  @file CompTestClass.cpp
  3. *  @author LiWang112358
  4. *  @date 2012/3/17
  5. *  @version 1.0
  6. *  @brief CompTestClass类的实现
  7. */
  8. #include "CompTestClass.h"
  9. ULONG CompTestClass::m_objNum = 0;//组件中CompTestClass对象的个数,用于判断是否可以卸载本组建,如值为0则可以卸载
  10. CRITICAL_SECTION CompTestClass::m_cs;//为了多线程调用对m_objNum加的锁
  11. CompTestClass::CompTestClass()
  12. {
  13. m_Ref = 0;
  14. autoLock tlock(m_cs);
  15. m_objNum ++;    //构造了一个对象
  16. }
  17. CompTestClass::~CompTestClass()
  18. {
  19. autoLock tlock(m_cs);
  20. m_objNum --;    //释放了一个对象
  21. }
  22. HRESULT _stdcall CompTestClass::QueryInterface(const IID &riid, void **ppvObject)
  23. {
  24. if (IID_IUnknown == riid){
  25. *ppvObject = (IUnknown*)this;
  26. ((IUnknown*)(*ppvObject))->AddRef();
  27. }
  28. else if (IID_ICompTest == riid){
  29. *ppvObject = (ICompTest*)this;
  30. ((ICompTest*)(*ppvObject))->AddRef();
  31. }
  32. else{
  33. *ppvObject = NULL;
  34. return E_NOINTERFACE;
  35. }
  36. return S_OK;
  37. }
  38. ULONG _stdcall CompTestClass::AddRef()
  39. {
  40. m_Ref ++;
  41. return m_Ref;
  42. }
  43. ULONG _stdcall CompTestClass::Release()
  44. {
  45. m_Ref --;
  46. if (0 == m_Ref){
  47. delete this;
  48. return 0;
  49. }
  50. return m_Ref;
  51. }
  52. int _stdcall CompTestClass::HelloWorld()//ICompTest接口的实现,返回一个整数89
  53. {
  54. return 89;
  55. }
  56. int CompTestClass::Init()
  57. {
  58. m_objNum = 0;
  59. InitializeCriticalSection(&m_cs);
  60. return 0;
  61. }
  62. ULONG CompTestClass::ObjNum()
  63. {
  64. return m_objNum;
  65. }

factory.h

[cpp] view plaincopy
  1. /**
  2. *  @file factory.h
  3. *  @author LiWang112358
  4. *  @date 2012/3/17
  5. *  @version 1.0
  6. *  @brief CompTestClass对象的类厂的声明
  7. */
  8. #ifndef FACTORY_H
  9. #define FACTORY_H
  10. #include <unknwn.h>
  11. class CompTestFactory :
  12. public IClassFactory
  13. {
  14. public:
  15. CompTestFactory();
  16. ~CompTestFactory();
  17. //要实现IUnknown接口
  18. virtual HRESULT _stdcall QueryInterface(const IID& riid, void** ppvObject);
  19. virtual ULONG _stdcall AddRef();
  20. virtual ULONG _stdcall Release();
  21. //要实现IClassFactory接口
  22. virtual HRESULT _stdcall CreateInstance(IUnknown *pUnkOuter, const IID& riid, void **ppvObject);
  23. virtual HRESULT _stdcall LockServer(BOOL fLock);
  24. protected:
  25. ULONG m_Ref;
  26. };
  27. #endif

factory.cpp

[cpp] view plaincopy
  1. /**
  2. *  @file factory.cpp
  3. *  @author LiWang112358
  4. *  @date 2012/3/17
  5. *  @version 1.0
  6. *  @brief CompTestClass对象的类厂的实现
  7. */
  8. #include "factory.h"
  9. #include "CompTestClass.h"
  10. CompTestFactory::CompTestFactory()
  11. {
  12. m_Ref = 0;
  13. }
  14. CompTestFactory::~CompTestFactory()
  15. {
  16. }
  17. HRESULT _stdcall CompTestFactory::QueryInterface(const IID &riid, void **ppvObject)
  18. {
  19. if (IID_IUnknown == riid){
  20. *ppvObject = (IUnknown*)this;
  21. ((IUnknown*)(*ppvObject))->AddRef();
  22. }
  23. else if (IID_IClassFactory == riid){
  24. *ppvObject = (IClassFactory*)this;
  25. ((IClassFactory*)(*ppvObject))->AddRef();
  26. }
  27. else{
  28. *ppvObject = NULL;
  29. return E_NOINTERFACE;
  30. }
  31. return S_OK;
  32. }
  33. ULONG _stdcall CompTestFactory::AddRef()
  34. {
  35. m_Ref ++;
  36. return m_Ref;
  37. }
  38. ULONG _stdcall CompTestFactory::Release()
  39. {
  40. m_Ref --;
  41. if (0 == m_Ref){
  42. delete this;
  43. return 0;
  44. }
  45. return m_Ref;
  46. }
  47. HRESULT _stdcall CompTestFactory::CreateInstance(IUnknown *pUnkOuter, const IID &riid, void **ppvObject)//最重要的函数,这个函数创建CompTestClass对象,并返回所需接口
  48. {
  49. if (NULL != pUnkOuter){
  50. return CLASS_E_NOAGGREGATION;
  51. }
  52. HRESULT hr = E_OUTOFMEMORY;
  53. CompTestClass::Init();
  54. CompTestClass* pObj = new CompTestClass();
  55. if (NULL == pObj){
  56. return hr;
  57. }
  58. hr = pObj->QueryInterface(riid, ppvObject);
  59. if (S_OK != hr){
  60. delete pObj;
  61. }
  62. return hr;
  63. }
  64. HRESULT _stdcall CompTestFactory::LockServer(BOOL fLock)
  65. {
  66. return NOERROR;
  67. }

以下是客户程序的运行结果

C++ COM组件的编写相关推荐

  1. 13-Flutter移动电商实战-ADBanner组件的编写

    1.AdBanner组件的编写 我们还是把这部分单独出来,需要说明的是,这个Class你也是可以完全独立成一个dart文件的.代码如下: 广告图片class AdBanner extends Stat ...

  2. 14-Flutter移动电商实战-ADBanner组件的编写

    拨打电话的功能在app里也很常见,比如一般的外卖app都会有这个才做.其实Flutter本身是没给我们提供拨打电话的能力的,那我们如何来拨打电话那? 1.编写店长电话模块 这个小伙伴们一定轻车熟路了, ...

  3. react项目中的参数解构_重构复杂的React组件:编写高效且可读组件的5个最佳实践...

    随着 React.js 的不断进化,现在的它已经成为 Web 组件中最受欢迎的视图库之一.但是你手中的它,是否真的能够正常工作呢?本文将主要描述 5 个关于React 组件的最佳实践,希望对正在关注 ...

  4. 关于Unity中的帧动画组件的编写

    一.帧动画 1: 美术准备好一个连续动作的离散图片; 2: 程序在准确的时间来切换这个图片; 3: 优点: 简单,速度快; 缺点:资源占用相对过大; 二.frame_anim组件编写 1: 代码里面强 ...

  5. Fit项目分页组件的编写

    2019独角兽企业重金招聘Python工程师标准>>> 项目中涉及列表显示的地方都会用到分页控件,为了能更好地与当前网站的样式匹配,这次要自己实现一个. 所以选择了模板中提供的分页样 ...

  6. 前端学习(2893):导航组件的编写和属性技巧设置

  7. Vue_案例_初始化显示_采用组件化编写页面_实现组件间通信---vue工作笔记0022

    我们来做一个上面的这样的页面,我们已经有了html页面,以及css文件了. 首先我们把原来的src文件夹中的内容,备份一下到 src-base文件夹中去. 然后,我们新建一个src文件夹, 先把原来的 ...

  8. 组件化 Todo List 编写笔记

    前言 在学习 Vue.js 组件化开发 Todo List 的时候,自己虽然也能编码实现,但如果不做笔记,只是写代码,学习的效果还不够好.只有把自己的实现思路记录下来,遇到的问题和解决方法也记录下来, ...

  9. VC6.0和VS2005:C++和C#编写调用COM组件

    这篇文章就是关于COM组件的编写和调用的,主要包含了使用VC6.0编写和调用COM组件,VS2005中使用C#编写和调用COM组件,以及在VC6.0和VS2005之间互相调用COM组件. AD: 前一 ...

最新文章

  1. Pytorch离线安装的纯净版
  2. ubuntu下 显示no module named “lib“或 “lib.xxx“
  3. wmsys.WM_CONCAT
  4. CNCF发布K8s项目历程报告,35k贡献者有你吗?
  5. 常用FTP命令 1. 连接ftp服务器
  6. js中如何判断一个DOM对象是否存在?
  7. svm预测结果为同一个值_SVM算法总结
  8. 【MySQL 】学习笔记千行总结
  9. java 16 binary_【图片】【困扰】java(tm) platform se binary 已停止工作该如何是好【minecraft吧】_百度贴吧...
  10. powershell执行c语言文件,c语言中调用shell脚本
  11. .net加载失败的程序集重新加载
  12. mugen4g补丁如何使用_《守望先锋联赛》宣布采用英雄池机制及游戏补丁更新方式详解...
  13. 组播vlan_【参与方式】耗时10小时编撰(下)带你轻松通关组播知识点!
  14. 深入理解JavaScript定时器
  15. HTML Agility Pack:简单好用的快速 HTML Parser
  16. 强化学习——多智能体强化学习
  17. Unity Timeline自定义轨道 DefaultPlayables源码剖析
  18. 计算机管理员权限打不开怎么办,administrator管理员权限怎么打开?
  19. 视频导入pr一卡一卡的(解决)
  20. 水仙花数是指一个N位正整数(N≥3),它的每个位上的数字的N次幂之和等于它本身

热门文章

  1. 数据库触发器实例讲解
  2. firewall ip白名单设置
  3. 【python】装饰模式
  4. 1 error LNK2019: 无法解析的外部符号 _Direct3DCreate9@4,该符号在函数 void __cdecl InitD3D(struct HWND__ *)
  5. echarts折线图背景线_echarts-设置折线图中折线线条颜色和折线点颜色
  6. 抖音怎么去赚钱?小白也能快速上手的抖音测试号项目。丨国仁网络资讯
  7. 独立后台带分销功能月老办事处交友盲盒微信小程序源码新版,更好的裂变推广引流
  8. 员工公寓楼建设项目电力监控系统的研究与应用
  9. golang加载双向认证加密的证书key文件
  10. F2FS源码分析-1.6 [F2FS 元数据布局部分] Segment Summary Area-SSA结构