C#开发ActiveX插件

来源:互联网 发布:淘宝折800报名要求 编辑:程序博客网 时间:2024/06/05 16:28

刚毕业来到公司主管安排个任务 就是封装一个第三方OCX包,开发个网页插件提供内部工作人员使用。经过了解OCX就是ActiveX插件,但试过网上的方法用都报错“对象没有此属性或方法”(已regsvr32注册),查看过方法发现没有构造函数,不知道是不是因为用VB编写的原因。无奈之下想到封装为WinForm下的dll再封装为ActiveX,最后委屈求全实现了需要的功能,现在ActiveX基本是被抛弃了吧?网上能找到的教程都是N年前的,而且关于C#都特别少,所以借鉴了很多博文,特此记录下来。

好像话太多了,直接上步骤:

运行环境:Windows8.1 + VS2013

1、用 AxImp.exe 将OCX转换为WinForm可承载的控件

aximp c:\systemroot\system32***.ocx /source

aximp 在路径 C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools
/source 生成 .cs文件
之后在目录下生成 .dll 、Ax.dll 、 *.cs三个文件。

2、用于测试,注册OCX包:

regsvr32 c:\windows\SysWOW64***.ocx

虽然主要是引用第一步生成的dll,但必须注册OCX包。

3、将ActiveX添加到WinForm可承载窗体,也就是继承UserControl的类。且必须BeginInit、EndInit:

UserControl ctl = new UserControl();AxyourActiveX activeX = new AxyourActiveX();activeX.BeginInit();ctl.Controls.Add(cti);activeX.EndInit();

这里有个小插曲,我原本这样写

public class CustomActiveX : UserControl{    public CustomActiveX()    {        AxyourActiveX activeX = new AxyourActiveX();        activeX.BeginInit();        this.Controls.Add(cti);        activeX.EndInit();    }}

理论上好像没问题,但跑起来报错(COMException),可能是当前的类运行在web上面的时候本身已经变成COM组件,所以出错,一定要注意。

4、实现IObjectSafety,声明为安全ActiveX,声明后IE会提示用户是否运行,而不是拦截。

接口:    [ComImport, Guid("CB5BDC81-93C1-11CF-8F20-00805F2CD064")]    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]    public interface IObjectSafety    {        [PreserveSig]        int GetInterfaceSafetyOptions(ref Guid riid, [MarshalAs(UnmanagedType.U4)] ref int pdwSupportedOptions, [MarshalAs(UnmanagedType.U4)] ref int pdwEnabledOptions);        [PreserveSig()]        int SetInterfaceSafetyOptions(ref Guid riid, [MarshalAs(UnmanagedType.U4)] int dwOptionSetMask, [MarshalAs(UnmanagedType.U4)] int dwEnabledOptions);    }
实现:private const string _IID_IDispatch = "{00020400-0000-0000-C000-000000000046}";        private const string _IID_IDispatchEx = "{a6ef9860-c720-11d0-9337-00a0c90dcaa9}";        private const string _IID_IPersistStorage = "{0000010A-0000-0000-C000-000000000046}";        private const string _IID_IPersistStream = "{00000109-0000-0000-C000-000000000046}";        private const string _IID_IPersistPropertyBag = "{37D84F60-42CB-11CE-8135-00AA004BB851}";        private const int INTERFACESAFE_FOR_UNTRUSTED_CALLER = 0x00000001;        private const int INTERFACESAFE_FOR_UNTRUSTED_DATA = 0x00000002;        private const int S_OK = 0;        private const int E_FAIL = unchecked((int)0x80004005);        private const int E_NOINTERFACE = unchecked((int)0x80004002);        private bool _fSafeForScripting = true;        private bool _fSafeForInitializing = true;        public int GetInterfaceSafetyOptions(ref Guid riid, ref int pdwSupportedOptions, ref int pdwEnabledOptions)        {            int Rslt = E_FAIL;            string strGUID = riid.ToString("B");            pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER | INTERFACESAFE_FOR_UNTRUSTED_DATA;            switch (strGUID)            {                case _IID_IDispatch:                case _IID_IDispatchEx:                    Rslt = S_OK;                    pdwEnabledOptions = 0;                    if (_fSafeForScripting == true)                        pdwEnabledOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER;                    break;                case _IID_IPersistStorage:                case _IID_IPersistStream:                case _IID_IPersistPropertyBag:                    Rslt = S_OK;                    pdwEnabledOptions = 0;                    if (_fSafeForInitializing == true)                        pdwEnabledOptions = INTERFACESAFE_FOR_UNTRUSTED_DATA;                    break;                default:                    Rslt = E_NOINTERFACE;                    break;            }            return Rslt;        }        public int SetInterfaceSafetyOptions(ref Guid riid, int dwOptionSetMask, int dwEnabledOptions)        {            int Rslt = E_FAIL;            string strGUID = riid.ToString("B");            switch (strGUID)            {                case _IID_IDispatch:                case _IID_IDispatchEx:                    if (((dwEnabledOptions & dwOptionSetMask) == INTERFACESAFE_FOR_UNTRUSTED_CALLER) &&                             (_fSafeForScripting == true))                        Rslt = S_OK;                    break;                case _IID_IPersistStorage:                case _IID_IPersistStream:                case _IID_IPersistPropertyBag:                    if (((dwEnabledOptions & dwOptionSetMask) == INTERFACESAFE_FOR_UNTRUSTED_DATA) &&                             (_fSafeForInitializing == true))                        Rslt = S_OK;                    break;                default:                    Rslt = E_NOINTERFACE;                    break;            }            return Rslt;        }

一字不漏贴上就ok,具体作用暂不探究。

5、实现注册时注册表的操作:

[ComRegisterFunction()]        public static void RegisterClass(string key)        {            // Strip off HKEY_CLASSES_ROOT\ from the passed key as I don't need it             StringBuilder sb = new StringBuilder(key);            sb.Replace(@"HKEY_CLASSES_ROOT\", "");            // Open the CLSID\{guid} key for write access             RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(), true);            // And create the 'Control' key - this allows it to show up in             // the ActiveX control container             RegistryKey ctrl = k.CreateSubKey("Control");            ctrl.Close();            // Next create the CodeBase entry - needed if not string named and GACced.             RegistryKey inprocServer32 = k.OpenSubKey("InprocServer32", true);            inprocServer32.SetValue("CodeBase", Assembly.GetExecutingAssembly().CodeBase);            inprocServer32.Close();            // Finally close the main key             k.Close();        }        [ComUnregisterFunction()]        public static void UnregisterClass(string key)        {            StringBuilder sb = new StringBuilder(key);            sb.Replace(@"HKEY_CLASSES_ROOT\", "");            // Open HKCR\CLSID\{guid} for write access             RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(), true);            // Delete the 'Control' key, but don't throw an exception if it does not exist             k.DeleteSubKey("Control", false);            // Next open up InprocServer32             RegistryKey inprocServer32 = k.OpenSubKey("InprocServer32", true);            // And delete the CodeBase key, again not throwing if missing             k.DeleteSubKey("CodeBase", false);            // Finally close the main key             k.Close();        }

6、这样就差不多了,业务需要 ActiveX回调数据到JavaScript上,百度activex调用js得到的方法:

//获取html的window对象Type typeIOleObject = this.GetType().GetInterface("IOleObject", true);object oleClientSite = typeIOleObject.InvokeMember("GetClientSite",BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public, null, this, null);IOleClientSite oleClientSite2 = oleClientSite as IOleClientSite;IOleContainer pObj;oleClientSite2.GetContainer(out pObj);IHTMLDocument pDoc2 = (IHTMLDocument)pObj;HTMLWindow2Class htmlWin = (HTMLWindow2Class)pDoc2.Script;string jsCode = string.Format("{0}({1})", func, param);this._htmlWindows.execScript(jsCode, "JScript");

这里的IOleClientSite、IOleContainer需要自己生成接口:

[ComImport,     Guid("0000011B-0000-0000-C000-000000000046"),     InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]    public interface IOleContainer    {        void EnumObjects([In, MarshalAs(UnmanagedType.U4)] int grfFlags,          [Out, MarshalAs(UnmanagedType.LPArray)] object[] ppenum);        void ParseDisplayName([In, MarshalAs(UnmanagedType.Interface)] object pbc,          [In, MarshalAs(UnmanagedType.BStr)] string pszDisplayName,          [Out, MarshalAs(UnmanagedType.LPArray)] int[] pchEaten,          [Out, MarshalAs(UnmanagedType.LPArray)] object[] ppmkOut);        void LockContainer([In, MarshalAs(UnmanagedType.I4)] int fLock);    } [ComImport,    Guid("00000118-0000-0000-C000-000000000046"),    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]    public interface IOleClientSite    {        void SaveObject();        void GetMoniker(uint dwAssign, uint dwWhichMoniker, object ppmk);        void GetContainer(out IOleContainer ppContainer);        void ShowObject();        void OnShowWindow(bool fShow);        void RequestNewObjectLayout();    }

但奇怪的事情来了,我的ActiveX并没有继承和实现这两个接口,但上面的
Type typeIOleObject = this.GetType().GetInterface(“IOleObject”, true);
并没有出错,而是获取到对象了。

7、到最后设置当前项目属性
1、 -> 应用程序 -> 程序集信息 -> 勾上 使程序集COM可见。
2、 -> 生成 -> 勾上 为COM互操作注册 。



测试成果:
方法一、手动注册我们的activex:

regasm c:/windows/SysWOW64/CustomActiveX.dll

路径: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\regasm.exe
注意:c#编的ActiveX一定要用regasm,不是regsvr32。

方法二:制作安装包
使用InstallShield Limited Edition for Visual Studio 2013,参考http://www.cnblogs.com/flydoos/p/3430922.html
主要部分:

Installation Requirements - 设置我们需要的 .Net框架。
Application Files - 添加主输出、将项目涉及的程序集添加进来(包括ocx)、将COM组件设置属性为“Extract COM
information”(网上的教程都说是”Self-Register”,但我安装时会出问题。)
2.Specify Application Data > Redistributables - 将.Net框架打包到安装文件
6.Prepare for Release > Release CD_ROM-Setup.exe-InstallShield Prerquisites Location设为Extract From Setup.exe 、SingleImage也一样。(这里的CD_ROM会生成.msi和exe文件,打包为cab需要用,SingleImage只会生成一个setup.exe)

调用:

<object id="obj" classid="clsid:F590031C-04A1-4100-921D-728340D7A21D" width="550" height="450"></object>clsid 为我们ActiveX的GUID,可以用VS-工具-创建GUID,快速生成。


cab打包方法:
添加install.inf

[version]signature="$CHICAGO$"AdvancedINF=2.0[Setup Hooks]hook1=hook1[hook1]run=msiexec.exe /i "%EXTRACT_DIR%\ActiveXSetup.msi" /qn

build.bat

"cabarc.exe"  n test.cab ActiveXSetup.msi install.inf 

因为打包是用.msi文件,而我需要将.net框架打包到客户端,所以就直接使用setup.exe了,大概68M左右。到此已可以实现我的需求,如果想签名发布,可以google一下也很多例子。
我本来不是做web开发的,只是懂一点点c# 临时分配了任务,所以总结得不太好和很多知识点都没有深究,但希望能帮助大家。第一篇blog,以后继续努力。

0 0