COM组件开发

0) 理论参考

0.1) COM组件的套间概念

To help the newbie cope with the initial learning curve, I have the following advise on the way to perceive apartments:

  1. They are created by implication. There are no direct function calls to create them or to detect their presence.
  2. Threads and objects enter apartments and engage in apartment-related activities also by implication. There are also no direct function calls to do so.
  3. Apartment Models are more like protocols, or a set of rules to follow.

译:为了帮助新手理解最开始的学习路线,关于探索套间的方法,我有以下建议

        1,他们(所有套间)是隐式创建的。没有直接的函数调用来创建或者探测他们的存在。

        2,线程和对象进入套间和参与套间相关的活动也是隐式的,没有直接的函数调用和完成这些。

        3,套间模型更像协议,或者说一组(提供给各个线程里面的对象以及线程本身)需要遵守的规则。

COM Apartments exist for the purpose of ensuring something known as thread-safety.

com组件的套间模型是为了保证线程安全而设计的。

Apartments stipulate the following general guidelines for participating threads and objects:

  • Each COM object is assigned to live in one and only one apartment. This is decided at the time the object is created at runtime. After this initial setup, the object remains in that apartment throughout its lifetime.
  • A COM thread (i.e., a thread in which COM objects are created or COM method calls are made) also belongs to an apartment. Like COM objects, the apartment in which a thread lives is also decided at initialization time. Each COM thread also remains in their designated apartment until it terminates.
  • Threads and objects which belong to the same apartment are said to follow the same thread access rules. Method calls which are made inside the same apartment are performed directly without any assistance from COM.
  • Threads and objects from different apartments are said to play by different thread access rules. Method calls made across apartments are achieved via marshalling. This requires the use of proxies and stubs.
套间为参与(到套间)的线程和对象规定了以下通用的指导意见:

     1, 每一个com对象被指定到一个并且是唯一的一个套间里面。这个是由对象在运行的时候被创建的的时候决定的。在这个初始化步骤以后,对象存在于他的套间中直到结束。

     2, 一个com线程(举个例子:在这个线程里面创建了com对象或者调用了com调用)也属于一个套间。和com对象一样,这个套间也是在该线程初始化的时候决定的。每一个com线程存在于他们自己的套间里面直到退出(译者按:应该是有一个套间并且只有一个套间?)。

     3,属于同一个套间的线程和对象,就可以认为他们遵守相同的线程访问规则,在线程内部完成的方法调用直接完成,不需要COM运行时库的干预。

     4,属于不同套间的线程和对象遵守不同的线程访问规则,跨套间的方法调用需要通过参数封装来完成,这就需要代理和连接桩。

The following are the thread access rules of an STA:

  1. An STA object created inside an STA thread will reside in the same STA as its thread.
  2. All objects inside an STA will receive method calls only from the thread of the STA.

Point 1 is natural and is easily understood. However, note that two objects of the same coclass and from the same DLL server created in separate STA threads will not be in the same apartment. This is illustrated in the diagram below:

以下是单线程套间的线程访问规则:

     1,一个在单线程套间中创建的对象(肯定是单线程的)存在于创建它的线程的套间中。

     2,所有单线程套间的对象只接受来自这个单线程套间的调用(其他套间也要通过这个套间代理)。

第一点很容易理解。然后,注意,来自同一个dll的同一个com对象类的2个对象实例,如果是两个STA线程分别创建的,那么,这两个对象也分别存在于不同的套间中。如下图所示:


0.2) 进程内COM组件

0.3) COM的线程模型

  • Single-threaded apartments consist of exactly one thread, so all COM objects that live in a single-threaded apartment can receive method calls only from the one thread that belongs to that apartment. All method calls to a COM object in a single-threaded apartment are synchronized with the windows message queue for the single-threaded apartment's thread. A process with a single thread of execution is simply a special case of this model.
  • 单线程套间 只由一个线程组成,所以,所有的com对象生存在一个单线程套间中,它们只能够接受来自于该套间的这个线程的方法调用。在一个单线程套间中,所有的对一个com对象的方法调用通过这个线程的消息队列来同步(译者按:也就是被串行了)。(windows的)单线程进程就是这个模型的一个例子。
  • Multithreaded apartments consist of one or more threads, so all COM objects that live in an multithreaded apartment can receive method calls directly from any of the threads that belong to the multithreaded apartment. Threads in a multithreaded apartment use a model called free-threading. Calls to COM objects in a multithreaded apartment are synchronized by the objects themselves.
  • 多线程套间由一个或者多个线程构成,所以,所有的生存与多线程套间中的com对象可以直接接收来自于该套间的任何其他线程的方法调用。多项成套间中的线程使用一个叫做自由线程的模型。对多线程套间中的com对象的方法调用由com对象们自己来同步。

