# RFID Kart Okuyucudan Klavye/HID Üzerinden 4 Bayt UID Okuma

> Klavye gibi davranarak sekiz onaltılık karakter gönderen RFID okuyucudan .NET/WinForms ile 4 bayt UID okuma; Türkçe Q/F düzeni, durum makinesi, zaman aşımı, WndProc, klavye kancası ve Raw Input sınırları.

- Author: Muhammet Ali Köker
- Language: tr
- Canonical: https://alikoker.com.tr/rfid-klavye-hid-kart-okuyucu-dotnet
- Translation: https://alikoker.com.tr/en/reading-rfid-keyboard-wedge-hid-dotnet
- Published: 2021-07-26T17:57:00+03:00
- Modified: 2026-08-26T23:04:00+03:00
- Verified: 2026-08-26T23:04:00+03:00
- Type: article

2014'te kartlı geçiş ve otomasyon yazılımları üzerinde çalışırken kullandığım RFID okuyuculardan biri uygulamaya seri port ya da üretici SDK'sı üzerinden veri vermiyordu. Kartı kendi içinde okuyup dört baytlık değeri sekiz onaltılık karakter olarak Windows'a “yazıyordu”. Uygulama açısından cihaz, özel bir RFID protokolü konuşan çevre biriminden çok, oldukça hızlı yazan bir klavye gibi görünüyordu.

Bu ayrım çözümün yönünü tamamen değiştiriyor. İncelenen kod MIFARE kimlik doğrulamasını, ISO/IEC 14443 hava arayüzünü, kart belleğini veya ham USB HID raporlarını çözmüyor. RF tarafındaki işlem okuyucunun içinde bitmiş durumda; benim kodum Windows'a ulaşan `0-9/A-F` tuş dizisini sınırlandırılmış bir uygulama çerçevesine dönüştürüyor.

Katmanları bu şekilde ayırıyorum:

```text
RFID kart
   ↓
okuyucu
   ↓
klavye gibi davranan / HID-benzeri giriş
   ↓
Windows klavye iletisi
   ↓
8 onaltılık basamak
   ↓
4 bayt kart değeri
   ↓
otomasyon uygulaması
```

Sekiz onaltılık basamağı dört baytlık değere dönüştüren aynı çekirdeği Windows Forms'ta dört ayrı giriş sınırında denememin nedeni buydu: normal klavye olayı, `IMessageFilter`, doğrudan `WndProc` ve `WH_KEYBOARD_LL`. Bunlar aynı işi dört kez yazmak için değil, odak ve mesaj akışının gerçek uygulamada nerede kesildiğini görmek için kullanıldı. Bir kiosk formuyla çok pencereli bir otomasyon uygulamasının doğru giriş noktası aynı olmak zorunda değildir.

### Önce RFID ile HID kavramını ayırmak gerekir

RFID, kart ile okuyucu arasındaki radyo frekanslı tanımlama teknolojisidir. HID ise bilgisayar tarafındaki giriş aygıtı davranışını anlatır. Bir RFID okuyucu USB üzerinden klavye gibi davranabilir; bu durumda Windows açısından gelen şey çoğu kez "RFID paketi" değil klavye girdisidir.

Klavye gibi davranan okuyucu (`keyboard-wedge`) modelinin pratik cazibesi dağıtım kolaylığıdır: okuyucu Not Defteri'ne kart numarası yazabiliyorsa WinForms uygulaması da aynı Windows girişini görebilir. Bunun bedeli, insan klavyesi ile okuyucunun aynı giriş zincirine karışabilmesidir.

Ben bu yöntemi ancak cihaz biçiminin sabit, çerçevenin kısa ve uygulama bağlamının kontrollü olduğu durumda tercih ederim. “Bu sekiz tuş hangi fiziksel aygıttan geldi?” sorusunun güvenilir yanıtı gerekiyorsa sıradan klavye iletisi sınırı artık yeterli değildir; Raw Input gibi aygıt kimliği taşıyan bir katmana geçmek gerekir.

### Neden tam sekiz karakter?

