终于看到一篇关于在win32里实现COM的文章了,文章很长,写的很好。
http://www.codeproject.com/Articles/13601/COM-in-plain-C#DLL
http://www.codeproject.com/Articles/3365/Embed-an-HTML-control-in-your-own-window-using-pla
1) 要实现的功能
typedef long SetStringPtr(char *);
typedef long GetStringPtr(char *, long);typedef struct {
SetStringPtr * SetString;
GetStringPtr * GetString;
DWORD count;
char buffer[80];
}IExample;
long SetString(char * str)
{
return(0);
}
long GetString(char *buffer, long length)
{
return(0);
}
2)需要将其改为下列的形式
typedef HRESULT STDMETHODCALLTYPE QueryInterfacePtr(IExample *, REFIID, void **);
typedef ULONG STDMETHODCALLTYPE AddRefPtr(IExample *);
typedef ULONG STDMETHODCALLTYPE ReleasePtr(IExample *);
typedef HRESULT STDMETHODCALLTYPE SetStringPtr(IExample *, char *);
typedef HRESULT STDMETHODCALLTYPE GetStringPtr(IExample *, char *, long);
typedef struct {
QueryInterfacePtr *QueryInterface;
AddRefPtr *AddRef;
ReleasePtr *Release;
SetStringPtr *SetString;
GetStringPtr *GetString;
} IExampleVtbl;
typedef struct {
IExampleVtbl *lpVtbl;
DWORD count;
char buffer[80];
} IExample
说明:But all COM objects begin with a pointer to their VTable, and the first three VTable pointers are to the object's QueryInterface, AddRef, and Release functions. The first argument passed to an object's function is a pointer to the object (struct) itself. That is the law. Obey it.
COM Object:
// {0B5B3D8E-574C-4fa3-9010-25B8E4CE24C2}
DEFINE_GUID(CLSID_IExample, 0xb5b3d8e, 0x574c, 0x4fa3,
0x90, 0x10, 0x25, 0xb8, 0xe4, 0xce, 0x24, 0xc2);
COM Object's Vtbl
// {74666CAC-C2B1-4fa8-A049-97F3214802F0}
DEFINE_GUID(IID_IExample, 0x74666cac, 0xc2b1, 0x4fa8,
0xa0, 0x49, 0x97, 0xf3, 0x21, 0x48, 0x2, 0xf0);
说明:In conclusion, every COM object has its own GUID, which is an array of 16 bytes that are different from any other GUID. A GUID is created with the GUIDGEN.EXE utility. A COM object's VTable (i.e., interface) also has a GUID.
4)我们的COM DLL中需要有一个IClassFactory,用它来创建IExample。IClassFactory的实现如下
static const IClassFactoryVtbl IClassFactory_Vtbl = {classQueryInterface,
classAddRef,
classRelease,
classCreateInstance,
classLockServer};
static IClassFactory MyIClassFactoryObj = {&IClassFactory_Vtbl};
- 实现可参照原文,(IClassFactory的实例永远是1,classAddRef和classRelease直接返回1即可。)
- 这里主要要注意classCreateInstance,它是创建用IExample的。
HRESULT STDMETHODCALLTYPE classCreateInstance(IClassFactory *this,
IUnknown *punkOuter, REFIID vTableGuid, void **ppv)
{
HRESULT hr;
struct IExample *thisobj;
// Assume an error by clearing caller's handle.
*ppv = 0;
// We don't support aggregation in IExample.
if (punkOuter)
hr = CLASS_E_NOAGGREGATION;
else
{
// Create our IExample object, and initialize it.
if (!(thisobj = GlobalAlloc(GMEM_FIXED,
sizeof(struct IExample))))
hr = E_OUTOFMEMORY;
else
{
thisobj->lpVtbl = &IExample_Vtbl;
thisobj->count = 1;
hr = IExample_Vtbl.QueryInterface(thisobj, vTableGuid, ppv);
IExample_Vtbl.Release(thisobj);
}
}
return(hr);
}
- 另外classQueryInterface函数的实现,作用返回IClassFactory。
HRESULT STDMETHODCALLTYPE classQueryInterface(IClassFactory *this,
REFIID factoryGuid, void **ppv)
{
// Check if the GUID matches an IClassFactory or IUnknown GUID.
if (!IsEqualIID(factoryGuid, &IID_IUnknown) &&
!IsEqualIID(factoryGuid, &IID_IClassFactory))
{
// It doesn't. Clear his handle, and return E_NOINTERFACE.
*ppv = 0;
return(E_NOINTERFACE);
}
*ppv = this;
// Call our IClassFactory's AddRef, passing the IClassFactory.
this->lpVtbl->AddRef(this);
// Let him know he indeed has an IClassFactory.
return(NOERROR);
}
说明:The really important function is CreateInstance. The program calls our IClassFactory's CreateInstance whenever the program wants us to create one of our IExample objects,
5)继续打包到一个DLL里
为了方便其他的程序调用我们的IClassFactory::CreateInstance, 微软要求我们必须在dll实现一个DllGetClassObject()函数。只是调用上面的classQueryInterface函数返回IClassFactory。
HRESULT PASCAL DllGetClassObject(REFCLSID objGuid,
REFIID factoryGuid, void **factoryHandle)
{
HRESULT hr;
if (IsEqualCLSID(objGuid, &CLSID_IExample))
{
hr = classQueryInterface(&MyIClassFactoryObj,
factoryGuid, factoryHandle);
}
else
{
*factoryHandle = 0;
hr = CLASS_E_CLASSNOTAVAILABLE;
}
return(hr);
}
6)DllCanUnloadNow()函数
这个函数是告诉操作系统是否可以卸载这个DLL了。只要确认IExample的引用个数是否为0。(文章里提到还要判断LockCount,不在这里解释了)
7)补充我们的源文件
- 定义INTERFACE宏
说明:Instead of defining things as above, Microsoft provides a macro we can use to define our VTable and object in a way that works for both C and C++, and hides the extra data members. To use this macro, we must first define the symbol INTERFACE to the name of our object (which in this case is IExample). And prior to that, we must undef that symbol to avoid a compiler warning. Then, we use the DECLARE_INTERFACE_ macro. Inside of the macro, we list our IExample functions. Here's what it will look like:
#undef INTERFACE
#define INTERFACE IExample
DECLARE_INTERFACE_ (INTERFACE, IUnknown)
{
STDMETHOD (QueryInterface) (THIS_ REFIID, void **) PURE;
STDMETHOD_ (ULONG, AddRef) (THIS) PURE;
STDMETHOD_ (ULONG, Release) (THIS) PURE;
STDMETHOD (SetString) (THIS_ char *) PURE;
STDMETHOD (GetString) (THIS_ char *, DWORD) PURE;
};
- DEF文件
LIBRARY IExample
EXPORTS
DllCanUnloadNow PRIVATE
DllGetClassObject PRIVATE
- register our dll
Under our "GUID key", we must create a subkey named InprocServer32. This subkey's default value is then set to the full path where our DLL has been installed.
We must also set a value named ThreadingModel to the string value "both"