对于不懂C++和VB的我, 在工作上却遇到需要重写旧ActiveX控件的任务.

好在客户机都是Windows PC, 基本上都有.net framework 2.0, 勉强用C#实现可以满足需求

所以我参考了不少前辈在网上分享的示例,

在.net framework 2.0的基础上用C#实现ActiveX小控件.

一波三折, 好不容易测试成功, 所以在此做一次简单的操作整理.

 

1. 准备一个的ActiveX控件, 这里的示例仅实现最简单的功能, 只包含IE与ActiveX之间的传值功能.

新建一个窗体控件库:

 整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_数字签名

修改工程的属性

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_数字签名_02

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_html_03

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_javascript_04

注意一下的"生成"中的选择框, 此处根据是Debug和Release分开的, 切换模式时, 请检查此项是否已经勾上:

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_控件_05

修改AssemblyInfo, 添加以下代码:

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_控件_06

删除默认的UserControl1, 自己新建一个UserControl:

 整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_数字签名_07

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_javascript_08

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_javascript_09

新建一个接口:

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_html_10

代码如下:



using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices;  namespace ActiveXDemo {     [ComImport, GuidAttribute("B5030596-63D6-4B21-9D01-90698B1FC277")]     [InterfaceTypeAttribute(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             );     } }


 

让我们刚刚新建的UserControl实现上面的接口, GUID处请使用VS自带的Tools->Create GUID新建GUID:

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_控件_11

实现接口代码如下:


整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_数字签名_12整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_javascript_13


#region IObjectSafety 成员
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; } #endregion

View Code

 为了可以与前台的js进行交换, 添加以下dll引用和命名空间:

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_html_14

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_数字签名_15

添加一个方法, 用于调用前台js, 参数win为前台传入的window对象, func为前台js方法的名字, para为参数:

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_前台js_16

代码如下:



public void SetFunc(object win, string func, string para)  {        IHTMLWindow2 htmlWin = win as IHTMLWindow2;        if (htmlWin == null || string.IsNullOrEmpty(func))        {             MessageBox.Show("Assign error.");        }        else        {             string jsCode = string.Format("{0}('{1}')", func, para);             htmlWin.execScript(jsCode, "jscript");        } }


再添加一个方法, 用于接收前台传入的信息, 然后后台调用前台的方法显示该信息(没错, 这个功能是多此一举的, 仅为了展示前后台之间的传值)



public void ShowMessage(object win, string msg) {       SetFunc(win, "show", msg); }


 

2. 打包步骤1生成的dll, 使之成为msi安装文件

ActiveX控件部分已经完成了, 现在要把控件Build出来的dll打包成msi

新建setup工程:

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_javascript_17

添加步骤1的工程为输出:

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_javascript_18

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_控件_19

检查属性:

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_前台js_20

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_html_21

Rebuild步骤1工程, 然后Rebuild步骤2工程, 在工程目录下取出安装包(例子是Release中的msi):

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_数字签名_22

把这个msi放在新建一个空的文件夹, 方便进一步打包:

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_数字签名_23

3. 进一步打包客户端下载后可以自动安装的Cab包

 在上面的文件夹中放入以下文件:

 cabarc.exe是打包cab的工具, signcode.exe为数字签名工具

 build.bat文件控制cabarc打包操作, install.inf是IE安装cab的配置

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_javascript_24

 build.bat文件内容如下:

 整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_数字签名_25

第一个文件为需要生成的cab, 后面的为cab包含的文件:



@echo off "cabarc.exe" -s 6144 n ActiveXDemo.cab install.inf ActiveXSetup.msi pause


install.inf文件内容如下:

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_html_26

在此不具体解释, 可以参考文后的参考文章



[version]   signature="$CHICAGO$"   AdvancedINF=2.0

[Strings] Version="1.0.0.0"

[Setup Hooks] InstallerHook=InstallerHook [InstallerHook] run=msiexec.exe /i "%EXTRACT_DIR%\ActiveXSetup.msi" /qn


双击build.bat文件, 打包cab完成:

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_数字签名_27

4. 部署在一个简单网页上

 准备一个简单网站(静态html也行, 不过要配搭配IIS上运行):

 如图放入cab文件:

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_数字签名_28

写入如下html和js脚本:

注意object标签中的clsid, 它对应前面步骤1生成的GUID

codebase, 意为: ActiveX目录下的ActiveXDemo.cab文件, 版本是1,0,0,0, 与install.inf和程序集中版本对应

IE会根据这个版本号, 决定是否下载或更新cab包

sendMsg获得ActiveX对象, 发送window对象和参数给ActiveX控件,

ActiveX控件接收到后, 根据我们定义的逻辑, ActiveX将调用前台js方法show, 回显msg参数的值



<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<object id="objActiveX" classid="clsid:1D1999CC-0221-4A81-A0C5-F5ABA2059249" codebase="ActiveX/ActiveXDemo.cab#version=1,0,0,0">
</object>
<input id="msg" type="text" />
<input type="button" onclick="sendMsg()" value="提交" />
</div>
</form>
<script type="text/javascript">
function sendMsg() { var objActiveX = document.getElementById("objActiveX"); var msg = document.getElementById('msg').value; objActiveX.ShowMessage(window, msg); } function show(msg) { alert(msg); } </script>
</body>
</html>


 

5. 使用测试

 整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_html_29

测试成功

要注意的是, 要下载并运行未签名的控件, 请在Interest选项中

添加信任域名, 启用与ActiveX相关设置

 

6. 数字签名(这里我们使用自己建立的数字证书)

使用VS自带的命令行, 创建Demo证书:

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_html_30

输入如下命令, 工具会让你输入3次相同的密码:



makecert -ss name -n "CN=DemoName" -sv d:\certDemo.pvk d:\certDemo.cer


 

接着输入以下命令:



cert2spc d:\certDemo.cer d:\certDemo.spc


这样就一共生成了3个文件:

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_前台js_31

放到有signcode.exe的文件夹, 双击工具, 下一步, 选择需要签名的cab包:

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_javascript_32

下一步, 选自定义:

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_javascript_33

选择刚刚生成的certDemo.spc

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_数字签名_34

选择私钥文件certDemo.pvk

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_前台js_35

然后一路到底, 弹出需要你输入密码, 就输入证书的密码, 最后就完成签名了.

检查是否签名, 右键cab, 查看属性, 会多出一项:

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_javascript_36

有了证书, 客户机就可以安装证书, 双击certDemo.cer:

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_数字签名_37

整理:C#写ActiveX, 从代码到打包到签名到发布的示例(转)_数字签名_38

证书安装完成, 接着可以像步骤5一样测试.

 

注意: 由于证书不是官方购买的, IE认为还是不安全的, 可能会遇到IE拒绝安装的情况.

本人认为上线使用最好还是官方证书, 安全, 不需要手动安装.cer文件.

 

示例代码和用到的工具: ​​http://yunpan.cn/QX2mhxb2NFHXM​