--- tags: C# --- # VCOM port slide: https://hackmd.io/@SquirrelPanda/SkNXgYBLP 因為一些技術問題, 無法直接使用 C# 所提供的 [**SerialPort**](https://docs.microsoft.com/zh-tw/dotnet/api/system.io.ports.serialport?view=dotnet-plat-ext-3.1), 因此需要使用原生 Windows API 的方式來收發資料. > Email account : s9926004@gmail.com > [name=Nick Xiao] [time=NOV, 2020] [color=#907bf7] {%hackmd ryr9Ug6sd %} --- ## 需要哪些 Windows API? 下面列出我有使用到的 Windows API, 詳細的說明還請參考 MSDN 上的說明 - CreateFile(): 用於建立代表該 VCOMPort 的 Handle, 只要有IO裝置的需求就都會需要建立該對應的 Handle. - ClearCommError(): 負責清掉目前 Windows 中所記錄通訊的相關狀態及記錄, 我們將透過這個API 取得當前 Queue 中的資料長度以及檢察目前狀態. - ReadFile(): 根據 ClearCommError() 所拿到的 cbInQue 長度, 將 Queue 中的資料讀出來. - WriteFile(): 將資料寫入IO裝置的方法 - GetLastError(): 取得 Windws API 最後一筆狀態 [(查詢)](https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-) - OutputDebugString(): 透過 [DebugView](https://docs.microsoft.com/en-us/sysinternals/downloads/debugview) 輸出字串, 方便 Debug ## 如何在 C# 使用 Windows API? ### 相關的命名空間: ```C# using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; ``` 根據MSDN的建議, 使用 SafeFileHandle ### 透過 DllImportAttribute 來引用 kernel32.dll 中的 Windows ```c# struct COMSTAT { public UInt32 fBitFields; //See Comment in Win32API.Txt; public UInt32 cbInQue; public UInt32 cbOutQue; } [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile); [DllImport("kernel32", SetLastError = true)] internal extern static int ReadFile(SafeFileHandle handle, byte[] bytes, int numBytesToRead, out int numBytesRead, IntPtr overlapped_MustBeZero); [DllImport("kernel32", SetLastError = true)] internal extern static int WriteFile(SafeFileHandle handle, byte[] bytes, int numBytesToWrite, out int numBytesWrite, IntPtr overlapped_MustBeZero); [DllImport("kernel32.dll", SetLastError = true)] static extern int ClearCommError(SafeFileHandle hFile, ref int lpErrors, ref COMSTAT lpStat); [DllImport("kernel32.dll", SetLastError = true)] static extern int GetLastError(); [DllImport("kernel32.dll", CharSet = CharSet.Auto)] public static extern void OutputDebugString(string message); ``` 注意, 部分 Structure 需要自行根據使用的.NET版本宣告 :::info MSDN所提供的資料比較零散, 部分宣告是在其他[範例](https://docs.microsoft.com/zh-tw/dotnet/api/system.runtime.interopservices.safehandle?view=netframework-4.5)中找到的 ::: ### 實作範例 #### Connect ```C# public bool Connect() { SafeFileHandle handle; IntPtr ptr = CreateFile("\\\\.\\" + comPortInfo.name, (GENERIC_READ | GENERIC_WRITE), 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero); handle = new SafeFileHandle(ptr, true); if (handle.IsInvalid) return false; // m_CurrentHandle is global variable m_CurrentHandle = handle; return true; } ``` :::warning 這邊要注意的有兩點: 1. 當COM number 大於 9 時, 在 CreateFile 需要加上 "\\\\\\.\\\\" [(Win32 Device Namespaces)](https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file) 1. 因為使用的解決方案不支援 Virtual COM port setup command, 因此跳過 Baud Rate 的部分, 即不呼叫設定 WinAPI SetCommState() ::: #### Receive ```C# public bool VComPortReceive(out byte[] packet) { int ErrorCode = 0; COMSTAT cs; cs.fBitFields = 0; cs.cbInQue = 0; cs.cbOutQue = 0; packet = null; if (m_CurrentHandle == null) return false; if (m_CurrentHandle.IsInvalid) return false; ClearCommError(m_CurrentHandle, ref ErrorCode, ref cs); if (ErrorCode != 0) { OutputDebugString("ErrorCode != 0, cs.fBitFields = "+cs.fBitFields.ToString()); return false; } int result; int GetReadByte; int ReadingByte = Convert.ToInt32(cs.cbInQue); packet = new byte[ReadingByte]; result = ReadFile(m_CurrentHandle, packet, ReadingByte, out GetReadByte, IntPtr.Zero); if (result == 0) { ErrorCode = GetLastError(); OutputDebugString("ReadFile fail, ErrorCode = " + ErrorCode.ToString()); m_CurrentHandle = null; return false; } return true; } ``` #### SendSerialPort ```C# private bool SendSerialPort(byte[] b) { int lrc = 0; int bufferSize = b.Length; int result; if (m_CurrentHandle == null) return false; if (m_CurrentHandle.IsInvalid) return false; Clear_RX_Queue(); result = WriteFile(m_CurrentHandle, b, bufferSize, out lrc, IntPtr.Zero); if (result == 0) { int ErrorCode = GetLastError(); OutputDebugString("WriteFile() Error Code : " + ErrorCode.ToString()); m_CurrentHandle = null; return false; } return true; } ``` #### Clear_RX_Queue 透過先讀一次的方式, 清掉 Windows Queue ```C# private void Clear_RX_Queue() { VComPortReceive(out packet); } ``` ## 參考資料: - Visual C#中調用Windows API的要點 - [連結](https://sites.google.com/site/willsnote/Home/visual-c%E4%B8%AD%E8%AA%BF%E7%94%A8windows-api%E7%9A%84%E8%A6%81%E9%BB%9E) - [C#] 直接調用Win32 API DLL - [連結](https://dotblogs.com.tw/yc421206/2008/11/18/6022) - Virtual Serial Port Tools Documentation - [連結](https://docs.hhdsoftware.com/vspt/vspt-api/overview.html)