The most important issue in programming with a multithreaded model is to make your code thread-safe so that messages intended for a particular thread go only to that thread and access to threads is protected.

使用多线程套间开发的一个最重要的问题就是确保你的代码是线程安全的,使得某个线程期望的消息只去那个指定的线程那里被处理,并且确保对于线程的访问是受保护的。


In a process, the main apartment is the first to be initialized. In a single-threaded process, this is the only apartment. Call parameters are marshaled between apartments, and COM handles the synchronization through messaging. If you designate multiple threads in a process to be free-threaded, all free threads reside in a single apartment, parameters are passed directly to any thread in the apartment, and you must handle all synchronization. In a process with both free-threading and apartment threading, all free threads reside in a single apartment and all other apartments are single-threaded apartments. A process that does COM work is a collection of apartments with, at most, one multithreaded apartment but any number of single-threaded apartments.

在进程中,主套间是第一个被初始化的。在一个单线程进程中,这就是那个唯一的套间(译者按:如果是一个单线程程序,那么com就是用这个线程作为自己的套间)。调用参数在套间层次上封装整理,COM通过消息来处理同步问题。如果你指定进程中的多个线程为自由线程模型,所有的自由线程生存在一个套间中,参数直接传递给套间中的任何一个线程,并且,你必须(在COM对象的实现内部)来处理同步问题。在一个拥有自由线程和套间线程的进程中,所有的自由线程生存与一个套间中(译者按:共享一个套间的上下文运行环境),并且,(剩下的)其他所有的套间的哦是单线程套间。完成COM工作的进程就是一个套间的集合,这个集合,最多包含一个多线程套间和任意个单线程套间。


The threading models in COM provide the mechanism for clients and servers that use different threading architectures to work together. Calls among objects with different threading models in different processes are naturally supported. From the perspective of the calling object, all calls to objects outside a process behave identically, no matter how the object being called is threaded. Likewise, from the perspective of the object being called, arriving calls behave identically, regardless of the threading model of the caller.

COM中的线程模型提供了客户端和服务器之间的机制,这个机制使得不同线程架构的(程序)可以一起协同工作。在不同的进程中,不同线程模型的对象之间的相互调用很自然的得到支持。从调用者的角度来看,对于进程外对象的所有调用完全和之前一样,不用在乎被调用的对象线程模型。类似的,从被调用的com组件的角度来看,所有来到的调用和之前一样,不用管调用者的线程模型。

Interaction between a client and an out-of-process object is straightforward, even when they use different threading models because the client and object are in different processes. COM, interposed between the client and the server, can provide the code for the threading models to interoperate, using standard marshaling and RPC. For example, if a single-threaded object is called simultaneously by multiple free-threaded clients, the calls will be synchronized by COM by placing corresponding window messages in the server's message queue. The object's apartment will receive one call each time it retrieves and dispatches messages. However, some care must be taken to ensure that in-process servers interact properly with their clients. (See In-Process Server Threading Issues.)

客户端和进程外组件对象的交互是直接的,甚至当他们使用不同的线程模型,因为客户端和com组件在不同的进程中(译者按:why?)。COM在客户端和服务器之间做工作,COM能提供线程模型方面的代码来完成(客户端和服务器之间的)相互操作,这些代码包括参宿封装整理和RPC。举个例子,如果一个单线程COM对象被多个自由线程的客户同时调用,所有的调用被COM同步化了,同步的方式就是在服务器(译者按:也就是COM组件)的消息队列里面放置相应的消息。COM组件的套间会在每次获取和分发消息的时候收到这些调用请求。不过,必须小心地保证进程内服务器和他们的客户端之间的正确的交互。

0.4) 总结

http://hi.baidu.com/myproton/item/9544523ddc55e381b611db56




========================

===== 理论与实践的分割线===

========================


1) 如何使用com组件

