調用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結尾的文件,這個就是我們生成的插件啦。

現在還沒有辦法使用這個插件,要在註冊表中進行註冊。可以用以下兩種方式:

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 伺服器不能創建對象,這樣就把是否安裝插件和這個版本的插件是否有這個方法這兩個問題區分開來了。

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

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

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

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