编写第一个com组件


新建项目>>Win32项目>应用程序类型选择DLL 勾选空项目

然后首先新建一个idl文件,我们取名为 MyCom.idl

image.png

MyCom.idl文件里删掉向导给我们创建的两行代码。并在里面写入:

import "unknwn.idl";

[object, uuid("288220D0-1677-4B57-B8FA-C71DA81E7C34")]
interface IDog :IUnknown{
	HRESULT Bark([in, out]long* plong);
};

[object, uuid("637C3E42-173F-4AEF-B3B7-DA9218AFD166")]
interface IPig :IUnknown{
	HRESULT Heng([in, out]long* plong);
};

[object, uuid("E02418EC-FCFB-489B-8D4C-5B186E4AEDEE")]
interface IMonkey :IUnknown{
	HRESULT Zhi([in, out]long* plong);
};

[uuid(9D3931EB-019C-441D-B6C6-0477600DDF02),]//这个com组件的CLSID就是这个
coclass MyCom //定义一个com组件  组件的名字叫MyCom
{
	interface IMonkey;
	interface IPig;
	interface IDog;
};

以上代码 我们定义了三个接口 分别是  狗,猪,猴  对应的都有一个叫声的方法: bark,heng,zhi

每个接口里都有一个 object和uuid  这两个属性是每个接口必须要有的

接口的uuid 可以在vs里点击工具>>创建GUID来获取

每个接口都继承了IUnknown接口

然后点击生成  会自动给我们生成一些文件,我们把Mycom_h.hMyCom_i.c分别引入到项目中

image.png

然后 再新建一个MyCom.cpp 和 MyCom.h

MyCom.h里写入接口对应的函数声明:

#include"MyCom_h.h"
EXTERN_C const CLSID CLSID_MyCom;
class MyCom :public IDog, public IMonkey, public IPig
{
	long m_Count;
public:
	MyCom();
	virtual HRESULT STDMETHODCALLTYPE QueryInterface(
		/* [in] */ REFIID riid,
		/* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR *__RPC_FAR *ppvObject);

	virtual ULONG STDMETHODCALLTYPE AddRef(void);

	virtual ULONG STDMETHODCALLTYPE Release(void);

	virtual HRESULT STDMETHODCALLTYPE Bark(
		/* [out][in] */ long *plong);
	virtual HRESULT STDMETHODCALLTYPE Heng(
		/* [out][in] */ long *plong);
	virtual HRESULT STDMETHODCALLTYPE Zhi(
		/* [out][in] */ long *plong);

	//long MyTest();
};

MyCom.cpp 里写实现

#include"MyCom.h"

MyCom _MyCom;

MyCom::MyCom() :m_Count(0){};


HRESULT STDMETHODCALLTYPE MyCom::QueryInterface(REFIID riid, void**ppvObject)//查询接口的函数
{
	if (!ppvObject)
		return E_POINTER;
	*ppvObject = NULL;
	if (riid == IID_IDog)//判断传入的riid是不是dog的
	{
		AddRef();//如果是  计数加一
		*ppvObject = dynamic_cast<IDog*>(this);  //并把狗的 函数指针的数组 返回去(类实际上就是一堆函数指针数组)
	}
	else if (riid == IID_IPig)//判断传入的riid是不是pig的
	{
		AddRef();//如果是  计数加一
		*ppvObject = dynamic_cast<IPig*>(this);//并把猪的 函数指针的数组 返回去(类实际上就是一堆函数指针数组)
	}
	else if (riid == IID_IMonkey)//判断传入的riid是不是Monkey的
	{
		AddRef();//如果是  计数加一
		*ppvObject = dynamic_cast<IMonkey*>(this);//并把猴的 函数指针的数组 返回去(类实际上就是一堆函数指针数组)
	}

	if (*ppvObject == NULL)//如果传入的iid 不是我们已实现了的接口
	{
		return E_NOINTERFACE;//返回一个错误  表示没有这个接口
	}

	return S_OK;
}

ULONG STDMETHODCALLTYPE MyCom::AddRef(void)//计数器 增加的函数
{
	InterlockedIncrement(&m_Count); //InterlockedIncrement :原子递增
	return m_Count;
}

ULONG STDMETHODCALLTYPE MyCom::Release(void)
{
	InterlockedDecrement(&m_Count);
	//if(m_Count==0)
	//delete this;
	//如果类实例作为全局变量,那么显然就不能这么释放这个类实例了。应该怎么释放呢?
	//如果是全局变量,要释放这个类实例,只能把这个dll从进程的地址空间中拿掉、
	return m_Count;
}

HRESULT STDMETHODCALLTYPE MyCom::Bark(
	/* [out][in] */ long *plong)
{
	MessageBox(NULL, L"Dog", NULL, MB_OK);
	return S_OK;
}
HRESULT STDMETHODCALLTYPE MyCom::Heng(
	/* [out][in] */ long *plong)
{
	MessageBox(NULL, L"Pig", NULL, MB_OK);
	return S_OK;
}
HRESULT STDMETHODCALLTYPE MyCom::Zhi(
	/* [out][in] */ long *plong)
{
	MessageBox(NULL, L"Monkey", NULL, MB_OK);
	return S_OK;
}

//CoGetClassObject
//由于一个dll 可能有多个com组件 所以这里会根据com组件的CLSID和接口的IID来查询接口
//本示例程序里 只有一个com组件
STDAPI DllGetClassObject(const CLSID &rclsid, const IID  &riid, void  **ppv)
{
	if (rclsid == CLSID_MyCom)
	{
		return _MyCom.QueryInterface(riid, ppv);
	}
	else
	{
		return S_FALSE;
	}
}

再在源文件里增加一个.def文件  文件名默认Source.def就好

image.png

里面写入: //后面的 PRIVATE 意思是不要静态装入的意思

LIBRARY
	EXPORTS
		DllGetClassObject  PRIVATE

然后就可以编译了,此时虽然可以编译成功,但是并不是一个真正的com组件,要想变成真正的com组件还需要继续往下看

COM是如何被程序调用的?

当程序要调用某个com组件的时候,会通过ole32.cll的一个服务来查询这个组件的所在位置(根据注册表来查)

并把这个com的cll装载到程序的内存中, 然后调用一个叫做DllGetClassObject的函数


未完待续