調用ocx ActiveX控件詳解(做一個簡單的ocx控件)

背景

最近做的項目都和插件有關,就是在頁面中調用插件的方法,然后進行操作。

插件就是ocx ActiveX控件,具體的說明可以自己去了解一下,在這里就不做贅述。

具體調用方式很簡單:

1、在頁面中寫一個object標簽,標簽中定義一個classid屬性,這個屬性是獲取到插件的關鍵

<object id="ocx" classid="clsid:c998ae90-5ffc-4a58-97d2-490a414bd6e5"></object>

2、直接在js中獲取到這個dom,然后就可以調用插件中的方法

let ocx = document.getElementById("ocx");
let sum = ocx.Add(1,2);

在使用過程中遇到了很多問題,于是自己去摸索了一下如何制作一個ocx插件,也能夠幫助自己加深一些使用方面的了解。

 

工具及下載準備

這里使用Visual Studio Community 2019,先下載必須的組件和依賴包。

使用C++的桌面開發Visual Studio擴展開發這兩項必須要勾選。

然后桌面開發中還需要勾選與MFC相關的庫,如果不勾選的話無法新建MFC項目

勾選完成后就開始下載相關的包,這個過程需要等待一會。

開始項目

下載完成后就可以開始新建一個項目,在這里選擇MFC ActiveX控件,命名為MyMFCActiveXControl

然后就會生成一系列控件名,頭文件等等的文件名,這里基本上不用做什么操作,直接完成就好。

唯一需要注意的是一個控件類型ID,這個ID我們后面會用到。

 

點擊完成后,就會生成一系列與插件相關的文件,這些文件里面包含了插件的類,頭文件,描述等信息。

因為不是專門學習C++的,不對里面的原理和結構進行探究,就取我需要的內容就行。

來找一找那個神奇的classid。

打開視圖中的解決方案資源管理器,找到源文件中以idl結尾的文件,這個文件包含了很多與插件相關的信息,例如版本,對外接口等等。

文件中有好幾個uuid都和我們用的classid長得很像,但是我們調用的classid是其中類信息下的uuid。

有興趣了解其他幾個uuid的信息可以參考這篇文章來了解:c++ ActiveX基礎1:使用VS2010創建MFC ActiveX工程項目

然后我們來添加一個方法測試一下這個插件。

在類視圖中找到以Lib結尾的Liberary,點開找到Control項右擊后點擊添加—>添加方法

在彈出的選項中添加一個最簡單的加法方法,點擊確定。

編譯器會幫我們在三個文件里面都做一些修改。

在.idl中定義了我們剛才添加的方法

在.h文件中聲明了這個方法

在同名的.cpp文件中對方法進行實現,我們將返回值改成p1 + p2;

生成項目,在生成的目錄下找到.ocx結尾的文件,這個就是我們生成的插件啦。

現在還沒有辦法使用這個插件,要在注冊表中進行注冊??梢杂靡韵聝煞N方式:

1、右擊ocx文件選擇打開方式,選擇C:\Windows\System32文件夾下的regsvr32.exe打開

2、直接運行regsvr32+ocx路徑

注冊成功后都有以下提示

然后就可以在頁面中編寫我們的代碼進行測試,

但是在調用時意外地報了找不到成員這個錯。

查閱資料發現是需要修改瀏覽器的安全設置。

點擊瀏覽器的設置,找到Internet選項—>安全,因為是在本地測試就選擇本地Internet。

選擇自定義級別—>對未標記為可安全的ActiveX控件初始化...—>點擊啟用

其實啟用這個選項不是特別安全,特別是讓用戶多了這一步操作,會增加使用難度,如何繞開這個安全模式可以參考下面的解決方式。

在Ctrl.h頭文件中添加如下代碼

