vs2019 Com元件初探-通過IDispatch介面呼叫Com

一隻萌萌的魚發表於2020-12-02

vs2019 Com元件初探-簡單的COM編寫以及實現跨語言呼叫

上一篇實現瞭如何編寫基於IDipatch介面的COM以及vbs如何呼叫編寫的COM

本次主要是實現VBS的CreateObject函式的邏輯

 

前提條件

  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)

 

相關文章