让 COM 脱离注册表

作者: admin 分类: win32 发布时间: 2014-03-28 19:13 ė1,732 浏览数 6没有评论
文章转自王牌软件
站长推荐:NSetup一键部署软件
一键式完成美化安装包制作,自动增量升级,数据统计,数字签名。应对各种复杂场景,脚本模块化拆分,常规复杂的脚本代码,图形化设置。无需专业的研发经验,轻松完成项目部署。(www.nsetup.cn)

引言

在上一篇《在 DLL 中加入第二个 COM 》的“单用户注册”一节中,我们曾提到脱离注册表依赖一事,现在我们来把这事儿给办了。

 

注册

我们在之前支持了“regsvr32 /n /i:user COMProvider.dll”这一注册命令。这一注册命令给了我们一定的扩展余地。从ATL默认的代码来看,对于DllInstall,目前已定义的命令行参数似乎只有user,于是我们可以定义自己的。

 

本文中,我们将从一个INI文件读入COM的相关信息,同时,也提供注册选项注册到INI文件。注册命令定义为:

regsvr32 /n /i:INI COMProvider.dll

regsvr32 /n /i:INI:FileName.ini COMProvider.dll

其中,第一条命令注册到工作目录的一个默认文件名,第二条命令注册到FileName.ini,可以带路径(相对于工作目录)。

 

因此,首先改造ComModule::DllInstall如下:

 

 
默认INI名字定为xlComReg.ini。这里调用了四个函数:

RegisterTypeLibToIni

UnregisterComClassesFromIni

RegisterComClassesToIni

UnregisterTypeLibFromIni

与之前写注册表的四个函数并列。这四个函数的实现比较简单,就是将之前写注册表的那几个函数换成写INI的,就可以了。代码如下:

 

RegisterTypeLibToIni

bool RegisterTypeLibToIni(const String &strIniFileName) 

{

if (!IniFile::SetValue(strIniFileName, m_strLibID, _T(“TypeLib”), m_strLibName))

{

return false;

}

 

if (!IniFile::SetValue(strIniFileName, m_strLibID, _T(“Version”), m_strLibVersion))

{

return false;

}

 

String strModulePath = GetModuleRelativePathToIni(strIniFileName);

 

#ifdef _WIN64

if (!IniFile::SetValue(strIniFileName, m_strLibID, _T(“Win64″), strModulePath))

{

return false;

}

#else

if (!IniFile::SetValue(strIniFileName, m_strLibID, _T(“Win32″), strModulePath))

{

return false;

}

#endif

return true;

}

 

UnregisterComClassesFromIni

bool UnregisterTypeLibFromIni(const String &strIniFileName) 

{

if (!IniFile::DeleteSection(strIniFileName, m_strLibID))

{

return false;

}

 

return true;

}

 

RegisterComClassesToIni

bool RegisterComClassesToIni(const String &strIniFileName) 

{

for (const ClassEntry * const *ppEntry = &LP_CLASS_BEGIN + 1; ppEntry < &LP_CLASS_END; ++ppEntry)

{

if (*ppEntry == nullptr)

{

continue;

}

 

TCHAR szClassID[40] = {};

StringFromGUID2(*(*ppEntry)->pClsid, szClassID, ARRAYSIZE(szClassID));

 

String strVersionIndependentProgID = (*ppEntry)->lpszProgID;

String strProgID = strVersionIndependentProgID + _T(“.”) + (*ppEntry)->lpszVersion;

 

if (!IniFile::SetValue(strIniFileName, szClassID, _T(“Class”), (*ppEntry)->lpszClassDesc))

{

return false;

}

 

String strModulePath = GetModuleRelativePathToIni(strIniFileName);

 

#ifdef _WIN64

if (!IniFile::SetValue(strIniFileName, szClassID, _T(“InprocServer64″), strModulePath))

{

return false;

}

#else

if (!IniFile::SetValue(strIniFileName, szClassID, _T(“InprocServer32″), strModulePath))

{

return false;

}

#endif

 

if (!m_strLibID.Empty())

{

if (!IniFile::SetValue(strIniFileName, szClassID, _T(“TypeLib”), m_strLibID))

{

return false;

}

}

 

if (!strProgID.Empty())

{

if (!IniFile::SetValue(strIniFileName, szClassID, _T(“ProgID”), strProgID))

{

return false;

}

 

if (!IniFile::SetValue(strIniFileName, strProgID, _T(“Class”), (*ppEntry)->lpszClassDesc))

{

return false;

}

 

if (!IniFile::SetValue(strIniFileName, strProgID, _T(“CLSID”), szClassID))

{

return false;

}

}

 

if (!strVersionIndependentProgID.Empty())

{

if (!IniFile::SetValue(strIniFileName, strVersionIndependentProgID, _T(“Class”), (*ppEntry)->lpszClassDesc))

{

return false;

}

 

if (!IniFile::SetValue(strIniFileName, strVersionIndependentProgID, _T(“CurVer”), strProgID))

{

return false;

}

 

if (!IniFile::SetValue(strIniFileName, strVersionIndependentProgID, _T(“CLSID”), szClassID))

{

return false;

}

}

}

 

return true;

}

 