这个例子的代码是用于一个资源管理器的命名扩展的com对象,所以里面一些shell和shellext开头的类和接口不具有普遍意义。

最常见的代码就是初始化com的套间,然后通过类参数和接口参数创建你需要的接口

// testcom.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <platformdefs.h>
#include <guids.h>
#include <IDemoInterface.h>

int _tmain(int argc, _TCHAR* argv[])
{
	// 初始化套间模型
	CoInitialize(NULL);

	// 通过类型ID和接口ID来获取接口
	// CLSID_CShellExt & IID_IDemoInterface的定义在guids.h
	IDemoInterface * pIDemo = NULL;
	CoCreateInstance(CLSID_CShellExt
		, NULL, CLSCTX_INPROC_SERVER
		, IID_IDemoInterface
		, (void **)&pIDemo);

	// 调用接口
	int sum = 0;
	pIDemo->DemoAdd(1, 2, &sum);

	// 释放
	pIDemo->Release();
	return 0;
}

好吧,从输入来看,用户需要知道CLSID和IID,IID可以从接口定义的头文件里面给出,这个头文件还会给出这个接口的功能说明。

看看IID_IDemoInterface,在guids.h, 可以理解成就是一个整数,在QueryInterface里面区分许多个接口。

// {0000000A-0000-0000-0000-00000000000B}
static const GUID IID_IDemoInterface = 
{ 0x0000000A, 0x0000, 0x0000, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B } };


看看 IDemoInterface接口定义, 在IDemoInterface.h里面定义

#ifndef _IDEMO_INTERFACE_H__
#define _IDEMO_INTERFACE_H__

class IDemoInterface : public IUnknown
{
public:
	virtual HRESULT STDMETHODCALLTYPE DemoAdd(int a, int b, int * sum) = 0;
};

#endif


那么,CLSID, 也就是host这些接口的对象(com对象)的id在那里定义呢,又是怎么知道的呢?

CLSID_CShellExt也是一个整数,标记一个com对象,最主要的作用是用于查找COM对象的位置(如果是INPROC,就加载dll,如果是exe或者远程,就marshal,然后发请求),

定义在guid.h里面(注意,引用GUID需要包含<initguid.h>)

// {0000000A-0000-0000-0000-00000000000A}
static const GUID CLSID_CShellExt = 
{ 0x0000000A, 0x0000, 0x0000, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A } };


你们COM运行时环境看到这个参数的时候,如何知道host这个com对象的代码在那个物理文件或者机器上呢?

答案是 注册表

所以说COM都是需要事先注册好的,然后,COM运行时就知道去哪里查找了。


这些信息(其实就是一个CLSID和com对象的位置的一个映射,顺便指定了一下线程模型)是必要的,其他信息不一定要注册。

注意 x64和win32注册位置的区别


Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\CLSID\{0000000A-0000-0000-0000-00000000000A}]
@="CLSID_CShellExt"

[HKEY_CLASSES_ROOT\CLSID\{0000000A-0000-0000-0000-00000000000A}\InprocServer32]
@="D:\\cifs\\lab\\temp\\comdev\\basense\\x64\\Release\\basense.dll"
"ThreadingModel"="Apartment"

如果为了方便,也可以实现一个DllRegisterServer来实现自注册,但这不是必须的。该例子希望实现尽量瘦小的一个框架。


2) 如何从COM对象获取接口

该例子只关心进程内的com组件,就是说这个com对象的dll是加载在caller(或者说是client)的进程里面的。


第一个要求:实现该com对象的dll必须导出的函数

一个是DllGetClassObject , 另一个是 DllCanUnloadNow, 主要是前一个的实现。


通过输入参数CLSID_CShellExt(例子的com对象ID)和标准的IID_IClassFactory接口ID,获取一个IClassFactory接口。

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppvOut)
{
    HRESULT hr = CLASS_E_CLASSNOTAVAILABLE;
    if (ppvOut == 0 )
        return E_POINTER;
    *ppvOut = NULL;


    if (!IsEqualCLSID(rclsid, CLSID_CShellExt) || !IsEqualIID(riid, IID_IClassFactory))
        return (hr = CLASS_E_CLASSNOTAVAILABLE);


    LPOLESTR clsidstring = NULL;
    StringFromCLSID(rclsid, &clsidstring);
    
    LPOLESTR iidstring = NULL;
    StringFromIID(riid, &iidstring);


    CShellExtClassFactory *pcf = new (std::nothrow) CShellExtClassFactory();
    if (pcf == NULL)
        return E_OUTOFMEMORY;


    // refcount currently set to 0
    hr = pcf->QueryInterface(riid, ppvOut);
    if(FAILED(hr))
        delete pcf;
    
    CoTaskMemFree(clsidstring);
    CoTaskMemFree(iidstring);
    return hr;
}


