如何用ACR122U阅读器+PCSC库实现VB.net自动读取Mifare Classic标签UID
解决方案:自动检测并读取Mifare Classic UID(.NET 8 WPF)
针对你的需求,我们可以利用PCSC-sharp的事件监听机制实现自动卡片检测,同时简化UID读取代码,避免UI冻结。以下提供C#和VB.NET两种实现方案:
核心改进点
- 用
SCardContext.StatusChanged事件替代轮询/递归,异步监听卡片状态变化 - 使用PCSC-sharp的异步API(
ConnectAsync/TransmitAsync)避免阻塞UI线程 - 简化APDU命令:使用ACR122U支持的标准UID读取命令
FF CA 00 00 00 - 加入重复UID过滤,避免同一卡片重复触发读取
- 正确释放资源,防止内存泄漏
C# 实现代码
MainWindow.xaml
<Window x:Class="WpfRfidReader.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="RFID UID Reader" Height="300" Width="400"> <Grid> <TextBlock x:Name="UidTextBlock" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="20" Text="等待卡片插入..."/> </Grid> </Window>
MainWindow.xaml.cs
using PCSC; using PCSC.Iso7816; using System.Windows; namespace WpfRfidReader { public partial class MainWindow : Window { private SCardContext _cardContext; private string _lastReadUid = string.Empty; public MainWindow() { InitializeComponent(); InitializeCardReader(); } private void InitializeCardReader() { _cardContext = new SCardContext(); _cardContext.Establish(SCardScope.System); // 注册卡片状态变化监听事件 _cardContext.StatusChanged += CardContext_StatusChanged; // 启动阅读器状态监控 var readers = _cardContext.GetReaders(); if (readers.Any()) { _cardContext.StartMonitoring(readers); } } private async void CardContext_StatusChanged(object? sender, StatusChangeEventArgs e) { foreach (var readerState in e.StateChanges) { // 检测卡片插入事件:当前阅读器存在卡片且ATR有效 if ((readerState.EventState & SCardState.Present) != 0 && readerState.Atr != null && readerState.Atr.Length > 0) { await ReadCardUidAsync(readerState.ReaderName); } // 检测卡片移除事件,重置UI显示 else if ((readerState.EventState & SCardState.Empty) != 0) { Dispatcher.Invoke(() => { UidTextBlock.Text = "等待卡片插入..."; _lastReadUid = string.Empty; }); } } } private async Task ReadCardUidAsync(string readerName) { try { using var reader = new SCardReader(_cardContext); // 异步连接阅读器 var connectResult = await reader.ConnectAsync(readerName, SCardShareMode.Shared, SCardProtocol.Any); if (connectResult != SCardError.Success) { return; } // 构造简化的UID读取APDU命令:FF CA 00 00 00 var apdu = new CommandApdu(IsoCase.Case2Short, reader.ActiveProtocol) { CLA = 0xFF, Instruction = InstructionCode.GetData, P1 = 0x00, P2 = 0x00, Le = 0x00 // 自动适配UID长度 }; // 异步发送APDU命令并获取响应 var transmitResult = await reader.TransmitAsync(apdu, out ResponseApdu response); if (transmitResult != SCardError.Success) { return; } if (response.HasData) { // 格式化UID字符串(去除分隔符) var uid = BitConverter.ToString(response.GetData()).Replace("-", ""); // 避免重复读取同一卡片 if (uid != _lastReadUid) { _lastReadUid = uid; // 回到UI线程更新控件 Dispatcher.Invoke(() => { UidTextBlock.Text = $"UID: {uid}"; // 此处添加UID获取后的业务逻辑 }); } } // 异步断开连接 await reader.DisconnectAsync(SCardReaderDisposition.Leave); } catch (Exception ex) { Dispatcher.Invoke(() => UidTextBlock.Text = $"读取错误: {ex.Message}"); } } protected override void OnClosed(EventArgs e) { base.OnClosed(e); // 释放资源 _cardContext?.CancelMonitoring(); _cardContext?.Release(); _cardContext?.Dispose(); } } }
VB.NET 实现代码
MainWindow.xaml
同C#版本的XAML代码
MainWindow.xaml.vb
Imports PCSC Imports PCSC.Iso7816 Imports System.Windows Class MainWindow Private _cardContext As SCardContext Private _lastReadUid As String = String.Empty Public Sub New() InitializeComponent() InitializeCardReader() End Sub Private Sub InitializeCardReader() _cardContext = New SCardContext() _cardContext.Establish(SCardScope.System) ' 注册卡片状态变化监听事件 AddHandler _cardContext.StatusChanged, AddressOf CardContext_StatusChanged ' 启动阅读器状态监控 Dim readers = _cardContext.GetReaders() If readers.Any() Then _cardContext.StartMonitoring(readers) End If End Sub Private Async Sub CardContext_StatusChanged(sender As Object, e As StatusChangeEventArgs) For Each readerState In e.StateChanges ' 检测卡片插入事件 If (readerState.EventState And SCardState.Present) <> 0 AndAlso readerState.Atr IsNot Nothing AndAlso readerState.Atr.Length > 0 Then Await ReadCardUidAsync(readerState.ReaderName) End If ' 检测卡片移除事件 If (readerState.EventState And SCardState.Empty) <> 0 Then Dispatcher.Invoke(Sub() UidTextBlock.Text = "等待卡片插入..." _lastReadUid = String.Empty End Sub) End If Next End Sub Private Async Function ReadCardUidAsync(readerName As String) As Task Try Using reader = New SCardReader(_cardContext) ' 异步连接阅读器 Dim connectResult = Await reader.ConnectAsync(readerName, SCardShareMode.Shared, SCardProtocol.Any) If connectResult <> SCardError.Success Then Return End If ' 构造UID读取APDU命令 Dim apdu = New CommandApdu(IsoCase.Case2Short, reader.ActiveProtocol) With { .CLA = &HFF, .Instruction = InstructionCode.GetData, .P1 = &H0, .P2 = &H0, .Le = &H0 } ' 异步发送命令 Dim response As ResponseApdu = Nothing Dim transmitResult = Await reader.TransmitAsync(apdu, response) If transmitResult <> SCardError.Success Then Return End If If response.HasData Then Dim uid = BitConverter.ToString(response.GetData()).Replace("-", "") If uid <> _lastReadUid Then _lastReadUid = uid Dispatcher.Invoke(Sub() UidTextBlock.Text = $"UID: {uid}" ' 添加业务逻辑 End Sub) End If End If Await reader.DisconnectAsync(SCardReaderDisposition.Leave) End Using Catch ex As Exception Dispatcher.Invoke(Sub() UidTextBlock.Text = $"读取错误: {ex.Message}") End Try End Function Protected Overrides Sub OnClosed(e As EventArgs) MyBase.OnClosed(e) ' 释放资源 _cardContext?.CancelMonitoring() _cardContext?.Release() _cardContext?.Dispose() End Sub End Class
关键说明
- 避免UI冻结:通过事件驱动+异步API,所有IO操作在后台线程执行,UI线程保持响应
- 自动检测卡片:利用
SCardContext.StatusChanged事件实时监听阅读器状态变化,无需手动触发 - 简化APDU命令:
FF CA 00 00 00是ACR122U读取Mifare UID的标准命令,比原代码更简洁高效 - 资源管理:窗口关闭时释放
SCardContext资源,防止内存泄漏 - UI线程安全:使用
Dispatcher.Invoke在UI线程更新控件,避免跨线程操作异常
内容的提问来源于stack exchange,提问作者Jake Wynn
相关产品推荐
相关产品推荐