UnregisterTypeLibFromIni

bool UnregisterComClassesFromIni(const String &strIniFileName) 

{

for (const ClassEntry * const *ppEntry = &LP_CLASS_BEGIN + 1; ppEntry < &LP_CLASS_END; ++ppEntry)

{

if (*ppEntry == nullptr)

{

continue;

}

 

TCHAR szClassID[40] = {};

StringFromGUID2(*(*ppEntry)->pClsid, szClassID, ARRAYSIZE(szClassID));

 

String strVersionIndependentProgID = (*ppEntry)->lpszProgID;

String strProgID = strVersionIndependentProgID + _T(“.”) + (*ppEntry)->lpszVersion;

 

if (!IniFile::DeleteSection(strIniFileName, szClassID))

{

return false;

}

 

if (!strProgID.Empty())

{

if (!IniFile::DeleteSection(strIniFileName, strProgID))

{

return false;

}

}

 

if (!strVersionIndependentProgID.Empty())

{

if (!IniFile::DeleteSection(strIniFileName, strVersionIndependentProgID))

{

return false;

}

}

}

 

return true;

}

 

其中DLL路径用的是DLL相对于INI的相对路径,用函数GetModuleRelativePathToIni获取,该函数的实现如下:

 

String GetModuleRelativePathToIni(const String &strIniFileName) 

{

TCHAR szIniPathAbsolute[MAX_PATH] = {};

if (GetFullPathName(strIniFileName.GetAddress(), ARRAYSIZE(szIniPathAbsolute), szIniPathAbsolute, nullptr) == 0)

{

return m_strModulePath;

}

 

TCHAR szModuleRelativePath[MAX_PATH] = {};

 

if (!PathRelativePathTo(szModuleRelativePath, szIniPathAbsolute, 0, m_strModulePath.GetAddress(), 0))

{

return m_strModulePath;

}

 

return szModuleRelativePath;

}

 

 

上面代码将一个COM在注册表中的所有信息全部写到了INI。其实这是不必要的,对于C++程序来说,要使用这个COM,可只需要知道CLSID对应到哪个DLL就可以了。因此,上面用橙色波浪线划出的代码可以去掉不用,不影响后续使用。

 

好了,运行“regsvr32 /n /i:ini COMProvider.dll”,生成xlComReg.ini,内容如下:

 

[{0DECBFF5-A8A5-49E8-9962-3D18AAC6088E}] 

Class=Streamlet COMProvider Sample Class

InprocServer32=.\COMProvider.dll

 

加载

注册好了,该使用了。这就涉及COM的加载过程了。简单的说,我们一般先CoInitialize,然后CoCreateInstance拿到对象去使用,完了之后CoUninitialize使用完毕。现在我们就来模拟这个过程。除了这三个函数以外,我们还将模拟CoGetClassObject以及CoFreeUnusedLibraries

 

于是加载器接口定义为:

struct __declspec(uuid(“FE52639A-5B41-49B0-9A50-7A1C4FBC83E2″)) 

IComLoader : public IDispatch