也就是说,com实现者,需要创造一个CShellExtClassFactory对象,并实现IClassFactory接口,让DllGetClassObject来查询。

为什么不直接传入一个目标IID来拿到IDemoInterface接口呢,非要绕一个圈子,用类工厂来获取呢? 答案是 标准。

也就是说这个导出函数的调用结果就是拿到一个IClassFactory接口。


好吧,拿到IClassFactory接口以后,可以调用这个接口的CreateInterface()方法来创建目标接口IDemoInterface。

在该函数中,直接创建一个功能对象的实例,然后从它里面query一个riid指定的接口出来。

STDMETHODIMP CShellExtClassFactory::CreateInstance(LPUNKNOWN pUnkOuter,
                                                   REFIID riid,
                                                   LPVOID *ppvObj)
{
    HRESULT hr = S_FALSE;

    if(ppvObj == 0)
        return E_POINTER;

    *ppvObj = NULL;

    // Shell extensions typically don't support aggregation (inheritance)

    if (pUnkOuter)
        return CLASS_E_NOAGGREGATION;

    // Create the main shell extension object.  The shell will then call
    // QueryInterface with IID_IShellExtInit--this is how shell extensions are
    // initialized.
    CShellExt* pShellExt = new (std::nothrow) CShellExt();  //Create the CShellExt object

    if (NULL == pShellExt)
        return E_OUTOFMEMORY;

    hr = pShellExt->QueryInterface(riid, ppvObj);
    if(FAILED(hr))
        delete pShellExt;

    return hr;
}

前提是这个功能对象CShellExt实现了这个riid=IDemoInterface指定的接口

class CShellExt : public IDemoInterface
// COMPILER ERROR? You need the latest version of the
// platform SDK which has references to IColumnProvider
// in the header files.  Download it here:
// http://www.microsoft.com/msdownload/platformsdk/sdkupdate/
{
protected:
    ULONG                           m_cRef;

public:
    CShellExt();
    virtual ~CShellExt();

    /** \name IUnknown
     * IUnknown members
     */
    //@{
    STDMETHODIMP         QueryInterface(REFIID, LPVOID FAR *);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    //@}

    /** \name IShellExtInit
     * IShellExtInit methods
     */
    //@{
    STDMETHODIMP    DemoAdd(int a, int b, int * sum);
    //@}
};

最后,需要这个功能对象实现了接口的方法实现

STDMETHODIMP_(HRESULT) CShellExt::DemoAdd(int a, int b, int * sum){
	//TRACE("%s(%d,%d,%p) called at %s:%d", __FUNCTION__, a, b, sum, __FILE__, __LINE__);
	*sum = a+b;
	return S_OK;
}


3) 关于引用计数的解释

这个例子使用了3个引用计数。一个g_cRefThisDll是关于这个dll的是否可以卸载的计数,主要是记录这个dll里面的c++对象的个数, 如果这个值为0,表示这个dll已经没有c++对象了,可以卸载了。


另外2个(都是成员变量m_cRef)是记录CShellExtClassFacotry对象的接口引用和CShellExt对象的接口引用,如果为0,代表这个对象的接口引用为0,这个对象可以delete了,如果所有的对象都已经delete了,那么dll也就可以卸载了。


所以可以看到,g_cRefThisDll的递减是放置在CShellExtClassFacotry和CShellExt对象的析构函数里面的。



4) 主要代码文件

4.1) dll模块相关的实现

TortoiseSVN.cpp

// TortoiseSVN - a Windows shell extension for easy version control

// Copyright (C) 2003-2010, 2012, 2014 - TortoiseSVN

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "platformdefs.h"
#include "Guids.h"
#include "ShellExtClassFactory.h"

volatile LONG       g_cRefThisDll = 0;              ///< reference count of this DLL.
HINSTANCE           g_hmodThisDll = NULL;           ///< handle to this DLL itself.

extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /* lpReserved */)
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        // Extension DLL one-time initialization
        g_hmodThisDll = hInstance;
    }
    return TRUE;   // ok
}

STDAPI DllCanUnloadNow(void)
{
    return (g_cRefThisDll == 0 ? S_OK : S_FALSE);
}

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppvOut)
{
    HRESULT hr = CLASS_E_CLASSNOTAVAILABLE;
    if (ppvOut == 0 )
        return E_POINTER;
    *ppvOut = NULL;

    if (!IsEqualCLSID(rclsid, CLSID_CShellExt) || !IsEqualIID(riid, IID_IClassFactory))
        return (hr = CLASS_E_CLASSNOTAVAILABLE);

    LPOLESTR clsidstring = NULL;
    StringFromCLSID(rclsid, &clsidstring);
    
    LPOLESTR iidstring = NULL;
    StringFromIID(riid, &iidstring);

    CShellExtClassFactory *pcf = new (std::nothrow) CShellExtClassFactory();
    if (pcf == NULL)
        return E_OUTOFMEMORY;

    // refcount currently set to 0
    hr = pcf->QueryInterface(riid, ppvOut);
    if(FAILED(hr))
        delete pcf;
    
    CoTaskMemFree(clsidstring);
    CoTaskMemFree(iidstring);
    return hr;
}


4.2) 工厂类

ShellExtClassFactory.cpp

用于返回IClassFactory接口

// TortoiseSVN - a Windows shell extension for easy version control

// Copyright (C) 2003-2006, 2009-2011 - TortoiseSVN

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//

#include "platformdefs.h"
#include "Globals.h"
#include "ShellExt.h"
#include "ShellExtClassFactory.h"

CShellExtClassFactory::CShellExtClassFactory()
{
    m_cRef = 0L;

    InterlockedIncrement(&g_cRefThisDll);
}

CShellExtClassFactory::~CShellExtClassFactory()
{
    InterlockedDecrement(&g_cRefThisDll);
}

STDMETHODIMP CShellExtClassFactory::QueryInterface(REFIID riid,
                                                   LPVOID FAR *ppv)
{
    if(ppv == 0)
        return E_POINTER;

    *ppv = NULL;

    // Any interface on this object is the object pointer

    if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IClassFactory))
    {
        *ppv = static_cast<LPCLASSFACTORY>(this);
        AddRef();
        return S_OK;
    }

    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) CShellExtClassFactory::AddRef()
{
    return ++m_cRef;
}

STDMETHODIMP_(ULONG) CShellExtClassFactory::Release()
{
    if (--m_cRef)
        return m_cRef;

    delete this;

    return 0L;
}

STDMETHODIMP CShellExtClassFactory::CreateInstance(LPUNKNOWN pUnkOuter,
                                                   REFIID riid,
                                                   LPVOID *ppvObj)
{
    HRESULT hr = S_FALSE;

    if(ppvObj == 0)
        return E_POINTER;

    *ppvObj = NULL;

    // Shell extensions typically don't support aggregation (inheritance)

    if (pUnkOuter)
        return CLASS_E_NOAGGREGATION;

    // Create the main shell extension object.  The shell will then call
    // QueryInterface with IID_IShellExtInit--this is how shell extensions are
    // initialized.
    CShellExt* pShellExt = new (std::nothrow) CShellExt();  //Create the CShellExt object

    if (NULL == pShellExt)
        return E_OUTOFMEMORY;

    hr = pShellExt->QueryInterface(riid, ppvObj);
    if(FAILED(hr))
        delete pShellExt;

    return hr;
}

STDMETHODIMP CShellExtClassFactory::LockServer(BOOL /*fLock*/)
{
    return E_NOTIMPL;
}


ShellExtClassFactory.h
// TortoiseSVN - a Windows shell extension for easy version control

// Copyright (C) 2003-2006 - TortoiseSVN

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#pragma once


/**
 * \ingroup TortoiseShell
 * This class factory object creates the main handlers -
 * its constructor says which OLE class it has to make.
 */
class CShellExtClassFactory : public IClassFactory
{
protected:
    ULONG m_cRef;

public:
    CShellExtClassFactory();
    virtual ~CShellExtClassFactory();

