C#开发ActiveX插件

刚毕业来到公司主管安排个任务 就是封装一个第三方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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值