本文介绍常用的几种发布COM组件的方式。

用注册表来注册组件

借助于注册表来注册组件,这样可以直接通过CoCreateInstance获取接口

注册表查找组件的CLSID支持两个路径:

	HKCU\Software\Classes\CLSID\{uuid-string}  
	HKCR\CLSID\{uuid-string}

当调用CoCreateInstance时,先查找第一个注册表项,如果查找失败,再查找第二项。

注册信息可以直接在项目注册脚本.rgs文件中查看

如testDLL项目默认生成的test.rhs为:

	HKCR
	{
	    NoRemove AppID
	    {
	       '%APPID%' = s 'testDll'
	       'testDll.DLL'
	       {
	           val AppID = s '%APPID%'
	       }
	    }
	}

也就是说默认是注册到第二个位置HKCR,其实最好将其修改为HKCU下面,因为注册表也是有读写权限的,而HKCR的权限要求比HKCU高。

比如改成:

	HKCU
	{
	    NoRemove Software
	    {
	       NoRemove Classes
	       {  
	           NoRemove AppID
	           {
	              '%APPID%' = s 'testDll'
	              'testDll.DLL'
	              {
	                  val AppID = s '%APPID%'
	              }
	           }
	       }
	    }
	}

发布程序时,需要注册对应的组件

所以下面介绍这两种方式的简单实现:

void RegDll_export()
{
    typedef HRESULT (WINAPI * FREG)();
 
    HMODULE hDll = LoadLibrary( L"testDll.dll" );
    if ( hDll )
    {
       FREG lpfunc = (FREG)::GetProcAddress( hDll, "DllRegisterServer" );
       if ( lpfunc )
       {
           lpfunc();
       }
 
       FreeLibrary(hDll);
    }
}
void RegDll_regsvr32()
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    memset(&si, 0, sizeof(si));
    memset(&pi, 0, sizeof(pi));
    si.cb = sizeof(si);
 
    static const int MAX_PATH_LEN = 1025;
    wchar_t processPath[MAX_PATH_LEN] = {0};
 
    GetCurrentDirectory( MAX_PATH_LEN, processPath ); // 当前路径下
    CString strCmd = L"regsvr32.exe";
    strCmd += L" /s "; // 静默方式
    strCmd += CString("\"") + CString(processPath) + CString("\"") + 
        L"\\testDll.dll" ; // 命令行可能不支持空格路径
    BOOL bOK = CreateProcess( 0, const_cast<LPTSTR>( (LPCTSTR)strCmd ),
       NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi );
    if ( bOK )
    {
       WaitForSingleObject( pi.hProcess, INFINITE );
       ::CloseHandle( pi.hProcess );
       ::CloseHandle( pi.hThread );
    }
}

release发布程序时还要注意几个问题:

借助于manifest免注册机制(替代注册表)

  1. 官方称为Reg-free或者SideBySide技术
  2. 基本思想:是将注册信息免去注册到注册表,而是直接使用本地的一个manifest文件来按照一定的格式存储组件信息。查找组件的时候,能从该文件中读到对应的信息。参考:http://msdn.microsoft.com/en-us/library/aa374219(v=VS.85).aspx
  3. 使用Reg-free,需要一些设置:
    • 需要将主程序exe对应的工程,设置非嵌入清单,这样它就在对应的目录下生成与主程序同名并且加上manifest后缀的配置文件。路径:工程右键属性—清单工具—输入与输出—嵌入清单,选择否。
    • 在manifest文件中,按照语法添加组件信息。

比如:某个动态库testDll.dll的IDL文件为

import "oaidl.idl";
import "ocidl.idl";
 
[
    object,
    uuid(EEB0D834-0984-4D8D-94B5-3F0E2168C450),
    dual,
    nonextensible,
    helpstring("IInterfaceTest 接口"),
    pointer_default(unique)
]
interface IInterfaceTest : IDispatch{
    [id(1), helpstring("方法Print")] HRESULT Print([in] LONG val1, [in] LONG val2);
};
[
    uuid(B6F0BC76-3F04-424A-85F3-DA7C0CE89EF5),
    version(1.0),
    helpstring("testDll 1.0 类型库")
]
library testDllLib
{
    importlib("stdole2.tlb");
    [
       uuid(0C7116D0-8ABE-4AEC-A3CB-88CDA86CFFA1),
       helpstring("InterfaceTest Class")
    ]
    coclass InterfaceTest
    {
       [default] interface IInterfaceTest;
    };
};

主程序testexc.exe依赖于该组件,则test.exe.manifest为:

	<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
	<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
	  <dependency>
	    <dependentAssembly>
	      <assemblyIdentity type="win32" name="Microsoft.VC80.CRT" version="8.0.50727.762" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
	    </dependentAssembly>
	  </dependency>
	  <dependency>
	    <dependentAssembly>
	      <assemblyIdentity type="win32" name="Microsoft.VC80.ATL" version="8.0.50727.762" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
	    </dependentAssembly>
	  </dependency>
	 
	  <file name="testDll.dll" hashalg="SHA1">
	    <comClass clsid="{0C7116D0-8ABE-4AEC-A3CB-88CDA86CFFA1}">
	    </comClass>
	    <typelib tlbid="{B6F0BC76-3F04-424A-85F3-DA7C0CE89EF5}" version="1.0" helpdir=""></typelib>
	  </file>
	 
	</assembly>

说明:

自己接管类厂来创建对象

要创建组件,首先已经准备好了:

根据COM的机制,下面来模拟CoCreateInstance的行为:

  1. 加载(LoadLibrary)动态库,找到导出函数”DllGetClassObject”
  2. 使用组件CLSID和导出函数DllGetClassObject创建类厂,得到接口IClassFactory
  3. 使用类厂的方法CreateInstance创建组件,得到对应的接口

注:
CoCreateInstance依赖的动态库从注册表中找,而我们自己接管过程,则不需要涉及注册表。

例如:

HINSTANCE hModule = ::LoadLibrary( pathDll );
if ( hModule )
{
    typedef HRESULT (__stdcall *FUNCTYPE_DllGetClassObject)(REFCLSID, REFIID, void**);
 
    FUNCTYPE_DllGetClassObject funGetClassObject = 
        (FUNCTYPE_DllGetClassObject)::GetProcAddress(hModule, "DllGetClassObject");
    if ( funGetClassObject )
    {
       // 获取类厂
       CComPtr<IClassFactory> spClassFactory;
       funGetClassObject( __uuidof(InterfaceTest), __uuidof(IClassFactory), (void**)&spClassFactory );
      
       if ( spClassFactory )
       {
           CComPtr<IInterfaceTest> spTest;
           spClassFactory->CreateInstance( NULL, __uuidof(IInterfaceTest), (void**)&spTest );
           if ( spTest )
           {
              spTest->Print(50, 50);
           }
           else
           {
            ::MessageBox( 0, L"Error to get Interface", L"DllTest", 0 );
           }
       }
 
    }
 
    ::FreeLibrary(hModule);
}

不过,需要注意的是自己接管的COM方式,只适合进程内,因为它会导致进程外组件的获取成为一个问题(就算通过序列化IStream传递过去了,也没有办法查询到对应的接口)。这也说明,默认的COM的API还能为我们处理进程间的组件传递的附加操作。