    //@{
    /// IUnknown members
    STDMETHODIMP         QueryInterface(REFIID, LPVOID FAR *);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    //@}

    //@{
    /// IClassFactory members
    STDMETHODIMP      CreateInstance(LPUNKNOWN, REFIID, LPVOID FAR *);
    STDMETHODIMP      LockServer(BOOL);
    //@}
};

4.3) 功能com对象的实现

ShellExt.cpp

host各种用户接口,以及实现代码

// TortoiseSVN - a Windows shell extension for easy version control

// Copyright (C) 2003-2014 - TortoiseSVN

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "platformdefs.h"
#include "Globals.h"
// Initialize GUIDs (should be done only and at-least once per DLL/EXE)
#include "Guids.h"
#include "ShellExt.h"
#undef swprintf

// *********************** CShellExt *************************
CShellExt::CShellExt()
{
    m_cRef = 0L;
    InterlockedIncrement(&g_cRefThisDll);
}

CShellExt::~CShellExt()
{
    InterlockedDecrement(&g_cRefThisDll);
}

STDMETHODIMP CShellExt::QueryInterface(REFIID riid, LPVOID FAR *ppv)
{
    if(ppv == 0)
        return E_POINTER;

    *ppv = NULL;

    if (IsEqualIID(riid, IID_IDemoInterface) || IsEqualIID(riid, IID_IUnknown))
    {
        *ppv = static_cast<IDemoInterface *>(this);
    }
    else
    {
        return E_NOINTERFACE;
    }

    AddRef();
    return S_OK;
}

STDMETHODIMP_(ULONG) CShellExt::AddRef()
{
    return ++m_cRef;
}

STDMETHODIMP_(ULONG) CShellExt::Release()
{
    if (--m_cRef)
        return m_cRef;

    delete this;

    return 0L;
}

STDMETHODIMP_(HRESULT) CShellExt::DemoAdd(int a, int b, int * sum){
	//TRACE("%s(%d,%d,%p) called at %s:%d", __FUNCTION__, a, b, sum, __FILE__, __LINE__);
	*sum = a+b;
	return S_OK;
}


ShellExt.h
// TortoiseSVN - a Windows shell extension for easy version control

// Copyright (C) 2003-2014 - TortoiseSVN

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#pragma once
#include "IDemoInterface.h"

#define MAKESTRING(ID) LoadStringEx(g_hResInst, ID, stringtablebuffer, _countof(stringtablebuffer), (WORD)CRegStdDWORD(_T("Software\\TortoiseSVN\\LanguageID"), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)))

// The actual OLE Shell context menu handler
/**
 * \ingroup TortoiseShell
 * The main class of our COM object / Shell Extension.
 * It contains all Interfaces we implement for the shell to use.
 * \remark The implementations of the different interfaces are
 * split into several *.cpp files to keep them in a reasonable size.
 */
class CShellExt : public IDemoInterface
// COMPILER ERROR? You need the latest version of the
// platform SDK which has references to IColumnProvider
// in the header files.  Download it here:
// http://www.microsoft.com/msdownload/platformsdk/sdkupdate/
{
protected:
    ULONG                           m_cRef;

public:
    CShellExt();
    virtual ~CShellExt();

    /** \name IUnknown
     * IUnknown members
     */
    //@{
    STDMETHODIMP         QueryInterface(REFIID, LPVOID FAR *);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    //@}

    /** \name IShellExtInit
     * IShellExtInit methods
     */
    //@{
    STDMETHODIMP    DemoAdd(int a, int b, int * sum);
    //@}
};

4.4) 用户接口文件

IDemoInterface.h

定制的各种接口都可以这样定义

#ifndef _IDEMO_INTERFACE_H__
#define _IDEMO_INTERFACE_H__

class IDemoInterface : public IUnknown
{
public:
	virtual HRESULT STDMETHODCALLTYPE DemoAdd(int a, int b, int * sum) = 0;
};

#endif

4.5) 模块定义文件

EXPORTS
    DllCanUnloadNow     PRIVATE
    DllGetClassObject   PRIVATE


COM组件的创建过程:

CoCreateInstance
    ->CoGetClassObject: Retrieve dll path from COM name
        ->DllGetClassObject: Retrieve a ptr to Class Factory
             ->IClassFactory::CreateInstance(riid)





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值