{

virtual HRESULT CoInitialize(_In_opt_ LPVOID pvReserved) PURE;

virtual void CoUninitialize() PURE;

virtual void CoFreeUnusedLibraries() PURE;

virtual HRESULT CoGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Out_ LPVOID *ppv) PURE;

virtual HRESULT CoCreateInstance(_In_ REFCLSID rclsid, _In_ REFIID riid, _Out_ LPVOID *ppv) PURE;

};

 

ComLoader本身将作为一个Com类实现,因此我在IComLoader的声明中加上了UUID。然后我们针对注册到INICOM写一个Loader

 

相关数据结构定义如下:

 

typedef HRESULT (__stdcall *FnDllCanUnloadNow)(); 

typedef HRESULT (__stdcall *FnDllGetClassObject)(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID *ppv);

 

struct ComDllModule

{

String              strFileName;

HMODULE             hModule;

FnDllCanUnloadNow   fnDllCanUnloadNow;

FnDllGetClassObject fnDllGetClassObject;

 

ComDllModule() : hModule(nullptr), fnDllCanUnloadNow(nullptr), fnDllGetClassObject(nullptr)

{

}

};

 

typedef Map<String, String>       ClassIDPathMap;

typedef Map<String, ComDllModule> PathModuleMap;

 

 

前面两行定义函数指针,这两个函数是COM DLL导出的。对于每个被加载的DLL,我们将查找这两个函数入口。ComDllModule结构用于保存一个已加载的COM DLL的信息。各分量意义很明白了,不解释。最后两个Map,一个是用于存储从INI读入的CLSIDDLL路径的对应关系,另一个是存储DLL加载后,DLL路径到ComDllModule结构的对应关系。

 

下面是ComLoaderFromIni的框架性定义:

 

class ComLoaderFromIni : public ComClass<ComLoaderFromIni>, 

public Dispatcher<IComLoader>

{

public:

ComLoaderFromIni(const String &strIniFile =_T(“xlComReg.ini”)) :

m_strIniFile(strIniFile), m_lInitializeCount(0)

{

}

 

~ComLoaderFromIni()

{

CoUninitialize();

}

 

public:

HRESULT CoInitialize(_In_opt_ LPVOID pvReserved)

{

return S_OK;

}

void CoUninitialize()

{

 

}

void CoFreeUnusedLibraries()

{

 

}

HRESULT CoGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Out_ LPVOID *ppv)

{

return S_OK;

}

 

private:

HRESULT FindComDllModule(REFCLSID rclsid, const ComDllModule **ppModule)

{

return S_OK;

}

 

bool LoadComDll(const String &strFileName)

{

return true;

}

 

private:

String          m_strIniFile;

LONG            m_lInitializeCount;

ClassIDPathMap  m_mapClassIDToPath;

PathModuleMap   m_mapPathToModule;

CriticalSection m_cs;

 

public:

XL_COM_INTERFACE_BEGIN(ComLoaderFromIni)

XL_COM_INTERFACE(IComLoader)

XL_COM_INTERFACE(IDispatch)

XL_COM_INTERFACE_END()

};

 

其中主要函数目前还没实现。成员变量中有个m_lInitializeCount,是给CoInitializeCoUninitialize做引用计数的,CriticalSection是给两个Map加锁用的。其余变量的意义很明白,也不介绍了。

 

LoadComDll

首先看最后一个函数,LoadComDll。它用于加载指定的COM DLL,并将模块信息存入Map。实现如下:

 

bool LoadComDll(const String &strFileName) 

{

XL_SCOPED_CRITICAL_SECTION(m_cs);

 

HMODULE hModule = LoadLibrary(strFileName.GetAddress());

 

if (hModule == nullptr)

{

return false;

}

 

ScopeGuard sgFreeLibrary = MakeGuard(Bind(FreeLibrary, hModule));

 

FnDllCanUnloadNow fnDllCanUnloadNow = (FnDllCanUnloadNow)GetProcAddress(hModule, “DllCanUnloadNow”);

 

if (fnDllCanUnloadNow == nullptr)

{

return false;

}

 

FnDllGetClassObject fnDllGetClassObject = (FnDllGetClassObject)GetProcAddress(hModule, “DllGetClassObject”);

 

if (fnDllGetClassObject == nullptr)

{

return false;

}

 

ComDllModule &module = m_mapPathToModule[strFileName];

module.strFileName = strFileName;

module.hModule = hModule;

module.fnDllCanUnloadNow = fnDllCanUnloadNow;

module.fnDllGetClassObject = fnDllGetClassObject;

 

sgFreeLibrary.Dismiss();

 

return true;

}

 