Tarihsel okuyucu dört baytlık değeri onaltılık metin halinde iletiyordu. Bir bayt iki hex basamağı olduğundan:

```text
4 bayt × bayt başına 2 onaltılık basamak = 8 onaltılık basamak
```

Sekiz karakterin tamamı geldiğinde 32 bit değer tamamlanır.

Bu, RFID dünyasının genel kuralı değildir. Kart UID'leri ve okuyucu çıktıları farklı uzunluklarda olabilir. Burada sekiz karakterlik çerçeve, kullandığım cihaz ve otomasyon sözleşmesinin özelliğidir. Bir kütüphaneyi "tüm RFID kartları dört bayttır" varsayımıyla yayımlamak teknik olarak yanlış olurdu.

### Q ve F klavye sorunu

İlk bakışta cihaz yalnız `0123456789ABCDEF` gönderdiği için klavye yerleşiminin önemsiz olduğu düşünülebilir. Rakamlar için büyük ölçüde doğrudur; `A-F` bölümü için değildir.

Windows giriş zincirinde "tuş", "sanal tuş kodu", "tarama kodu" ve ortaya çıkan karakter aynı kavram değildir. Fiziksel bir tuşun anlamı etkin klavye yerleşimine göre değişebilir. Klavye gibi davranan okuyucu da belirli tuş konumlarını veya sanal tuş kodu dizilerini ürettiğinde, Q klavyede doğru görünen A-F karakterleri F klavyede başka tuş kodlarından gelebilir.

Tarihsel uygulamada bu nedenle iki adet 256 konumlu eşleme tablosu kullandım. Q düzeninde `A-F`, standart `VK_A..VK_F` aralığına karşılık gelirken, kullandığım Türkçe F düzeninde gözlemlediğim eşleme şöyleydi:

```text
A -> VK_U
B -> VK_OEM_2
C -> VK_V
D -> VK_E
E -> VK_OEM_1
F -> VK_A
```

Yayın sürümünde 256 konumlu sabit-zaman eşleme fikrini korudum fakat eşlemeyi uzun bir karakter dizisinin içine saklamadım. Hangi sanal tuş kodunun hangi dört bitlik parçaya dönüştüğü artık açıkça görülebiliyor ve doğrudan test edilebiliyor.

Q/F problemi hangi katmanda ortaya çıkıyorsa düzeltmeyi de orada yapmak gerekir. `WM_KEYDOWN` seviyesinde elimde nihai karakter değil sanal tuş kodu vardır; önce yanlış karakter üretip sonra metin üzerinde “düzeltme” yapmak yerine sanal tuş kodunu doğrudan doğru dört bitlik parçaya eşlemek daha deterministik bir davranış verir.

### Çekirdeği bir sonlu durum makinesi gibi düşünmek

RFID okuyucudan gelen sekiz tuşu bir `TextBox` içinde biriktirmek mümkündür; fakat otomasyon yazılımında bu gereksiz bir kullanıcı arayüzü bağımlılığı doğurur. Benim için daha temiz model, kısa bir durum makinesiydi.

Yayın sürümünde kabul edilen her onaltılık tuş doğrudan dört bitlik parçaya çevriliyor:

```text
partial = (partial << 4) | nibble
```

Sekizinci dört bitlik parça geldiğinde `partial`, doğrudan 32 bit kart değeridir.

Sekiz basamağı önce metin olarak biriktirip sonunda ayrıştırmıyorum. Her geçerli tuş dört bit olarak doğrudan `UInt32` içine kaydırılıyor; sekizinci dört bitlik parça geldiğinde eldeki değer zaten hedef 32 bit kart değeridir. Onaltılık metin gerekiyorsa yalnız tüketici `TryTakeHex` çağırdığında `X8` biçimi oluşturulur.

Sık çalışan yol kabaca sabittir:

```text
sanal tuş kodu
→ klavye düzeni eşlemesi
→ dört bitlik parça
→ kaydırma + OR
→ sayaç
```

Regex, LINQ, büyüyen liste veya tuş başına nesne üretimi yoktur.

