本帖最后由 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组件, 当我们在Excel的VBA中使用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++写了一个辅助DLL叫COMHelper.DLL,并导出了一个CreatObjectEx的函数,代码如下: - extern "C" HMODULE __stdcall CreateObjectEx(
- LPCSTR lpDllPath, LPCSTR lpClsid, _Out_ LPDISPATCH* ppDisp)
- {
- typedef int (CALLBACK *MYPROC)(REFCLSID rClsID, REFIID rIID, _Out_ LPVOID * ppv);
- auto hLib = LoadLibraryA(lpDllPath);
- if (!hLib)
- {
- return 0;
- }
- CLSID clsid;
- auto hr = CLSIDFromString((LPOLESTR)lpClsid, &clsid);
- if (FAILED(hr))
- {
- FreeLibrary(hLib);
- return 0;
- }
- auto GetClassObjProc = reinterpret_cast<MYPROC>(GetProcAddress(hLib, "DllGetClassObject"));
- if (!GetClassObjProc)
- {
- FreeLibrary(hLib);
- return 0;
- }
- IClassFactory* pClassFactory = nullptr;
- hr = GetClassObjProc(clsid, IID_IClassFactory, (LPVOID*)&pClassFactory);
- if (FAILED(hr) || pClassFactory == nullptr)
- {
- FreeLibrary(hLib);
- return 0;
- }
- hr = pClassFactory->CreateInstance(nullptr, IID_IDispatch, (LPVOID*)ppDisp);
- if (FAILED(hr) || ppDisp == nullptr)
- {
- FreeLibrary(hLib);
- pClassFactory->Release();
- return 0;
- }
- pClassFactory->Release();
- return hLib;
- }
复制代码假如你有一个未注册的COM组件DLL叫RegFreeCom.DLL,把它和COMHelper.DLL放到代码所在工作簿同一个目录下. 在vba中这样使用: - Private Declare Function CreateObjectEx Lib "COMHelper.DLL" ( _
- ByVal szDllPath As String, ByVal pClsid As Long, ByRef pObject As Any) As Long
- Sub test()
- ChDrive (ThisWorkbook.Path)
- ChDir (ThisWorkbook.Path)
- Dim obj As Object,hMod as Long
- Dim sClsid As String
- sClsid = "{A037DB7D-AB51-4613-A56E-779CAE50FB28}"
- hMod = CreateObjectEx(ThisWorkbook.Path & "\RegFreeCom.DLL", StrPtr(sClsid), obj)
- MsgBox obj.Sum(3, 5)
- End Sub
复制代码其中第二个参数是COM对象所属类的CLSID,可以用oleView这个软件来获取.
补充内容 (2018-1-21 14:20):
NOTE:第一种方法需要引用TypeLib(对象类型库),否则不能创建对象 |