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 取得組件的 CLSID 21 // 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 // 智能指針獲取 IUnknow 28 CComPtr<IUnknown>spUnk; 29 30 /* 31 * CoCreateInstance 32 * 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函數傳入參數__param1 VARIANT __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"); // 調用函數並釋放BSTR VARIANT __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源碼:
3065190005/ComTest: ComTest Code (github.com)