### Zaman eşiği neden gerekli?

Klavye gibi davranan okuyucu sekiz karakteri insanın yazamayacağı kadar hızlı bir kısa ve hızlı bir dizi halinde gönderir. Kullanıcı ise aynı karakterleri saniyeler içinde yazabilir. Salt hız ölçümünü güvenlik mekanizması olarak görmek doğru değildir, fakat bir çerçeve sınırı olarak yararlıdır.

Tarihsel kodda kullandığım bir saniyelik sınırı yayın sürümünde varsayılan olarak korudum, fakat süre artık kurucu (`constructor`) üzerinden değiştirilebilir. Zaman ölçümü `TimeProvider` üzerinden yapıldığı için çerçeve zaman aşımı davranışı testte sistem saatine bağımlı olmak zorunda değildir.

Çerçeve mantığı şöyledir:

```text
ilk dört bitlik parça
   ↓
zaman damgası
   ↓
sonraki dört bitlik parça zamanında geldi mi?
   ├─ evet → devam
   └─ hayır → yarım çerçeveyi at
```

Bu sınır, yarıda kalan eski bir okumanın birkaç saniye sonra gelen yeni kartla birleşmesini engeller. Ama süreyi kimlik doğrulama kanıtı olarak kullanmıyorum; yalnız ayrıştırıcının çerçeve sınırıdır.

Enter, Tab, Shift, Ctrl gibi okuyucunun gönderebildiği yaygın son ek ve değiştirici tuş olayları çerçeveye eklenmez. Beklenmeyen yazılabilir bir tuş yarım çerçeveyi sıfırlar. Tamamlanan kart ise bekleyen tek bir `UInt32` olarak tutulur ve `TryTakeCard` / `TryTakeHex` ile bir kez okunup tüketilecek biçimde alınır.

### 1. Normal KeyDown: en basit yol

Windows Forms'ta ilk seçenek sıradan `KeyDown` olayıdır.

Avantajı, P/Invoke veya Windows kanca API'si gerektirmemesidir. Form için `KeyPreview=true` kullanıldığında form, alt kontrollerden önce klavye olaylarını görebilir.

Dezavantajı odaktır. Bazı kontroller belirli tuşları kendi girdi/diyalog işleme mekanizmasında işler. Microsoft belgelerinde de `KeyDown`, `KeyPress`, `KeyUp` sırası ve bazı özel tuşların kontroller tarafından önceden ele alınabileceği belirtilir.

Bu nedenle basit bir kiosk veya kart okutma formunda `KeyDown` yeterli olabilirken, karmaşık bir masaüstü otomasyonunda daha erken bir mesaj katmanı gerekebilir.

### 2. IMessageFilter: uygulama mesaj pompasının önünde

`IMessageFilter`, Windows Forms ileti pompalarında mesaj kontrol veya forma gönderilmeden önce çalışır. `Application.AddMessageFilter` ile uygulamaya eklenebilir.

Benim kullanımımda bu yaklaşımın avantajı, tek tek her kontrolün `KeyDown` olayına bağlanmadan uygulama seviyesinde `WM_KEYDOWN` görebilmesiydi.

Yayın sürümü burada önemli bir sınır koyuyor: mesajı yalnız gözlüyor ve `false` döndürüyor. Yani kart okuyucu mantığı normal kullanıcı arayüzü davranışını engellemiyor.

Tarihsel kodda aynı filtre içinde `WM_ERASEBKGND` mesajı da engelleniyordu. Bu RFID alımıyla ilgili değildi ve form çizimini etkileyebilecek bağımsız bir optimizasyon/deneydi. Açık kaynak sürümünden çıkardım. Bir kart okuyucu bileşeninin pencere arka plan çizimini değiştirmesi için teknik gerekçe yok.

Microsoft'un `IMessageFilter` sözleşmesi tam olarak bu seviyeyi tanımlar: mesaj kontrol veya forma iletilmeden önce incelenebilir; `true` döndürülürse mesaj durdurulur, `false` döndürülürse yoluna devam eder.

