调用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 服务器不能创建对象,这样就把是否安装插件和这个版本的插件是否有这个方法这两个问题区分开来了。
如果插件存在,通过调试,也可以查看插件暴露出来的所有方法。
插件可以做很多事情,可以绕过浏览器的安全限制在本地读写文件,也可以绘制图像,显示视频流等等。
但是插件的局限性也很大。
在实际使用中,版本的更迭提示、不同系统不同浏览器版本的安全模式等等问题处理起来更是让人头疼。
所以建议不到万不得己最好不要使用插件。