// for IObjectSafety;不要忘了這個頭
#include <objsafe.h> 


   //////////////////////////////////////////////////////////////////////////
    // ActiveX控件安全初始化:實現ISafeObject接口
    //////////////////////////////////////////////////////////////////////////
    //ISafeObject  
    DECLARE_INTERFACE_MAP()
    BEGIN_INTERFACE_PART(ObjSafe, IObjectSafety)
        STDMETHOD_(HRESULT, GetInterfaceSafetyOptions) (
            /* [in] */ REFIID riid,
            /* [out] */ DWORD __RPC_FAR* pdwSupportedOptions,
            /* [out] */ DWORD __RPC_FAR* pdwEnabledOptions
            );

    STDMETHOD_(HRESULT, SetInterfaceSafetyOptions) (
        /* [in] */ REFIID riid,
        /* [in] */ DWORD dwOptionSetMask,
        /* [in] */ DWORD dwEnabledOptions
        );
    END_INTERFACE_PART(ObjSafe);
    //ISafeObject

在Ctrl.cpp中添加如下代碼,如果創建的項目名稱和我的不一樣,記得修改里面的類名

//////////////////////////////////////////////////////////////////////////
// ActiveX控件安全初始化:實現ISafeObject接口
//////////////////////////////////////////////////////////////////////////
// Interface map for IObjectSafety  
BEGIN_INTERFACE_MAP(CMyMFCActiveXControlCtrl, COleControl)
    INTERFACE_PART(CMyMFCActiveXControlCtrl, IID_IObjectSafety, ObjSafe)
END_INTERFACE_MAP()
// IObjectSafety member functions  
// Delegate AddRef, Release, QueryInterface  
ULONG FAR EXPORT CMyMFCActiveXControlCtrl::XObjSafe::AddRef()
{
    METHOD_PROLOGUE(CMyMFCActiveXControlCtrl, ObjSafe)
        return pThis->ExternalAddRef();
}
ULONG FAR EXPORT CMyMFCActiveXControlCtrl::XObjSafe::Release()
{
    METHOD_PROLOGUE(CMyMFCActiveXControlCtrl, ObjSafe)
        return pThis->ExternalRelease();
}
HRESULT FAR EXPORT CMyMFCActiveXControlCtrl::XObjSafe::QueryInterface(
    REFIID iid, void FAR* FAR* ppvObj)
{
    METHOD_PROLOGUE(CMyMFCActiveXControlCtrl, ObjSafe)
        return (HRESULT)pThis->ExternalQueryInterface(&iid, ppvObj);
}
const DWORD dwSupportedBits =
INTERFACESAFE_FOR_UNTRUSTED_CALLER |
INTERFACESAFE_FOR_UNTRUSTED_DATA;
const DWORD dwNotSupportedBits = ~dwSupportedBits;
//.............................................................................  
// CStopLiteCtrl::XObjSafe::GetInterfaceSafetyOptions  
// Allows container to query what interfaces are safe for what. We're  
// optimizing significantly by ignoring which interface the caller is  
// asking for.  
HRESULT STDMETHODCALLTYPE
CMyMFCActiveXControlCtrl::XObjSafe::GetInterfaceSafetyOptions(
    /* [in] */ REFIID riid,
    /* [out] */ DWORD __RPC_FAR* pdwSupportedOptions,
    /* [out] */ DWORD __RPC_FAR* pdwEnabledOptions)
{
    METHOD_PROLOGUE(CMyMFCActiveXControlCtrl, ObjSafe)
        HRESULT retval = ResultFromScode(S_OK);
    // does interface exist?  
    IUnknown FAR* punkInterface;
    retval = pThis->ExternalQueryInterface(&riid,
        (void**)&punkInterface);
    if (retval != E_NOINTERFACE) { // interface exists  
        punkInterface->Release(); // release it--just checking!  
    }

    // we support both kinds of safety and have always both set,  
    // regardless of interface  
    *pdwSupportedOptions = *pdwEnabledOptions = dwSupportedBits;
    return retval; // E_NOINTERFACE if QI failed  
}
/////////////////////////////////////////////////////////////////////////////  