FindComDllModule

倒数第二个函数,FindComDllModule,定义为从CLSID找到ComDllModule。首先从m_mapClassIDToPath找到路径,再尝试从m_mapPathToModule找到ComModule。如果未找到,那就尝试使用上面的LoadComDll加载它。代码如下:

 

HRESULT FindComDllModule(REFCLSID rclsid, const ComDllModule **ppModule) 

{

XL_SCOPED_CRITICAL_SECTION(m_cs);

 

if (ppModule == nullptr)

{

return E_INVALIDARG;

}

 

*ppModule = nullptr;

TCHAR szClassID[40] = {};

StringFromGUID2(rclsid, szClassID, ARRAYSIZE(szClassID));

 

auto itPath = m_mapClassIDToPath.Find(szClassID);

 

if (itPath == m_mapClassIDToPath.End())

{

return REGDB_E_CLASSNOTREG;

}

 

auto itModule = m_mapPathToModule.Find(itPath->Value);

 

if (itModule == m_mapPathToModule.End())

{

if (!LoadComDll(itPath->Value))

{

return E_FAIL;

}

 

itModule = m_mapPathToModule.Find(itPath->Value);

}

 

if (itModule == m_mapPathToModule.End())

{

return E_FAIL;

}

 

*ppModule = &itModule->Value;

 

return S_OK;

}

 

CoInitialize

下面按使用流程分别介绍五个标准函数。首先是CoInitialize,它主要就是从INI读取CLSIDDLL路径的对应关系,代码如下:

 

HRESULT CoInitialize(_In_opt_ LPVOID pvReserved) 

{

XL_SCOPED_CRITICAL_SECTION(m_cs);

 

if (m_lInitializeCount > 0)

{

InterlockedIncrement(&m_lInitializeCount);

return S_FALSE;

}

 

Array<String> arrSections;

 

if (!IniFile::EnumSections(m_strIniFile, &arrSections))

{

return E_FAIL;

}

 

for (auto it = arrSections.Begin(); it != arrSections.End(); ++it)

{

String strClass;

 

if (!IniFile::GetValue(m_strIniFile, *it, _T(“Class”), &strClass))

{

continue;

}

 

String strPath;

 

#ifdef _WIN64

if (!IniFile::GetValue(m_strIniFile, *it, _T(“InprocServer64″), &strPath))

{

continue;

}

#else

if (!IniFile::GetValue(m_strIniFile, *it, _T(“InprocServer32″), &strPath))

{

continue;

}

#endif

 

m_mapClassIDToPath.Insert(*it, strPath);

}

 

InterlockedIncrement(&m_lInitializeCount);

 

return S_OK;

}

 

需要留意的就是引用计数处理,这使得多次调用CoInitialze也是安全的。

 

CoGetClassObject

这个函数用于取得类厂。由于上面已经准备了FindComDllModule,直接调用获取到ComDllModule信息,然后调用COM DLL导出的DllGetClassObject就可以了:

 

HRESULT CoGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Out_ LPVOID *ppv) 

{

XL_SCOPED_CRITICAL_SECTION(m_cs);

 

const ComDllModule *pModule = nullptr;

HRESULT hr = FindComDllModule(rclsid, &pModule);

 

if (FAILED(hr))

{

return hr;

}

 

return pModule->fnDllGetClassObject(rclsid, riid, ppv);

}

 

CoCreateInstance

由于上面已经可以拿到类厂了,这里直接调用,获取类厂后调用类厂的CreateInstance创建对象:

 

HRESULT CoCreateInstance(_In_ REFCLSID rclsid, _In_ REFIID riid, _Out_ LPVOID *ppv) 

