最近放假了,闲来没事,总结一下免注册调用COM组件的两种办法。虽然是老古董的东西,但是对经常需要用VB/VC做一些小工具的我来说还是很有用的。
一、调用DLL的DllGetClassObject函数
下面是VC++实现的代码,原理很简单,直接看代码就明白了,代码面前毫无秘密。
HRESULT WINAPI GetClassIID(LPCTSTR lpszDllFileName, LPCTSTR lpszClassName, REFIID classIID)
{
ITypeLib* lpTypeLib = NULL;
HRESULT hr = LoadTypeLibEx(lpszDllFileName, REGKIND_NONE, &lpTypeLib);
if (FAILED(hr))return hr;
CComPtr pTypeLib(lpTypeLib);
UINT nTypeInfoCount = pTypeLib->GetTypeInfoCount();
for (UINT i = 0 ; i < nTypeInfoCount; i++)
{
ITypeInfo* lpTypeInfo = NULL;
hr = pTypeLib->GetTypeInfo(i, &lpTypeInfo);
if (SUCCEEDED(hr))
{
CComPtr pTypeInfo(lpTypeInfo);
TYPEATTR* lpTypeAttr = NULL;
hr = lpTypeInfo->GetTypeAttr(&lpTypeAttr);
if (SUCCEEDED(hr))
{
if (lpTypeAttr->typekind == TKIND_COCLASS)
{
_bstr_t strTmp;
hr = pTypeLib->GetDocumentation(i, strTmp.GetAddress(), NULL, NULL, NULL);
if (SUCCEEDED(hr))
{
if (strTmp == _bstr_t(lpszClassName))
{
CopyMemory((void*)&classIID, &(lpTypeAttr->guid), sizeof IID);
pTypeInfo->ReleaseTypeAttr(lpTypeAttr);
return S_OK;
}
}
}
pTypeInfo->ReleaseTypeAttr(lpTypeAttr);
}
}
}
return S_FALSE;
}
HRESULT WINAPI CreateDllObjectW(LPCTSTR lpszDllFileName, LPCTSTR lpszClassName, IDispatch **ppvObject)
{
IID classIID = {0};
HRESULT hr = GetClassIID(lpszDllFileName, lpszClassName, classIID);
if (FAILED(hr)) return hr;
HINSTANCE hDllInst = GetModuleHandle(lpszDllFileName);
if (!hDllInst) hDllInst = LoadLibrary(lpszDllFileName);
if (!hDllInst) return S_FALSE;
typedef HRESULT (__stdcall* pfnDllGetClassObject)(REFCLSID, REFIID, void**);
pfnDllGetClassObject fnDllGetClassObject = (pfnDllGetClassObject)GetProcAddress(hDllInst, "DllGetClassObject");
if (!fnDllGetClassObject)
{
FreeLibrary(hDllInst);
return S_FALSE;
}
IClassFactoryPtr lpClassFactory = NULL;
hr = (fnDllGetClassObject)(classIID, IID_IClassFactory, (void**)&lpClassFactory);
if (SUCCEEDED(hr))
{
hr = lpClassFactory->CreateInstance(NULL, IID_IDispatch, (void**)ppvObject);
if (SUCCEEDED(hr)) return S_OK;
}
FreeLibrary(hDllInst);
return S_FALSE;
};
HRESULT WINAPI CreateDllObjectA(LPCSTR lpszDllFileName, LPCSTR lpszClassName, IDispatch **ppvObject)
{
return CreateDllObjectW(CA2T(lpszDllFileName), CA2T(lpszClassName), ppvObject);
}
我把上面代码编译成一个DLL,方便给VB使用,文章最后有源码的下载链接。如果要用纯VB代码实现上面的功能,就比较费劲。但也是可以实现的,有兴趣的可以自己去试试,这样就不用多带一个DLL文件。
在VB中还有一种变通的办法,详细可以看我之前写的另外一个例子。
VB调用JMail发邮件(无需注册JMail.dll)
二、利用Manifest清单文件
关于Manifest清单文件的详细介绍,可以到微软的网站看一下。
https://learn.microsoft.com/zh-cn/windows/win32/sbscs/manifest-files-reference
下面是一个免注册调用COMDLG32.OCX和TLBINF32.DL的清单文件,里面的每一项在微软的网站都有详细的介绍。
我做了一个小工具用来生成Manifest清单文件,这个小工具同时也是一个Manifest清单文件应用的例子。文章最后有源码的下载链接。
三、两种方法的比较
方法一:
一般用于调用自己开发的,可能经常需要更改的COM类库,也可以用于调用一些通用的类库,但是函数参数中不能含有自定义类型的参数,比如结构体类型的参数。
方法二:
一般用于调用接口稳定的DLL类库,或者OCX等需要处理事件的组件。用这个办法,开发软件和以前一样,只是发布的时候增加一个清单文件即可,或者直接把清单文件嵌入资源里面。
两种方法可以结合在一起同时使用,怎么方便怎么来。
四、源码下载以及学习资源
免注册调用COM组件方法一-DllGetClassObject
https://download.csdn.net/download/Zezese/87394537
免注册调用COM组件方法二-AssemblyManifests
https://download.csdn.net/download/Zezese/12269960
清单文件参考 - Win32 apps | Microsoft Learn
https://learn.microsoft.com/zh-cn/windows/win32/sbscs/manifest-files-reference
Isolated Applications and Side-by-side Assemblies - Win32 apps | Microsoft Learn
https://learn.microsoft.com/en-us/windows/win32/sbscs/isolated-applications-and-side-by-side-assemblies-portal
Building C/C++ Isolated Applications and Side-by-side Assemblies | Microsoft Learn
https://learn.microsoft.com/zh-tw/previous-versions/visualstudio/visual-studio-2013/ms235532(v=vs.120)
Registration-Free Activation of COM Components: A Walkthrough | Microsoft Learn
https://learn.microsoft.com/en-us/previous-versions/dotnet/articles/ms973913(v=msdn.10)