---
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)