{

IClassFactory *pClassFactory = nullptr;

HRESULT hr = CoGetClassObject(rclsid, __uuidof(IClassFactory), (LPVOID *)&pClassFactory);

 

if (FAILED(hr))

{

return hr;

}

 

hr = pClassFactory->CreateInstance(NULL, riid, ppv);

pClassFactory->Release();

 

return hr;

}

 

CoFreeUnusedLibraries

这个函数用于清理不再使用的COM DLL。实现思路就是遍历已加载的模块信息,调用DllCanUnloadNow,如果DLL返回S_OK,将其卸载:

 

void CoFreeUnusedLibraries() 

{

XL_SCOPED_CRITICAL_SECTION(m_cs);

 

for (auto it = m_mapPathToModule.Begin(); it != m_mapPathToModule.End(); )

{

if (it->Value.fnDllCanUnloadNow() == S_OK)

{

FreeLibrary(it->Value.hModule);

it = m_mapPathToModule.Delete(it);

}

else

{

++it;

}

}

}

 

CoUninitialize

这是最终的卸载函数,卸载所有已加载的DLL,清除所有信息:

 

void CoUninitialize() 

{

XL_SCOPED_CRITICAL_SECTION(m_cs);

 

if (m_lInitializeCount == 0)

{

return;

}

 

InterlockedDecrement(&m_lInitializeCount);;

 

if (m_lInitializeCount > 0)

{

return;

}

 

for (auto it = m_mapPathToModule.Begin(); it != m_mapPathToModule.End(); ++it)

{

FreeLibrary(it->Value.hModule);

}

 

m_mapClassIDToPath.Clear();

m_mapPathToModule.Clear();

}

 

需要注意的是,这里也有引用计数的处理,与CoInitialize重的对应。

 

使用

使用之前,先为刚才的加载器创建一个工厂函数吧(当然,直接使用也没关系):

 

enum ComLoadType 

{

CLT_FROM_INI,

};

 

inline IComLoader *CreateComLoader(ComLoadType type, const String &strData = _T(“xlComReg.ini”))

{

IComLoader *pLoader = nullptr;

 

switch (type)

{

case CLT_FROM_INI:

pLoader = new ComLoaderFromIni(strData);

pLoader->AddRef();

break;

default:

break;

}

 

return pLoader;

}

 

目前只有INI加载器。

 

使用方式如下:

 

int _tmain(int argc, TCHAR *argv[]) 

{

xl::IComLoader *pComLoader = xl::CreateComLoader(xl::CLT_FROM_INI);

HRESULT hr = pComLoader->CoInitialize(NULL);

 

ISampleInterface *pSampleInterface = nullptr;

hr = pComLoader->CoCreateInstance(__uuidof(SampleClass),

__uuidof(ISampleInterface),

(LPVOID *)&pSampleInterface);

 

if (SUCCEEDED(hr))

{

pSampleInterface->SampleMethod();

pSampleInterface->Release();

}

 

pComLoader->Release();

 

return 0;

}

 

运行结果:

 

 

以上,框架代码见:

http://xllib.codeplex.com/SourceControl/changeset/view/20034#319450

例子代码见COMProtocol4.rarhttp://sdrv.ms/SF3Ebm

 

在本文中,我们脱离了注册表依赖,将COM信息存到了本目录文件,这意味着我们在发布的时候可以事先生成这个文件,或者可以由安装程序生成。至此,我们给出了使用COM DLL作为替代普通DLL的一整套方案。如果能忍受COM接口讨厌的有限的变量类型,以及冗长的行文方式,COM DLL的形式将比普通DLL导出函数更具有优势,可以作为动态链接库实现形式的一般解决方案。

 

等等,有人可能会问:套间模型哪去了呢?

 

——套间模型是什么?这显然不影响我们将COM DLL用于替代普通DLL的整个过程。不需要这个概念。



只回答业务咨询点击这里给我发消息 点击这里给我发消息

学习日记,兼职软件设计,软件修改,毕业设计。

本文出自 学习日记,转载时请注明出处及相应链接。

本文永久链接: https://www.softwareace.cn/?p=751

0

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">


Ɣ回顶部

无觅相关文章插件,快速提升流量