转载

C# ActiveX开发及安装部署

最近项目中,因为需要在WEB页面上操作串口,包括串口查询、打开、发送指令、接收数据、关闭串口等功能。如下所示:

C# ActiveX开发及安装部署

考虑使用ActiveX来实现。因为以前没有这方面的经验,开发过程中也是遇到各种问题。废话不多说,下面进入正题:

1:打开VS2008,新建项目,以下是具体代码:

C# ActiveX开发及安装部署
  1 using System;   2 using System.Collections.Generic;   3 using System.Windows.Forms;   4 using System.IO.Ports;   5 using System.IO;   6 using System.Reflection;   7 using System.Runtime.InteropServices;   8 using mshtml;   9 using System.Text;  10 using Microsoft.Win32;  11 using System.Threading;  12   13 namespace WebSerial  14 {  15     [ProgId("WebSerial")]//控件名称  16     [ClassInterface(ClassInterfaceType.AutoDual), ComSourceInterfaces(typeof(ControlEvents))]  17     [Guid("6C6A0DE4-193A-48f5-BA91-3C180558785B")]//控件的GUID,用于COM注册和HTML中Object对象classid引用  18     [ComVisible(true)]  19     public partial class SerialPortControl : UserControl,IObjectSafety  20     {  21         ExSerialPort serialPort;  22         List<byte> buffer = new List<byte>(4096);  23   24         /// <summary>  25         /// 是否准备关闭串口  26         /// </summary>  27         private static bool m_IsTryToClosePort = false;  28         /// <summary>  29         /// 是否正在接收数据  30         /// </summary>  31         private static bool m_IsReceiving = false;  32         public SerialPortControl()  33         {  34   35         }  36   37         /// <summary>  38         /// 获取本地串口列表,以逗号隔开  39         /// </summary>  40         /// <returns></returns>  41         public string getComPorts()  42         {  43             string ports = "";  44             foreach (string s in ExSerialPort.GetPortNames())  45             {  46                 ports += "," + s;  47             }  48             return ports.Length > 0 ? ports.Substring(1) : "";  49         }  50   51         /// <summary>  52         /// 以指定串口号和波特率连接串口  53         /// </summary>  54         /// <param name="com">端口号</param>  55         /// <param name="baudRate">波特率</param>  56         /// <returns></returns>  57         [ComVisible(true)]  58         public string connect(string com, int baudRate)  59         {  60             close();  61             serialPort = null;  62             serialPort = new ExSerialPort(com);  63             serialPort.BaudRate = baudRate;  64             serialPort.Parity = Parity.None;  65             serialPort.DataBits = 8;  66             serialPort.Encoding = Encoding.ASCII;  67             serialPort.ReceivedBytesThreshold = 5;  68             serialPort.ReadBufferSize = 102400;  69   70             try  71             {  72                 serialPort.Open();  73                 if (serialPort.IsOpen)  74                 {  75                     m_IsTryToClosePort = false;  76                     this.clear();  77                     serialPort.DataReceived += new SerialDataReceivedEventHandler(serialPort_DataReceived);  78                     return "true";  79                 }  80             }  81             catch { }  82   83             return "false";  84         }  85   86         /// <summary>  87         /// 清理串口数据并关闭串口  88         /// </summary>  89         [ComVisible(true)]  90         public void close()  91         {  92             m_IsTryToClosePort = true;  93             while (m_IsReceiving)  94             {  95                 Application.DoEvents();  96             }  97   98             if (serialPort != null)  99             { 100                 serialPort.Dispose(); 101             } 102         } 103  104         /// <summary> 105         /// 清理串口数据 106         /// </summary> 107         [ComVisible(true)] 108         public void clear() 109         { 110             if (serialPort != null && serialPort.IsOpen) 111             { 112                 serialPort.Clear(); 113             } 114         } 115  116         /// <summary> 117         /// 发送字符串 118         /// </summary> 119         /// <param name="s"></param> 120         [ComVisible(true)] 121         public void writeString(string hexString) 122         { 123             if (serialPort != null && serialPort.IsOpen) 124             { 125                 byte[] bytes = strToToHexByte(hexString); 126                 serialPort.Write(bytes, 0, bytes.Length); 127             } 128         } 129  130         /// <summary> 131         /// 字符串转16进制字节数组 132         /// </summary> 133         /// <param name="hexString"></param> 134         /// <returns></returns> 135         private static byte[] strToToHexByte(string hexString) 136         { 137             hexString = hexString.Replace(" ", ""); 138             if ((hexString.Length % 2) != 0) 139                 hexString += " "; 140             byte[] returnBytes = new byte[hexString.Length / 2]; 141             for (int i = 0; i < returnBytes.Length; i++) 142                 returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16); 143             return returnBytes; 144         } 145  146         ///  <summary>   147         /// 字节数组转字符串16进制   148         ///  </summary>   149         ///  <param name="InBytes"> 二进制字节 </param>   150         ///  <returns>类似"01 02 0F" </returns>   151         public static string ByteToString(byte[] InBytes) 152         { 153             string StringOut = ""; 154             foreach (byte InByte in InBytes) 155             { 156                 //StringOut += String.Format("{0:X2}", InByte) + " ";   157                 StringOut += " " + InByte.ToString("X").PadLeft(2, '0'); 158             } 159  160             return StringOut.Trim(); 161         } 162  163         /// <summary> 164         /// 接收数据 165         /// </summary> 166         /// <param name="sender"></param> 167         /// <param name="e"></param> 168         void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) 169         { 170             if (m_IsTryToClosePort) 171             { 172                 return; 173             } 174  175             m_IsReceiving = true; 176  177             try 178             { 179                 int n = serialPort.BytesToRead; 180                 if (n > 0) 181                 { 182                     byte[] buf = new byte[n]; 183                     serialPort.Read(buf, 0, n); 184                     string dataString = ByteToString(buf); 185                     Receive(dataString); 186                 } 187  188                 ////1.缓存数据 189                 //buffer.AddRange(buf); 190                 ////2.完整性判断 191                 //while (buffer.Count >= 5) //至少包含帧头(2字节)、长度(1字节)、校验位(1字节);根据设计不同而不同 192                 //{ 193                 //    //2.1 查找数据头 194                 //    if (buffer[0] == 0xff && buffer[1] == 0x55) //传输数据有帧头,用于判断 195                 //    { 196                 //        int len = buffer[2]; 197                 //        if (buffer.Count < len + 4) //数据区尚未接收完整 198                 //        { 199                 //            break; 200                 //        } 201                 //        //得到完整的数据,复制到ReceiveBytes中进行校验 202                 //        byte[] ReceiveBytes = new byte[len + 4]; 203                 //        buffer.CopyTo(0, ReceiveBytes, 0, len + 4); 204  205                 //        byte checkByte = ReceiveBytes[len + 3];//获取校验字节 206                 //        byte realCheckByte = 0x00; 207                 //        realCheckByte -= buffer[2]; 208                 //        for (int packIndex = 0; packIndex < len; packIndex++)//将后面的数据加起来 209                 //        { 210                 //            realCheckByte -= ReceiveBytes[packIndex + 3]; 211                 //        } 212  213                 //        if (checkByte == realCheckByte)//验证,看数据是否合格 214                 //        { 215                 //            string dataString = ByteToString(ReceiveBytes);                             216                 //            Receive(dataString); 217                 //        } 218                 //        buffer.RemoveRange(0, len + 4); 219                 //    } 220                 //    else //帧头不正确时,清除 221                 //    { 222                 //        buffer.RemoveAt(0); 223                 //    } 224  225                 //} 226             } 227             finally 228             { 229                 m_IsReceiving = false; 230             } 231         } 232  233         public event ControlEventHandler OnReceive; 234         [ComVisible(true)] 235         private void Receive(string dataString) 236         { 237             if (OnReceive != null) 238             { 239                 OnReceive(dataString); //Calling event that will be catched in JS 240             } 241         } 242  243         ///    <summary> 244         ///    Register the class as a    control    and    set    it's CodeBase entry 245         ///    </summary> 246         ///    <param name="key">The registry key of the control</param> 247         [ComRegisterFunction()] 248         public static void RegisterClass(string key) 249         { 250             // Strip off HKEY_CLASSES_ROOT/ from the passed key as I don't need it 251             StringBuilder sb = new StringBuilder(key); 252  253             sb.Replace(@"HKEY_CLASSES_ROOT/", ""); 254             // Open the CLSID/{guid} key for write access 255             RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(), true); 256  257             // And create    the    'Control' key -    this allows    it to show up in 258             // the ActiveX control container 259             RegistryKey ctrl = k.CreateSubKey("Control"); 260             ctrl.Close(); 261  262             // Next create the CodeBase entry    - needed if    not    string named and GACced. 263             RegistryKey inprocServer32 = k.OpenSubKey("InprocServer32", true); 264             inprocServer32.SetValue("CodeBase", Assembly.GetExecutingAssembly().CodeBase); 265             inprocServer32.Close(); 266             // Finally close the main    key 267             k.Close(); 268             MessageBox.Show("Registered"); 269         } 270  271         ///    <summary> 272         ///    Called to unregister the control 273         ///    </summary> 274         ///    <param name="key">Tke registry key</param> 275         [ComUnregisterFunction()] 276         public static void UnregisterClass(string key) 277         { 278             StringBuilder sb = new StringBuilder(key); 279             sb.Replace(@"HKEY_CLASSES_ROOT/", ""); 280  281             // Open    HKCR/CLSID/{guid} for write    access 282             RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(), true); 283  284             // Delete the 'Control'    key, but don't throw an    exception if it    does not exist 285             k.DeleteSubKey("Control", false); 286  287             // Next    open up    InprocServer32 288             //RegistryKey    inprocServer32 =  289             k.OpenSubKey("InprocServer32", true); 290  291             // And delete the CodeBase key,    again not throwing if missing 292             k.DeleteSubKey("CodeBase", false); 293  294             // Finally close the main key 295             k.Close(); 296             MessageBox.Show("UnRegistered"); 297         } 298  299         #region IObjectSafety 成员 300         public void GetInterfacceSafyOptions(int riid, out int pdwSupportedOptions, out int pdwEnabledOptions) 301         { 302             pdwSupportedOptions = 1; 303             pdwEnabledOptions = 2; 304         } 305         public void SetInterfaceSafetyOptions(int riid, int dwOptionsSetMask, int dwEnabledOptions) 306         { 307             throw new NotImplementedException(); 308         } 309         #endregion 310  311         #region IObjectSafety 成员 312  313         private const string _IID_IDispatch = "{00020400-0000-0000-C000-000000000046}"; 314         private const string _IID_IDispatchEx = "{a6ef9860-c720-11d0-9337-00a0c90dcaa9}"; 315         private const string _IID_IPersistStorage = "{0000010A-0000-0000-C000-000000000046}"; 316         private const string _IID_IPersistStream = "{00000109-0000-0000-C000-000000000046}"; 317         private const string _IID_IPersistPropertyBag = "{37D84F60-42CB-11CE-8135-00AA004BB851}"; 318  319         private const int INTERFACESAFE_FOR_UNTRUSTED_CALLER = 0x00000001; 320         private const int INTERFACESAFE_FOR_UNTRUSTED_DATA = 0x00000002; 321         private const int S_OK = 0; 322         private const int E_FAIL = unchecked((int)0x80004005); 323         private const int E_NOINTERFACE = unchecked((int)0x80004002); 324  325         private bool _fSafeForScripting = true; 326         private bool _fSafeForInitializing = true; 327  328  329         public int GetInterfaceSafetyOptions(ref Guid riid, ref int pdwSupportedOptions, ref int pdwEnabledOptions) 330         { 331             int Rslt = E_FAIL; 332  333             string strGUID = riid.ToString("B"); 334             pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER | INTERFACESAFE_FOR_UNTRUSTED_DATA; 335             switch (strGUID) 336             { 337                 case _IID_IDispatch: 338                 case _IID_IDispatchEx: 339                     Rslt = S_OK; 340                     pdwEnabledOptions = 0; 341                     if (_fSafeForScripting == true) 342                         pdwEnabledOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER; 343                     break; 344                 case _IID_IPersistStorage: 345                 case _IID_IPersistStream: 346                 case _IID_IPersistPropertyBag: 347                     Rslt = S_OK; 348                     pdwEnabledOptions = 0; 349                     if (_fSafeForInitializing == true) 350                         pdwEnabledOptions = INTERFACESAFE_FOR_UNTRUSTED_DATA; 351                     break; 352                 default: 353                     Rslt = E_NOINTERFACE; 354                     break; 355             } 356  357             return Rslt; 358         } 359  360         public int SetInterfaceSafetyOptions(ref Guid riid, int dwOptionSetMask, int dwEnabledOptions) 361         { 362             int Rslt = E_FAIL; 363  364             string strGUID = riid.ToString("B"); 365             switch (strGUID) 366             { 367                 case _IID_IDispatch: 368                 case _IID_IDispatchEx: 369                     if (((dwEnabledOptions & dwOptionSetMask) == INTERFACESAFE_FOR_UNTRUSTED_CALLER) && 370                          (_fSafeForScripting == true)) 371                         Rslt = S_OK; 372                     break; 373                 case _IID_IPersistStorage: 374                 case _IID_IPersistStream: 375                 case _IID_IPersistPropertyBag: 376                     if (((dwEnabledOptions & dwOptionSetMask) == INTERFACESAFE_FOR_UNTRUSTED_DATA) && 377                          (_fSafeForInitializing == true)) 378                         Rslt = S_OK; 379                     break; 380                 default: 381                     Rslt = E_NOINTERFACE; 382                     break; 383             } 384  385             return Rslt; 386         } 387  388         #endregion 389     } 390  391     /// <summary> 392     /// Event handler for events that will be visible from JavaScript 393     /// </summary> 394     public delegate void ControlEventHandler(string dataString); 395  396     /// <summary> 397     /// This interface shows events to javascript 398     /// </summary> 399     [Guid("68BD4E0D-D7BC-4cf6-BEB7-CAB950161E79")] 400     [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] 401     public interface ControlEvents 402     { 403         //Add a DispIdAttribute to any members in the source interface to specify the COM DispId. 404         [DispId(0x60020001)] 405         void OnReceive(string dataString); //This method will be visible from JS 406     } 407  408     public class ExSerialPort : SerialPort 409     { 410         public ExSerialPort(string name) 411             : base(name) 412         { 413         } 414  415         public void Clear() 416         { 417             try 418             { 419                 if (this.IsOpen) 420                 { 421                     this.DiscardInBuffer(); 422                     this.DiscardOutBuffer(); 423                 } 424             } 425             catch { } 426         } 427  428         protected override void Dispose(bool disposing) 429         { 430             Clear(); 431  432             var stream = (Stream)typeof(SerialPort).GetField("internalSerialStream", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(this); 433  434             if (stream != null) 435             { 436                 stream.Dispose(); 437             } 438  439             base.Dispose(disposing); 440         } 441     } 442 }
View Code

2:新建安装项目,将上面项目DLL打包生成Msi安装文件。这里不多说,网上大把文章教你怎么做。

3:新建Web项目,具体代码如下:

C# ActiveX开发及安装部署
  1 <html>    2 <head>    3 <title>JavaScript串口测试</title>   4 <meta http-equiv="Content-Type" content="text/html; charset=GB2312" />   5 <script   language="javascript"   type="text/javascript">    6 <!--    7     function getComPorts()   8     {   9         var ports = webSerial.getComPorts();  10         var arr = ports.split(',');  11         var ctl = document.getElementById("ComName");   12         if(ctl)   13         {   14             ctl.options.length = 0;  15             for(var i=0;i<arr.length;i++)  16             {  17                 ctl.options[i] = new Option(arr[i],arr[i]);   18             }  19         }   20    }  21       22     function connectComPort()  23     {  24         var com = document.getElementById("ComName").value.toString();  25         var baudRate = document.getElementById("BaudRate").value;  26         var result = webSerial.connect(com,baudRate);  27         alert(result);  28     }  29       30     function clearComPort()  31     {  32         webSerial.clear();  33         alert("清理串口成功!");  34     }  35       36     function closeComPort()  37     {  38         webSerial.close();  39         alert("关闭串口成功!");  40     }  41       42     function writeString()  43     {  44         var hexString = document.getElementById("txtSend").value.toString();   45         webSerial.writeString(hexString);  46         alert("发送指令成功!");  47     }   48       49     function clearSend()  50     {  51         document.getElementById("txtSend").innerText="";  52     }  53       54     function clearReceive()  55     {  56         document.getElementById("txtReceive").innerText="";  57     }  58       59     function startTime()  60     {  61         var today=new Date()  62         var h=today.getHours()  63         var m=today.getMinutes()  64         var s=today.getSeconds()  65         // add a zero in front of numbers<10  66         m=checkTime(m)  67         s=checkTime(s)  68         document.getElementById('lblTime').innerHTML=h+":"+m+":"+s  69         t=setTimeout('startTime()',500)  70     }  71   72     function checkTime(i)  73     {  74         if (i<10)   75           {i="0" + i}  76           return i  77     }  78   79 -->   80 </script>     81   82 </head>   83 <body onload="startTime()" onunload="closeComPort()">  84 <form name="form1">       85 <fieldset style="width:225px;height:180px;text-align:center;">  86    <legend>串口</legend>  87    <div style="float:left;width:250px">     88        <br/>     89        <span>串口号:</span>  90        <select name="ComName" id="ComName" style="width:100px" >    91        </select>  92        <br/>     93        <span>波特率:</span>  94    <select name="BaudRate" id="BaudRate" style="width:100px" >  95    <option value="9600" selected="selected">9600</option>  96    <option value="57600"  >57600</option>  97    <option value="115200" >115200</option>     98    <option value="1382400" >1382400</option>    99    <option value="3000000" >3000000</option>   100    </select>    101    <br/> 102        <br/> 103        <input   type="button" id="btnGetPort" style="width:80px;height:30px;font-size:13px"   name="btnGetPort"   value="获取串口"   onclick="getComPorts()"/>        104        <input   type="button" id="btnOpenPort" style="width:80px;height:30px;font-size:13px"   name="btnOpenPort"   value="打开串口"   onclick="connectComPort()"/>      105        <input   type="button" id="btnClearPort" style="width:80px;height:30px;font-size:13px"   name="btnClearPort"   value="清理串口"   onclick="clearComPort()"/>          106        <input   type="button" id="btnClosePort" style="width:80px;height:30px;font-size:13px"   name="btnClosePort"   value="关闭串口"   onclick="closeComPort()"/>        107     </div>  108 </fieldset> 109 <br /><br /> 110 <fieldset style="width:800px;height:150px;text-align:center;"> 111    <legend>发送区域</legend> 112    <div align="left">V6-握手指令: FF 55 01 01 FE</div> 113    <div align="left">V6-50HZ采集:FF 55 05 03 20 4E 00 00 8A</div> 114    <div align="left">V6-停止指令: FF 55 01 10 EF</div> 115    <div style="float:left;"> 116        <textarea id="txtSend"  name="txtSend" style="width:800px;height:80px"></textarea>  117        <br/> 118        <input   type="button" id="btnSend" style="width:100px;height:30px"   name="btnSend"   value="发送"   onclick="writeString()"/>    119        <input  type="button" id="btnClearSend" style="width:100px;height:30px"   name="btnClearSend"   value="清空"   onclick="clearSend()"/> 120    </div>  121 </fieldset> 122 <br /><br /> 123 <fieldset style="width:800px;height:500px;text-align:center;"> 124    <legend>接收区域</legend>    125    <div style="float:left;"> 126    <textarea id="txtReceive"  readonly="readonly" name="txtReceive" style="width:800px;height:430px"></textarea>   127    <br/> 128    <input  type="button" id="btnClearReceive" style="width:100px;height:30px"   name="btnClearReceive"   value="清空"   onclick="clearReceive()"/> 129    </div> 130 </fieldset>    131 <span id="lblTime"></span> 132 </form>  133  134 <p> 135  136 <object classid="clsid:6C6A0DE4-193A-48f5-BA91-3C180558785B" codebase="../WebSerialSetup.msi" width="442" height="87" id="webSerial" name="webSerial"> 137         </object> 138 </p> 139  140  <!-- Attaching to an ActiveX event--> 141 <script language="javascript" type="text/javascript"> 142     function webSerial::OnReceive(dataString) 143     { 144         document.getElementById("txtReceive").value += dataString+"/r/n";;    145     } 146 </script> 147 </body>  148 </html>
View Code

在使用JS调用ActiveX的时候碰上问题一:方法可以成功调用,而事件却调用失败。网上文章大都是说JS如何调ActiveX,而ActiveX这边的方法或者事件需要满足什么条件才能被JS成功调用却少有涉及。正当我山穷水尽疑无路的时候,事情有了转折,无意中看到一篇老外写的文章。链接地址是:http://www.codeproject.com/Articles/24089/Create-ActiveX-in-NET-Step-by-Step。才知道事件需要实现一个接口才能被JS识别。所以这部分代码后面被加上去了:

/// <summary>     /// Event handler for events that will be visible from JavaScript     /// </summary>     public delegate void ControlEventHandler(string dataString);      /// <summary>     /// This interface shows events to javascript     /// </summary>     [Guid("68BD4E0D-D7BC-4cf6-BEB7-CAB950161E79")]     [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]     public interface ControlEvents     {         //Add a DispIdAttribute to any members in the source interface to specify the COM DispId.         [DispId(0x60020001)]         void OnReceive(string dataString); //This method will be visible from JS     }

控件类名前面也加上这个

[ClassInterface(ClassInterfaceType.AutoDual), ComSourceInterfaces(typeof(ControlEvents))]

事件定义如下:

public event ControlEventHandler OnReceive;  [ComVisible(true)]  private void Receive(string dataString)  {      if (OnReceive != null)      {   OnReceive(dataString); //Calling event that will be catched in JS      }  } 

本地打开网页,执行全部功能,操作正常,大功告成!!!

随后我把网页在本地发布并使用局域网中其他电脑(操作系统WIN7)IE访问该网页。那么问题来了:

1:机器弹出对话框拒绝安装当前ActiveX。经过对IE internet选项设置,放开对无签名ActiveX访问限制后。有的机器能弹出安装对话框,有的机器仍然坚决禁止。而且就算是弹出安装对话框,在确认安装后,对话框消失,插件也没装上。。。

好吧,这个问题也是没搞明白啥原因。后面时间紧迫,只好给客户一个下载链接,自己去点击下载。

2:下载安装包并安装完毕后,在客户机器上操作网页功能。前面几个按钮功能都OK,但是在填入指令点击发送,网页出现崩溃重新刷新的情况,而且换了几台机器都是这样。后面想起在生成安装包的时候,有弹出一个对话框,提示Visual Studio registry capture utility 已停止工作。百度一番后,找到解决方法:在Microsoft Visual Studio 9.0/Common7/Tools/Deployment 路径下面的regcap.exe文件,点击右键在属性页面中,选择兼容性页面,选中“以兼容模式运行”框就好了。兼容win7 就行。

相应设置后,重新生成安装文件。。。在客户机上安装后,一切正常!!!

正文到此结束
Loading...