ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 Excel Home精品图文教程库
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
300集Office 2010微视频教程 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
查看: 4121|回复: 1

[原创] 介绍两种免注册使用COM组件的方法

[复制链接]

TA的精华主题

TA的得分主题

发表于 2018-1-17 16:07 | 显示全部楼层 |阅读模式
本帖最后由 etsure 于 2018-1-17 17:09 编辑

COM组件通常是一个DLL文件,里面包含着一个或者多个COM.
VB6,ATL框架等生成的COM组件都会暴露四个函数:
DLLRegisterServer,DLLUnregisterServer,DLLCanUnloadNow,DLLGetClassObject.
每一类对外暴露的COM类用一个GUID(CLSID)来唯一标识.COM对象是COM类的一个实例,COM对象对外暴露的只有COM接口,其也是用一个GUID(IID)来唯一标识自己.
COM接口是一组方法的组合,我们使用COM对象,实际就是调用这些接口的方法.
一般情况下,对于一个正确注册并只包含一个COM类的COM组件, 当我们在ExcelVBA中使用New或者CreateObject等方法时,COM库将根据注册表的内容找到组件DLL的路径,LoadLibrary把组件DLL装入Excel进程的内存空间,然后用GetProcAddress找到DLLGetClassObject函数并调用.
COM库调用DLLGetClassObject函数来得到一个专门负责生产这类COM对象的工厂对象(也是一个COM对象) 的接口(记住,COM对象对外永远只暴露接口),然后再调用接口的CreateInstance方法来得到COM对象的指针.
所以,当一个COM组件没有正确注册,也就是没有在注册表写入自身的一些信息时,是不能被我们使用的.
但是,从上面的流程可以看到,我们可以通过LoadLibrary载入组件DLL,并调用DLLGetClassObject.因为这些都是标准的DLL导出函数,我们在vba中可以通过Declare来完成.但因为vba中没有函数指针这个数据类型,手动实现的话比较麻烦,
这里我用C++写了一个辅助DLLCOMHelper.DLL,并导出了一个CreatObjectEx的函数,代码如下:
  1. extern "C" HMODULE __stdcall CreateObjectEx(
  2.         LPCSTR  lpDllPath, LPCSTR  lpClsid, _Out_ LPDISPATCH* ppDisp)
  3. {
  4.         typedef int (CALLBACK *MYPROC)(REFCLSID rClsID, REFIID rIID, _Out_ LPVOID * ppv);
  5.         auto hLib = LoadLibraryA(lpDllPath);
  6.         if (!hLib)
  7.         {
  8.                 return 0;
  9.         }
  10.         CLSID clsid;
  11.         auto hr = CLSIDFromString((LPOLESTR)lpClsid, &clsid);
  12.         if (FAILED(hr))
  13.         {
  14.                 FreeLibrary(hLib);
  15.                 return 0;
  16.         }
  17.         auto GetClassObjProc = reinterpret_cast<MYPROC>(GetProcAddress(hLib, "DllGetClassObject"));
  18.         if (!GetClassObjProc)
  19.         {
  20.                 FreeLibrary(hLib);
  21.                 return 0;
  22.         }
  23.         IClassFactory* pClassFactory = nullptr;
  24.         hr = GetClassObjProc(clsid, IID_IClassFactory, (LPVOID*)&pClassFactory);
  25.         if (FAILED(hr) || pClassFactory == nullptr)
  26.         {
  27.                 FreeLibrary(hLib);
  28.                 return 0;
  29.         }
  30.         hr = pClassFactory->CreateInstance(nullptr, IID_IDispatch, (LPVOID*)ppDisp);
  31.         if (FAILED(hr) || ppDisp == nullptr)
  32.         {
  33.                 FreeLibrary(hLib);
  34.                 pClassFactory->Release();
  35.                 return 0;
  36.         }
  37.         pClassFactory->Release();
  38.         return hLib;
  39. }
复制代码
假如你有一个未注册的COM组件DLLRegFreeCom.DLL,把它和COMHelper.DLL放到代码所在工作簿同一个目录下.
vba中这样使用:
  1. Private Declare Function CreateObjectEx Lib "COMHelper.DLL" ( _
  2.     ByVal szDllPath As String, ByVal pClsid As Long, ByRef pObject As Any) As Long

  3. Sub test()
  4.     ChDrive (ThisWorkbook.Path)
  5.     ChDir (ThisWorkbook.Path)
  6.     Dim obj As Object,hMod as Long
  7.     Dim sClsid As String
  8.     sClsid = "{A037DB7D-AB51-4613-A56E-779CAE50FB28}"
  9. hMod  = CreateObjectEx(ThisWorkbook.Path & "\RegFreeCom.DLL", StrPtr(sClsid), obj)
  10.     MsgBox obj.Sum(3, 5)
  11. End Sub
复制代码
其中第二个参数是COM对象所属类的CLSID,可以用oleView这个软件来获取.



补充内容 (2018-1-21 14:20):
NOTE:第一种方法需要引用TypeLib(对象类型库),否则不能创建对象

免注册调用Com.zip

76.38 KB, 下载次数: 108

TA的精华主题

TA的得分主题

 楼主| 发表于 2018-1-17 17:00 | 显示全部楼层
本帖最后由 etsure 于 2018-1-17 17:04 编辑

