惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 叶小钗
云风的 BLOG
云风的 BLOG
G
Google Developers Blog
S
SegmentFault 最新的问题
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
美团技术团队
爱范儿
爱范儿
博客园 - 三生石上(FineUI控件)
H
Hackread – Cybersecurity News, Data Breaches, AI and More
D
DataBreaches.Net
F
Fortinet All Blogs
TaoSecurity Blog
TaoSecurity Blog
D
Docker
C
Cybersecurity and Infrastructure Security Agency CISA
K
Kaspersky official blog
宝玉的分享
宝玉的分享
腾讯CDC
Google Online Security Blog
Google Online Security Blog
Recorded Future
Recorded Future
T
The Exploit Database - CXSecurity.com
T
The Blog of Author Tim Ferriss
V
V2EX
S
Securelist
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
C
CERT Recently Published Vulnerability Notes
A
Arctic Wolf
Scott Helme
Scott Helme
L
LINUX DO - 热门话题
Y
Y Combinator Blog
P
Proofpoint News Feed
T
Tor Project blog
AWS News Blog
AWS News Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
The Last Watchdog
The Last Watchdog
博客园 - 聂微东
T
Threat Research - Cisco Blogs
B
Blog
Attack and Defense Labs
Attack and Defense Labs
L
Lohrmann on Cybersecurity
C
CXSECURITY Database RSS Feed - CXSecurity.com
阮一峰的网络日志
阮一峰的网络日志
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
IT之家
IT之家
N
News and Events Feed by Topic
博客园 - 司徒正美
H
Help Net Security
C
Cisco Blogs
C
Check Point Blog
S
Secure Thoughts

博客园 - Zzx飘遥

发布一个注册类型库(TypeLib)的小工具 【软件发布】发布一个查单词的小工具 COM 入门(4) COM 入门(3) COM 入门(2) C#内嵌汇编代码的讨论 仿Win7显示桌面的工具 Deep Zoom Composer初探 ASP.NET VirtualPathProvider (下) Silverlight3离线运行 [译]理解Windows消息循环 WPF BitmapImage与byte[]的转换 VC++中启用XP主题外观 C#4.0初探:dynamic 关键字 - Zzx飘遥 - 博客园 四个字节整型转换为IP格式 - Zzx飘遥 - 博客园 拯救开启桌面效果后白屏的openSUSE 遭遇SqlDataReader锁定表 软件更新:网页设计师必备 之 网站截图工具 (附源码) C#4.0初探: Optional and named parameters
COM 入门(1)
Zzx飘遥 · 2010-08-28 · via 博客园 - Zzx飘遥

最近需要用到COM知识,总结一下刚学到的知识。

1. 什么是COM
COM全称是Component Object Model,中文译为组件对象模型。COM组件在物理上是一些DLL或EXE文件;COM组件实现二进制级别的代码重用;COM是与程序设计语言无关,理论上任何语言都可以开发和调用COM组件;COM组件用引用计数实现生命周期的自我管理;COM组件调用者能够查询它所支持的接口;COM组件的位置对调用者是透明的;COM组件依赖于注册表;COM组件都要直接或间接的实现IUnknown接口……

2. IUnknown接口
所有COM组件都直接或间接实现IUnknown接口,IUnknown接口是COM的根接口,其声明为:

interface IUnknown
{
    
virtual STDMETHODIMP QueryInterface(REFIID riid, VOID** ppv) = 0;
    
virtual STDMETHODIMP_(ULONG) AddRef(VOID) = 0;
    
virtual STDMETHODIMP_(ULONG) = 0;
};

标准C++中没有interface关键字,这里的interface是typedef struct interface。
ULONG,VOID等都是windows对C/C++中标准数据类型的宏定义,目的是屏蔽不同平台的差异,并且使编码风格统一。
STDMETHODIMP等价于HRESULT __stdcall
STDMETHODIMP_(DataType)等价于DataType __stdcall

COM规定,调用函数的方式必须为__stdcall,这是pascal语言缺省的调用函数的方式:函数的参数从右到左依次压栈,函数退出时自己清空占用的堆栈。与之相对的为__cdecl,这是C/C++语言缺省的调用函数的方式:函数的参数从右到左依次压栈,函数退出时,由调用者清空函数占用的堆栈。

QueryInterface函数,客户通过该函数查询COM实现的接口;riid是接口的标识,为GUID形式;返回值标识查询的接口是否实现,如果实现了,则返回S_OK,并且ppv指向接口的实例,否则返回E_NOINTERFACE,ppv指向的内容无效。QueryInterface函数隔离了不同编程语言构造对象实例的差异。
AddRef和Release函数实现了COM对象生命周期的自我管理。实现该接口的类需要有一个ULONG型成员记录其实例的引用计数,如果AddRef一次,引用计数加1,否则引用计数减1,如果引用计数为0时,就释放该实例。这两个函数隔离了不同变成语言释放对象实例的差异。

3. 实现一个最最简单的COM组件

