C# ActiveX开发及安装部署

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

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

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

  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项目,具体代码如下:

  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 就行。

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

  最后附上完整项目代码:http://pan.baidu.com/s/1hq8MzJ2

posted @ 2015-08-06 09:08  SpringX  阅读(2726)  评论(15编辑  收藏  举报