Ok,现在介绍第二种方法:Side-by-side技术(以下简称sxs)

Sxs在WinXP时候引入,目的是解决COM的DLL版本冲突问题.

sxs使用一个后缀为manifest的文件来作为配置文件,其本质是xml格式文件.

假如现在有个client.exe程序只能用2.0版本的RegFreeCom.DLL,如果其他程序注册了3.0版本的,那么client.exe运行时将崩溃.

sxs的解决办法是把RegFreeCom.DLL放到client.exe目录下,并分别为这两个创建两个配置文件: client.exe.manifest和RegFreeCom.X.manifest, client.exe.manifest里表明了client只使用RegFreeCom.X.manifest指定的RegFreeCom.DLL,这样就解决了版本冲突问题.

Sxs同时还引入两个新概念: Assembly和Activation Context.

Assembly借用了.NET的名词,但不完全相同,一个Assembly是逻辑上的DLL,其可以包含标准DLL, Windows classes, COM组件, 类型库等.

通常一个Assembly只对应一个DLL.

RegFreeCom.X.manifest里包含了COM类的关键信息,这也是COM库可以不借助注册表也能正确工作的原因.

  1. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  2. <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  3. <assemblyIdentity
  4. type="win32"
  5. name="RegFreeCom.X"
  6. version="1.0.0.0" processorArchitecture="x86"/>
  7. <file name = "RegFreeCom.dll">
  8. <comClass description="免注册COM" progid="RegFree.COM"
  9. clsid="{A037DB7D-AB51-4613-A56E-779CAE50FB28}"
  10. threadingModel = "Apartment"
  11. tlbid="{57A10BF8-E26E-4DAC-B8C4-39996475241D}"
  12. />
  13. <typelib tlbid="{57A10BF8-E26E-4DAC-B8C4-39996475241D}" version="1.0" helpdir=""/>
  14. </file>
  15. </assembly>
复制代码

上面是RegFreeCom.X.manifest的内容

  1. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  2. <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  3. <assemblyIdentity
  4. type = "win32"
  5. name = "client.asm"
  6. version = "1.0.0.0" processorArchitecture="x86"/>
  7. <dependency>
  8. <dependentAssembly>
  9. <assemblyIdentity
  10. type="win32"
  11. name="RegFreeCom.X"
  12. version="1.0.0.0" processorArchitecture="x86"/>
  13. </dependentAssembly>
  14. </dependency>

  15. </assembly>
复制代码

上面是client.exe.manifest的内容

Activation Context是assmebly在内存中的映像,其关系类似COM对象与COM类.

当client启动时,如果没有关联的manifest,则生成一个默认Activation Context,否则以关联manifest为蓝本新建一个Activation Context,

类似一个隔离的空间,不受系统其他因素的影响,这是上面提到的解决版本冲突问题的实质.

我们回到Excel,在Excel安装目录下,有一个Excel.exe.manifest,这就是微软为Excel提供的配置文件,为了让Excel可以免注册调用myCOMSevr.DLL,我们可以修改这个manifest文件使他依赖于myCOMSevr.X.manifest指定的Assembly,也就是myCOMSevr.DLL.但是有时候我们不想修改这个问题,或者想动态加载不同的manifest以调用不同的COM组件,这个时候需要用到Activation Context API,这个在vba中可以直接操作.下面是代码:
  1. Private Declare Function CreateActCtxW Lib "kernel32.dll" (ByRef pActCtx As ACTCTXW) As Long
  2. Private Declare Function ActivateActCtx Lib "kernel32.dll" (ByVal hActCtx As Long, ByRef lplpCookie As Long) As Long
  3. Private Declare Function DeactivateActCtx Lib "kernel32.dll" (ByVal dwFlags As Long, ByVal cookie As Long) As Long
  4. Private Type ACTCTXW
  5.    cbSize As Long
  6.    dwFlags As Long
  7.    lpcwstrSource As Long
  8.    wProcessorArchitecture As Integer
  9.    wLangId As Integer
  10.    lpcwstrAssemblyDirectory As Long
  11.    lpcwstrResourceName As Long
  12.    lpcwstrApplicationName As Long
  13.    hModule As Long
  14. End Type

  15. Public Sub Test()
  16.     Dim act As ACTCTXW
  17.     Dim hAct As Long, iCookie As Long
  18.    
  19.     act.cbSize = Len(act)
  20.     act.lpcwstrSource = StrPtr(ThisWorkbook.Path & "\client.exe.manifest")
  21.     '载入Activation Context
  22.     hAct = CreateActCtxW(act)
  23.     If hAct <> -1 And hAct <> 0 Then
  24.         If ActivateActCtx(hAct, iCookie) Then '激活Activation Context
  25.             Dim obj As Object
  26.             Set obj = CreateObject("RegFree.COM") '成功激活,我们尝试一下
  27.             DeactivateActCtx 0, iCookie '这个时候取消激活,看看obj是否受影响
  28.             MsgBox obj.Sum(3, 5) '对的!
  29.             Set obj = CreateObject("RegFree.COM") '成功激活,我们尝试一下
  30.         End If
  31.     End If
  32. End Sub
复制代码

评分

2

查看全部评分

您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

关闭

最新热点上一条 /1 下一条

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-4-25 04:48 , Processed in 0.042256 second(s), 12 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表