//BeginningCOM.h
#ifndef __BEGINNINGCOM_H__
#define __BEGINNINGCOM_H__ #pragma once
#include
<windows.h> // {7BB69A25-68E4-427a-BE4B-B06ED17531AA}
CLSID CLSID_BeginningCOM =
{
0x7bb69a25, 0x68e4, 0x427a, { 0xbe, 0x4b, 0xb0, 0x6e, 0xd1, 0x75, 0x31, 0xaa } }; #endif //__BEGINNINGCOM_H__ //BeginningCOM.cpp
#include "BeginningCOM.h" class BeginningCOM : public IUnknown
{
public:
    BeginningCOM(VOID);
    STDMETHODIMP QueryInterface(REFIID riid, VOID
** ppv);
    STDMETHODIMP_(ULONG) AddRef(VOID);
    STDMETHODIMP_(ULONG) Release(VOID);
protected:
    ULONG m_ulRefCount;
};

BeginningCOM::BeginningCOM(VOID) : m_ulRefCount(

0)
{
}

STDMETHODIMP BeginningCOM::QueryInterface(REFIID riid, VOID

** ppv)
{
    
if(riid == IID_IUnknown)
    {
        
*ppv = static_cast<IUnknown*>(this);
    }
    
else
    {
        
*ppv = NULL; return E_NOINTERFACE;
    }

    reinterpret_cast

<IUnknown*>(*ppv) -> AddRef();
    
return S_OK;
}

STDMETHODIMP_(ULONG) BeginningCOM::AddRef(VOID)
{

return InterlockedIncrement(&m_ulRefCount);
}

STDMETHODIMP_(ULONG) BeginningCOM::Release(VOID)
{
    ULONG tmp

= InterlockedDecrement(&m_ulRefCount); if(tmp == 0)
    {
        delete
this;
    }
return tmp;
}
// DLL entry point.
BOOL APIENTRY DllMain( HMODULE hModule,
                      DWORD  ul_reason_for_call,
                      LPVOID lpReserved
                      )
{
    
switch (ul_reason_for_call)
    {
    
case DLL_PROCESS_ATTACH:
    
case DLL_THREAD_ATTACH:
    
case DLL_THREAD_DETACH:
    
case DLL_PROCESS_DETACH:
        
break;
    }
    
return TRUE;
}

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid,

void **ppv)
{
    
if(rclsid == CLSID_BeginningCOM)
    {
        BeginningCOM
*pbc = new BeginningCOM; if(pbc == NULL)
        {
            
return E_OUTOFMEMORY;
        }
return pbc -> QueryInterface(riid, ppv);
    }
*ppv = 0; return CLASS_E_CLASSNOTAVAILABLE;
}

BeginningCOM.h中,先定义一个GUID作为实现接口的class的ID。BeginningCOM.cpp中实现IUnknown接口。
DllGetClassObject是客户调用COM组件的入口,需要导出这个函数:

//BeginningCOM.def
LIBRARY    "BeginningCOM"
EXPORTS
    DllGetClassObject
private

4. 注册COM组件
在HKEY_CLASSES_ROOT\CLSID键下注册COM组件的class信息。创建一个reg文件,导入注册表即可。

//regsvr.reg
Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\CLSID\{7BB69A25

-68E4-427a-BE4B-B06ED17531AA}]
@
="BeginningCOM"

[HKEY_CLASSES_ROOT\CLSID\{7BB69A25

-68E4-427a-BE4B-B06ED17531AA}\InprocServer32]
@
="E:\\Projects\\BeginningCOM\\Debug\\BeginningCOM.dll"
"ThreadingModel"="Both"

@="BeginningCOM"是class的描述信息,可忽略。
"ThreadingModel"="Both"是COM组件的套件类型,后面介绍。

相应的卸载COM组件的reg文件内容应该为:

//unregsvr.reg
Windows Registry Editor Version 5.00

[

-HKEY_CLASSES_ROOT\CLSID\{7BB69A25-68E4-427a-BE4B-B06ED17531AA}]
@
="BeginningCOM"

需要注意的是,在Win64中,如果COM组件编译选择的是X86,那么注册表会进行重定向,读取位置变为HKEY_CLASSES_ROOT\Wow6432Node\CLSID,因此,注册时,键值应该写在这个路径下。见《Win64 注册表重定机制向导致程序运行异常

5. 调用COM组件
要调用COM组件,必须向客户公布接口ID,class ID等,我们这里把需要公布的信息放到BeginningCOM.h中。
下面的代码用于调用刚创建的COM组件。

#include <Windows.h>
#include
<tchar.h>
#include
<iostream>
#include
"../BeginningCOM/BeginningCOM.h"
using namespace std; int _tmain(int argc, _TCHAR* argv[])
{
    CoInitialize(NULL);

    HRESULT hr

= NULL;
    IUnknown
*puk;

    hr

= CoGetClassObject(CLSID_BeginningCOM, CLSCTX_INPROC_SERVER, NULL, IID_IUnknown, (void**)&puk); if(SUCCEEDED(hr))
    {
        
//do nothing
        puk -> Release();
    }
    
else
    {
        cout
<< "Failed to create object"<<endl;
    }

    CoUninitialize();

return 0;
}

在使用COM组件前,需要初始化COM调用环境,CoInitialize初始化单线程套间,只有一个保留的参数,CoInitializeEx除了一个保留的参数,还可以指定初始化的套件类型。

Author: Zhenxing Zhou
Blog: http://www.xianfen.net/