// CStopLiteCtrl::XObjSafe::SetInterfaceSafetyOptions  
// Since we're always safe, this is a no-brainer--but we do check to make  
// sure the interface requested exists and that the options we're asked to  
// set exist and are set on (we don't support unsafe mode).  
HRESULT STDMETHODCALLTYPE
CMyMFCActiveXControlCtrl::XObjSafe::SetInterfaceSafetyOptions(
    /* [in] */ REFIID riid,
    /* [in] */ DWORD dwOptionSetMask,
    /* [in] */ DWORD dwEnabledOptions)
{
    METHOD_PROLOGUE(CMyMFCActiveXControlCtrl, ObjSafe)

        // does interface exist?  
        IUnknown FAR* punkInterface;
    pThis->ExternalQueryInterface(&riid, (void**)&punkInterface);
    if (punkInterface) { // interface exists  
        punkInterface->Release(); // release it--just checking!  
    }
    else { // interface doesn't exist  
        return ResultFromScode(E_NOINTERFACE);
    }
    // can't set bits we don't support  
    if (dwOptionSetMask & dwNotSupportedBits) {
        return ResultFromScode(E_FAIL);
    }

    // can't set bits we do support to zero  
    dwEnabledOptions &= dwSupportedBits;
    // (we already know there are no extra bits in mask )  
    if ((dwOptionSetMask & dwEnabledOptions) !=
        dwOptionSetMask) {
        return ResultFromScode(E_FAIL);
    }

    // don't need to change anything since we're always safe  
    return ResultFromScode(S_OK);
}
//////////////////////////////////////////////////////////////////////////

然后就可以在頁面上進行調用啦,調用成功返回了正確的值。

let ocx = document.getElementById("ocx");
let sum = ocx.Add(1, 2);
alert(sum);

拓展

剛才演示的都是正常情況下的調用,在實際使用時,最重要的場景是如何驗證電腦中是否安裝了我們需要使用的插件。

常見的辦法有兩種:

1、調用插件方法時使用異常處理,用try...catch來捕獲調用不到插件的情況

我們把插件卸載掉,運行regsvr /u 插件地址來卸載插件

會提示卸載成功

這個時候我們再來調用一下上面的Add方法。

try {
    let ocx = document.getElementById("ocx");
    let sum = ocx.Add(1, 2);
    alert(sum);
} catch (e) {
    alert(e);
}

會提示對象不支持Add屬性或方法,這樣好像就能判斷本機是否安裝了插件。

但是在版本迭代中,插件的方法肯定會越來越多,不止一個方法,那么這個方法還能幫助我們判斷嘛?

我們來嘗試注冊插件后,調用一個不存在Sub方法。

try {
    let ocx = document.getElementById("ocx");
    let sum = ocx.Sub(1, 2);
    alert(sum);
} catch (e) {
    alert(e);
}

也會得到同樣的結果,所以這個方法不是判斷本機是否安裝插件的最佳辦法。

這里我推薦第二種辦法

2、通過ActiveXObject來檢測是否安裝插件

var findOcx = function () {
    let control;
    try {
        //插件ProgID
        control = new ActiveXObject('MFCACTIVEXCONTRO.MyMFCActiveXControlCtrl.1');
    } catch (e) {
        console.log(e);
        return false;
    }
    return true;
};        

這個方法new了一個ActiveXObject對象,里面的參數就是剛才我們新建項目時標注的控件類型ID。

在.idl文件中也可以找到這個ID。

在注冊表中他以這樣的形式存在

如果未安裝插件,會提示Automation 服務器不能創建對象,這樣就把是否安裝插件和這個版本的插件是否有這個方法這兩個問題區分開來了。

如果插件存在,通過調試,也可以查看插件暴露出來的所有方法。

插件可以做很多事情,可以繞過瀏覽器的安全限制在本地讀寫文件,也可以繪制圖像,顯示視頻流等等。

但是插件的局限性也很大。

在實際使用中,版本的更迭提示、不同系統不同瀏覽器版本的安全模式等等問題處理起來更是讓人頭疼。

所以建議不到萬不得己最好不要使用插件。

posted @ 2020-04-29 13:46  Chellyyy  閱讀(...)  評論(...編輯  收藏
美人江湖手游