### 3. WndProc: Windows mesajına en açık WinForms sınırı

`WndProc`, WinForms kontrol/form sınıfının Windows `WindowProc` karşılığıdır. Formun aldığı işletim sistemi mesajlarını doğrudan görmek istediğimde en deterministik sınır budur.

Örnek yapı:

```csharp
protected override void WndProc(ref Message m)
{
    reader.Observe(ref m);
    base.WndProc(ref m);
}
```

Burada önemli olan, ele almadığım mesajlar için `base.WndProc` çağrısını korumaktır. Microsoft da türeyen kontroller için işlenmeyen mesajların temel gerçeklenime aktarılmasını önerir.

`WndProc` yaklaşımı pencereye özeldir. Bu bazen avantajdır: yalnız belirli form kart okuyucuyu dinler. Bazen dezavantajdır: uygulamanın başka pencereleri veya odak dışı senaryoları kapsam dışı kalır.

### 4. WH_KEYBOARD_LL: düşük seviyeli klavye kancası

Daha geniş bir giriş sınırı gerektiğinde Windows `SetWindowsHookEx` ile `WH_KEYBOARD_LL` kurulabilir. Bu kanca, klavye girdisi iş parçacığı giriş kuyruğuna gönderilmeden önce geri çağırım üretir.

Tarihsel uygulamada kanca yolunu da kullandım. Yayın sürümünü hazırlarken burada “çalışıyor” olmasını yeterli görmeyip yerel Windows API sözleşmesine ve yaşam döngüsüne ilişkin birkaç noktayı düzelttim.

Birincisi, `KBDLLHOOKSTRUCT` yapısının ilk alanı olan `vkCode` 32 bittir. Eski kodda `Marshal.ReadByte(lParam)` kullanılmıştı; yaygın sanal tuş kodu değerleri 0-255 aralığında olduğu için pratikte çalışsa da veri sözleşmesini doğru ifade etmiyordu. Yeni kod `Marshal.ReadInt32(lParam)` kullanır.

İkincisi, geri çağırım temsilcisi (`delegate`) nesne alanında tutulur. Microsoft .NET uygulamalarında geri çağırım temsilcisinin GC tarafından toplanmaması gerektiğini özellikle belirtir.

Üçüncüsü, bütün olaylar `CallNextHookEx` ile zincire aktarılır. Bu bileşen bir tuş kaydedici veya girdi engelleyici değildir. İlgisiz tuşları saklamaz, günlüğe kaydetmez ve diğer uygulamalara giden girdiyi yutmaz.

Dördüncüsü, varsayılan davranış yalnız mevcut süreç ön plandayken veri toplamaktır. Sistem genelinde yakalama teknik olarak mümkün olsa da otomasyon bileşeni için gereksiz kapsam genişletmek doğru değildir. Sistem geneli davranış ancak açıkça istenirse açılır.

Düşük seviyeli kancanın geri çağırımı hızlı olmalıdır. Microsoft, geri çağırım süresi aşılırsa özellikle modern Windows sürümlerinde kancanın sessizce kaldırılabileceğini de belgeliyor. Bu yüzden geri çağırım içinde veri tabanı sorgusu, günlük G/Ç veya kullanıcı arayüzü işlemi yapmak yerine yalnız küçük durum makinesini beslemek daha sağlıklıdır.

### Raw Input: fiziksel aygıtı klavyeden ayırmak gerektiğinde

Tarihsel kodum klasik klavye iletisi ve kanca yollarını kullanıyordu. Bugün aynı problem için özellikle birden fazla klavye benzeri cihaz varsa Raw Input'u da değerlendirmek gerekir.

Raw Input'ta uygulama önce ilgilendiği aygıt sınıfını `RegisterRawInputDevices` ile kaydeder. Ardından `WM_INPUT` alır; `lParam` içindeki `HRAWINPUT` üzerinden `GetRawInputData` çağrılır. `RAWINPUTHEADER.hDevice` sayesinde girdinin hangi fiziksel cihazdan geldiği anlaşılabilir.

Klavye gibi davranan RFID okuyucu ile kullanıcının gerçek klavyesini ayırmak için bu çok değerlidir. Kanca veya `WM_KEYDOWN` tarafında genellikle "hangi klavye" bilgisi uygulamaya doğrudan verilmez.

Raw Input kaydını buna rağmen kütüphanenin içine taşımadım. Windows aynı süreç içinde belirli bir Raw Input aygıt sınıfı için tek hedef pencere kaydı tutar; son kayıt ana uygulamanın önceki düzenini etkileyebilir. Yeniden kullanılabilir bir ayrıştırıcının `RegisterRawInputDevices` çağrısını gizlice sahiplenmesi bu yüzden doğru sınır değildir. Aygıt kaydı gerekiyorsa ana uygulama bunu kendi pencere mimarisinde açıkça yapmalıdır.

### WM_KEYDOWN ile WM_CHAR aynı şey değildir

Bu tür kodlarda en çok karıştırılan noktalardan biri budur.

`WM_KEYDOWN` / `KeyDown` tuş kimliğine yakındır. `WM_CHAR` / `KeyPress` ise klavye düzeni ve girdi dönüşümü sonrasında ortaya çıkan karaktere yakındır.

Eğer okuyucu zaten doğru karakterleri üretip etkin klavye düzeni sorunu yaşamıyorsa `KeyPress` ile sekiz karakter toplamak en kolay çözüm olabilir. Fakat Q/F farkını sanal tuş kodu seviyesinde düzeltmek istiyorsam `WM_KEYDOWN` daha kontrollü bir giriş noktasıdır.

Raw Input ise bir alt katmandadır ve cihaz kimliği avantajı sağlar.

Dolayısıyla "en iyi klavye okuma yöntemi" diye tek cevap yoktur:

| Yöntem | Kapsam | Odak bağımlılığı | Cihaz ayrımı | Karmaşıklık |
|---|---|---:|---:|---:|
| KeyDown/KeyPress | kontrol/form | yüksek | hayır | düşük |
| IMessageFilter | uygulama mesaj pompası | orta | hayır | düşük-orta |
| WndProc | belirli pencere | orta | hayır | orta |
| WH_KEYBOARD_LL | masaüstü/sistem geneli kanca | düşük | hayır | orta-yüksek |
| Raw Input | kayıtlı pencere/aygıt sınıfı | yapılandırılabilir | evet | yüksek |

### Kart değerini doğrudan yetki saymamak gerekir

Klavye gibi davranan okuyucu entegrasyonu kullanışlıdır fakat kriptografik bir güven sınırı oluşturmaz. Aynı klavye olayları yazılımla üretilebilir; kullanılan kart teknolojisine bağlı olarak UID kopyalama, öykünme veya yeniden oynatma olasılığı da ayrıca değerlendirilmelidir.

Sekiz karakterlik sonucu bu yüzden:

```text
kart tanımlayıcısı
```

olarak ele almak daha doğrudur; doğrudan:

```text
kimlik doğrulama kanıtı
```

olarak değil.

Kartın hangi kullanıcıya ait olduğu, erişim yetkisi, zaman/kapı politikası, anti-passback veya benzeri kurallar otomasyon uygulamasının güvenilir veri ve yetkilendirme katmanında çözülmelidir.

### Bu çalışmanın tarihsel yeri

2014'te Afyon Kocatepe Üniversitesi Bilgi İşlem Daire Başkanlığı'ndaki çalışmalarım kartlı geçiş sistemleri, RFID, seri iletişim, veri tabanı ve grafiksel kullanıcı arayüzü yazılımlarını birlikte kapsıyordu. Kişisel arşivimde bu dönem için "RFID Kart Okuyucudan Veri Alma", "RFID Kartlı Güvenlik Yazılımı", UID dönüştürme/kriptolama/oluşturma ve kartlı geçiş veri tabanı yönetimi gibi ayrı çalışmalar yer alıyor.

Kod satırı sayısı küçük olsa da burada birkaç farklı sınır aynı noktada kesişiyor: cihazın gerçekte hangi protokolü uygulamaya sunduğunu doğru tanımlamak, Q/F klavye düzeni farkını sanal tuş kodu seviyesinde ele almak, çerçeveyi zaman sınırıyla ayırmak, Windows mesaj yolunu seçmek ve sonuçta kullanıcı arayüzünden bağımsız dört baytlık bir değer üretmek.

Bugün aynı kodu tekrar tasarlasam da koruyacağım parça bu küçük ayrıştırıcı olurdu. WinForms olayları, kanca kullanımı veya Raw Input entegrasyonu uygulamanın çevresindeki bağdaştırıcılardır; sekiz geçerli dört bitlik parçayı sınırlı durumla tek `UInt32` değere dönüştüren çekirdek ise değişmeden kalabilir. `HexCardAssembler` içindeki kilit de bu durumu tek seferde güncelleyerek aynı nesnenin farklı giriş bağdaştırıcılarından güvenli biçimde kullanılabilmesini sağlar.

### Kaynaklar

- Microsoft, `IMessageFilter` / `PreFilterMessage`: https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.imessagefilter
- Microsoft, `Control.WndProc`: https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.control.wndproc
- Microsoft, `SetWindowsHookEx` ve `WH_KEYBOARD_LL`: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowshookexw
- Microsoft, `LowLevelKeyboardProc`: https://learn.microsoft.com/en-us/windows/win32/winmsg/lowlevelkeyboardproc
- Microsoft, Raw Input: https://learn.microsoft.com/en-us/windows/win32/inputdev/about-raw-input
- Microsoft, `WM_INPUT`: https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-input
- Muhammet Ali Köker, Afyon Kocatepe Üniversitesi BİDB çalışma arşivi: https://alikoker.com.tr/aku-bidb-staj
- Muhammet Ali Köker, proje arşivi: https://alikoker.com.tr/projelerim
- Muhammet Ali Köker, `rfid-keyboard-wedge-csharp`: [rfid-keyboard-wedge-csharp](https://github.com/alikoker/rfid-keyboard-wedge-csharp)



### Açık Kaynak Kod

Makalede incelenen yayın gerçeklenimi açık kaynak olarak GitHub üzerinde yayımlanmıştır:

**Kaynak kod:** [rfid-keyboard-wedge-csharp](https://github.com/alikoker/rfid-keyboard-wedge-csharp)

Depo; `HexCardAssembler`, Türkçe Q/F sanal tuş eşlemeleri, `KeyDown`, `IMessageFilter`, `WndProc` ve `WH_KEYBOARD_LL` bağdaştırıcıları ile örnek kullanım ve testleri içerir. Makale donanım-yazılım sınırını ve tasarım kararlarını açıklarken GitHub deposu çalıştırılabilir gerçeklenimi taşır. Kaynak arşivine makale kopyaları gömülmez; depo üst verisi İngilizce kanonik makaleyi `reading-rfid-keyboard-wedge-hid-dotnet` slug'ı üzerinden referanslar.

## Yazılım Kaydı

**GitHub:** [rfid-keyboard-wedge-csharp](https://github.com/alikoker/rfid-keyboard-wedge-csharp)

**Kalıcı yazılım arşivi (Zenodo DOI):** [10.5281/zenodo.22117311](https://doi.org/10.5281/zenodo.22117311)

## Bu Çalışmaya Atıf

Köker, M. A. (2021). RFID Kart Okuyucudan Klavye/HID Üzerinden 4 Bayt UID Okuma. alikoker.com.tr. https://alikoker.com.tr/rfid-klavye-hid-kart-okuyucu-dotnet

- BibTeX: https://alikoker.com.tr/rfid-klavye-hid-kart-okuyucu-dotnet.bib
- RIS: https://alikoker.com.tr/rfid-klavye-hid-kart-okuyucu-dotnet.ris
- CSL-JSON: https://alikoker.com.tr/rfid-klavye-hid-kart-okuyucu-dotnet.csl.json
