最近项目中,因为需要在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 }