vs2019 Com组件初探-实现vbs的CreateObject函数逻辑
vs2019 Com组件初探-简单的COM编写以及实现跨语言调用
上一篇实现了如何编写基于IDispatch接口的COM以及vbs如何调用编写的COM
本次主要是实现VBS的CreateObject函数的逻辑,也就是在不知道类名的情况下如何调用基于IDispathc接口的COM
前提条件
1、掌握C++基础语法
2、平台安装 vs2019
3、本地平台为 windows 10 1909 X64
4、基本的DLL编程知识 (不是必备)
本次目标
1、创建DLL并实现CreateObject函数
2、写一个调用DLL的demo
1、创建DLL并实现CreateObject函数
首先通过VS创建一个 动态链接库
在编写之前先梳理程序的执行流程
初始化 Com库
获取函数指针
传入参数
调用函数指针
卸载Com库
接下来就开始写我们的DLL
vs2019 创建DLL项目后系统会默认多出来头文件
以及源文件
我们打开pch.h头文件定义我们的函数声明
参数为 COM组件progID,函数名,参数数量,变长参数
extern "C" 以C的方式定义
_declspec(dllimport) 定义此函数为要导出的函数
新建一个ComInit.h 定义Com库的初始化和卸载库函数
1 // ComInit.h 2 3 #pragma once 4 static bool _init = false; 5 6 // 初始化 7 bool Init(); 8 9 // 结束初始化10 void Release();
新建一个ComInit.cpp 实现Init和Release函数
// ComInit.cpp#include "pch.h"#include "ComInit.h"bool Init(){if (_init == true) {return _init; }else{if (S_OK == CoInitialize(NULL)) _init = true;else_init = false;return _init; }return false;}void Release(){if (true == _init) { CoUninitialize(); _init = false; }}
之后打开pch.cpp实现CreateObject函数
1 #include "pch.h" 2 #include "ComStart.h" 3 #include <assert.h> 4 #include <atlbase.h> 5 6 // 报错宏 7 #define ASSERT(s) if((s) == true) 8 9 // Com类名,函数名,传入的参数数量,变长参数10 VARIANT CreateObject(const WCHAR* __comname,const WCHAR* __funcname,int __count, ...)11 {12 /* Com注册到系统后使用 */13 14 // 是否成功初始化15 if (true == Init())16 {17 // ProgId值存放18 CLSID clsid;19 20 // 通过 ProgID 取得组件的 CLSID21 // CLSID 值存放在注册表 HKEY_CLASSES_ROOT [以__comname加.1为键值(MyCom.FirstClass.1)]22 HRESULT hr = ::CLSIDFromProgID(__comname, &clsid);23 24 ASSERT(S_OK != hr)25 assert(hr != S_OK);26 27 // 智能指针获取 IUnknow28 CComPtr<IUnknown>spUnk;29 30 /* 31 * CoCreateInstance32 * CLSIDFromProgId获取的值33 * 指向接口IUnknown的指针34 * 运行可执行代码的上下文[CLSCTX_ALL 为所有]35 * IID_IUnknown为返回类型36 * 用来接收指向Com对象接口地址的指针变量37 */38 // 获取IUnknow内容 39 hr = ::CoCreateInstance(clsid, NULL, CLSCTX_ALL, IID_IUnknown, (LPVOID*)&spUnk);40 41 ASSERT(S_OK != hr)42 assert(hr != S_OK);43 44 // 通过IUnknown智能指针,声明新的IDispatch智能指针45 CComDispatchDriver spDisp(spUnk);46 47 // 参数数组48 VARIANT* __args = new VARIANT[__count];49 50 // 变长参数变量51 va_list ap;52 53 // 定位到第一个函数变长参数54 va_start(ap, __count);55 56 // 循环获取变长参数,并转换为 VARIANT 类型放入 __args变量57 for (auto i = 0; i < __count; i++)58 __args[i] = va_arg(ap, VARIANT);59 60 // 结束变长参数61 va_end(ap);62 63 // Com函数返回值存放64 VARIANT __ret;65 66 // 执行Com函数67 68 /*69 * [InvokeN]70 * 函数名71 * 函数参数72 * 函数数量73 * 返回值存放处74 */75 hr = spDisp.InvokeN((LPCOLESTR)__funcname, __args, __count, &__ret);76 77 ASSERT(S_OK != hr)78 assert(hr != S_OK);79 80 // 内存回收81 delete[] __args;82 83 // 卸载 Com库84 Release();85 86 // 返回值87 return __ret;88 }89 90 assert(_init == false);91 }
完成后编译(CTRL+B)获取到新的dll和lib文件(x64)以及项目的pch.h头文件
2、写一个调用DLL的demo
vs2019 新建基于 控制台程序 的项目
移动dll和lib以及pch.h文件到新建项目目录下,并对pch.h文件添加代码
// pch.h#pragma once#include <combaseapi.h>// 新添加的代码#pragma comment(lib,"ComPack.lib")extern "C" _declspec(dllimport) VARIANT CreateObject(const WCHAR * __comname, const WCHAR * __funcname, int __count, ...);
找到main函数 写入调用代码
#include <iostream>#include "pch.h"int main(){// 参数类型必须为VARIANT VARIANT __param1;// 参数类型为 LONG__param1.vt = VT_I4;// 参数值为 2__param1.lVal = 2;// 获取ComTest.Temp并调用Number 函数 参数数量为1 对Number函数传入参数__param1VARIANT __ret = CreateObject(L"ComTest.Temp", L"Number",1, __param1); std::cout << __ret.lVal << std::endl;}
执行并运行显示执行结果
运行出现错误,检查调用的Com是否已经注册
如何注册我在上一篇里面有讲过
接下来修改代码尝试调用Wscript.shell里面的Run函数
#include <iostream>#include "pch.h"int main(){ VARIANT __param1;// 参数类型为BSTR__param1.vt = VT_BSTR;// 创建BSTR格式的字符串__param1.bstrVal = SysAllocString(L"notepad.exe");// 调用函数并释放BSTRVARIANT __ret = CreateObject(L"Wscript.shell", L"run",1, __param1); SysFreeString(__param1.bstrVal);}
值得一提的是 COM组件的字符串和以往的字符串有所不同,创建方式和销毁方式也不同
SysAllocString为创建BSTR字符串
SysFreeString 为释放BSTR字符串
运行结果可以看到已经成功的执行了系统命令,打开了一个记事本
注意事项:
com基于IDispatch 接口才可以调用
Com必须已经注册到系统 (小心误删或者移动路径)
卸载DLL函数为 regsvr32.exe -ui [DLL未知]
DLL对应版本尽量一致
github源码: