Hızlı Konu Açma

Hızlı Konu Açmak için tıklayınız.

Son Mesajlar

Konulardaki Son Mesajlar

Reklam

Forumda Reklam Vermek İçin Bize Ulaşın

Win32 API ve XLINQ: USB Aygıtların Listelenmesi

YaSa22

Fahri Üye
Fahri Üye
Katılım
12 Temmuz 2014
Mesajlar
2,293
Tepkime puanı
2
Puanları
0
Konum
GTA
Bu makalemizde, C# ile Win32 API’lerini kullanarak bilgisayara bağlı USB aygıtları bulacağız. Aynı zamanda yönetilmeyen (unmanaged) kodlara erişimi ve basit bir XLINQ sorgulaması örneğini göreceğiz.

Win32 API (Application Programming Interface)
Uygulama Programlama Arayüzü (API), Windows sistemlerinin programlanabilir arayüzünü içermektedir. Windows altında çalışan uygulamalar, bu arayüzdeki fonksiyonları, kütüphaneleri ve diğer yapıları kullanmaktadır. Bu ortak arayüz sayesinde programcılar işletim sistemlerin bazı fonksiyonlarını isteklerine göre kullanırlar. Bunu işletim sistemi hizmetlerinden yaralanmayı sağlayan bir fonksiyon, yapı ve kütüphanelerden oluşan bir grup olarak görebiliriz. Programcıların daha hızlı ve güvenli uygulamalar yapması için derlenmiş kütüphanelerdir diyebiliriz. Şimdi projemizde Windows API’lerini kullanmadan önce bazı kavramlara bakalım.

Platform Invoke
.NET Framework’ün, COM Interop özelliği sayesinde, herhangi bir .NET diliyle yazılmış bir kod içerisinden, DLL’ler içerisinden yönetilmeyen (unmanaged) olarak yazılmış fonksiyonlar çağrılıp, yönetilen (managed) ortamda çalıştırılabilir. Bu yönteme kısaca P/Invoke deniliyor. DLL’in ilgili fonksiyonu (metod) inetroperability sayesinde çağrılabilmektedir. Yalnız çağıracağınız fonksiyonun aldığı parametrleri ve veri tiplerini bildirmek zorundasınız.

DllImport Attribute
Attribute, çalışma ortamına (runtime) bilgi veren nitelikler olarak tanımlanabilir. Bir sınıf, metod veya yapı ile ilgili bilgi verebilir. DllImport niteliği, yönetilmeyen (unmanaged) kodla yaratılmış bir DLL’deki metodları kullanmaya yarar. Bu kodlara erişebilmek için System.Runtime.InteropServices çağrılmalıdır. DllImport ile işaretlenen bütün metotlar public static extern olarak bildirilmelidir. Dışarıda bulunan bir metoda erişim için extern yazmak zorundayız. Ayrıca, yönetilen .NET uygulamanızdan yönetilmeyen işlevleri çağırmak için, yönetilmeyen işlevlere parametre olarak geçirdiğiniz yapıların başvurularını ve geçirdiğiniz sabit değerleri bildirmelisiniz. Şimdi bir API nasıl çağrılır bir göz atalım:

Aşağıda verilen örnekleri bir çok makalede ve MSDN’ de görebilirsiniz. Buradaki amaç, DLLImport ve fonksiyon parametrelerinin C# içinden nasıl kullanılacağını göstermektir. Şimdi bazı API fonksiyonlarını nasıl kullanacağımızı iki örnekle görelim: Aşağıda yönetilmeyen (unmanaged) kodu gösterilen Beep fonksiyonunu çağıralım. Bu fonksiyon, PC’deki hoparlörden belli bir frekanstan, istenilen süre kadar ses çıkarır. Fonksiyon kernel32.dll içinde tanımlanmış olup, geri dönüş değeri BOOL türündendir.
BOOL Beep(
DWORD dwFreq, // frekans, hertz
DWORD dwDuration // süre, ms
);
Şimdi Beep fonksiyonunu çağıralım:
using System;
using System.Runtime.InteropServices;
namespace BeepExample
{
class Program
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern Boolean Beep(UInt32 dwFreq, UInt32 dwDuration);
static void Main(string[] args)
{
if (!Beep(466, 500))
Console.WriteLine("Beep fonksiyonu çağrısı başarısız.");
}
}
}


Yukarıdaki kod örneğine dikkat edecek olursak, Dllimport niteliği kullanılarak yapılan tanımlama sonucunda, sınıfımıza ait özel bir metot elde etmiş oluyoruz. Burada, Beep metodu statik ve harici (extern) olarak tanımlanmıştır. Bu metod iki tamsayı değeri alıp, mantıksal bir değer döndürüyor. Program çalıştırıldığında, C# derleyicisi metodun gövdesini kod içinde aramayacak, bunu kernel32.dll’den ihraç edilen bir fonksiyon olarak işleyecektir. DLL’den Beep metodu belleğe yüklenir ve metodun başlangıç adresi saklanır. Yönetilen kod bölümünden parametre olarak girilen değişkenler, Beep fonksiyonu içinde yeralan parametrelere uygun şekle çevrilir ve geçirilir. Yönetilmeyen koddan geri dönüş değeri alınıyorsa, uygun C# türüne dönüştürülür. Burada, C# içinden sadece CLR veri tiplerine erişilebilir. Oysa, Windows API içinde C dilindeki veri tipleri kullanılmaktadır. Metodlarımızı tanımlarken CLR tipleri kullanmalıyız.
Win32 tipi CLR tipi Uzunluk
BYTE Byte 8 bit
BOOL Boolean (*)
CHAR Char 16 bit
SHORT Int16 16 bit
WORD UInt16 16 bit
INT, LONG Int32 32 bit
DWORD, UINT, ULONG UInt32 32 bit
HANDLE IntPtr 32/64 bit
FLOAT Single 32 bit
DOUBLE Double 64 bit
LPCTSTR String (**)


Tablo-1: Win32 ve CLR veri tipleri. (*) BOOL veri tipi Win32’ de 32-bit signed integer olmasına rağmen, CLR’de Int32’ ye karşılık gelir. C#’ ta ise bool true/false mantıksal değerini alır. Marshalling sırasında, System.Int32 yerine System.Boolean kullanmamız daha anlamlı olur.

(**) MarshalAs niteliği kullanmak gerekir. Örnek: [MarshalAs(UnmanagedType.LPTStr)] string

İkinci örneğimizde GetWindowsDirectory fonksiyonunu çağıracağız. Bu fonksiyon Windows’un kurulu olduğu klasörü verir. Ayrıca, fonksiyon kernel32.dll içinde tanımlanmış olup, geri dönüş değeri UINT türündendir.
UINT GetWindowsDirectory(
LPTSTR lpBuffer, // dizin adını içerecek olan değişken
UINT uSize // lpBuffer uzunluğu, MAX_PATH’e eşitlenmeli
);

using System;
using System.Text;
using System.Runtime.InteropServices;
namespace WinDirExample
{
class Program
{
// Win32 sistemlerde MAX_PATH = 260 karakter.
const int MAX_PATH = 260;
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint GetWindowsDirectory(StringBuilder lpBuffer, uint uSize);
static void Main(string[] args)
{
StringBuilder sb = new StringBuilder(MAX_PATH);

if (GetWindowsDirectory(sb, MAX_PATH) != 0)
{
Console.WriteLine(sb.ToString());
}
else
{
Console.WriteLine("GetWindowsDirectory fonksiyonu çağrısı başarısız.");
}
}
}
}


İkinci örnekte dikkat edilmesi gereken iki nokta var:
DLLImport içinde ANSI veya Unicode karakter seti seçimi için Charset.Auto parametresi bildirilmiştir. Diğer taraftan, fonksiyonun birinci parametresi StringBuilder türündedir. İkinci parametre de StringBuilder’ın uzunluğunu bildiren bir tamsayıdır. Bu sayı, StringBuilder’a yeterince uzunluk verebilmek için MAX_PATH’e eşitlenmelidir. Hata oluşması durumunda, fonksiyon geriye sıfır değerini döndürür. Windows klasörünün adı, sıfırla sonlanan bir string tipiyle StringBuilder’a geçirilmektedir.

Yukarıdaki her iki örnekte görüldüğü gibi, dış metodlara (extern) yapılan bir çağrıdan sonra, hata bilgilerine erişmek için SetLastError özelliğine true değeri verilmiştir. Bu, CLR’nin hataları tutmasını sağlar. Dönen hata koduna erişmek için GetLastWin32Error metodunu çağırırız.

Yapılar (Structs)
Yapılar (structs) sınıflarda olduğu gibi, bir nesne için veri modeli sağlar. Sınıflara benzer özellikler göstermelerine rağmen, aralarındaki en önemli fark, yapıların değer türü (value type) ve sınıfların referans türünden (reference type) olmasıdır. Değer türünde bir değişken, belleğin stack bölgesinde tutulur. Referans türünde bir değişken ise değeri heap bölgesinde tutulurken, nesnenin kendisi stack bölgesinde tutulur ve değerlerin bulunduğu adresi işaret eder. Böylece aynı verinin iki defa saklanması önlenmiş olur. Diğer taraftan, yapı değer türü bir parametre olarak metoda geçirilirken, yapının kopyası geçirilir. Fiziksel olarak nesnenin kopyalanmasıdır. Sınıf örneğinde ise referans türünden olan bir sınıfın kendisi değil, referansı metoda geçirilir. Bu anlam olarak bir göstericinin kopyalanmasıdır. CLR, ortak dil çalışma zamanı sayesinde, değer ve referans nesneleri, alan düzenlemesine tabi tutularak tekrar düzenlenebilir. Böylece API’ deki yapılar, yönetimli referans tipine veya yönetimli değer tiplerine çevrilebilirler. Setupapi.h içinde tanımlanmış SP_DEVINFO_DATA yapısını ele alarak, bunları açıklamaya başlayalım. Özgün tanımı aşağıdaki gibidir:
typedef struct _SP_DEVINFO_DATA {
DWORD cbSize;
GUID ClassGuid;
DWORD DevInst;
ULONG_PTR Reserved;
} SP_DEVINFO_DATA,
*PSP_DEVINFO_DATA;
Şimdi bunu yönetimli bir yapıya çevirelim:
public struct SP_DEVINFO_DATA
{
public Int32 cbSize;
public Guid ClassGuid;
public Int32 DevInst;
public UIntPtr Reserved;
};
C# kod içerisinde kullanımı:
[DllImport("setupapi.dll")]
static extern Boolean SetupDiEnumDeviceInfo(
IntPtr DeviceInfoSet, Int32 MemberIndex, out SP_DEVINFO_DATADeviceInfoData);
SP_DEVINFO_DATA devInfoData;
devInfoData.cbSize = Marshal.SizeOf(typeof(SP_DEVINFO_DATA));
...
// Yapının SetupDiEnumDeviceInfo() fonksiyonuyla kullanılması.
// Fonksiyon parametreleri için bkz. Platform SDK
SetupDiEnumDeviceInfo (myHandle, myInt, out devInfoData);


Burada, değer türüne marshalling uygulamış olduk. İlk önce bu yapıdan yeni bir nesne oluştuduğumuzu ve bunu bir API fonksiyonuna geçirmek istediğimizi varsayalım. Nesne değerinin bir kopyası çıkarılıp, CLR tarafından stack’a push edilir. Ama bazı durumlarda API fonksiyonu yapıyı değil de buna işaret eden göstericiyi bekler. Bu durumda nesnenin kopyası yerine, geçireceğimiz yapının değişkenini işaret eden göstericiyi kullanırız. Aklımıza şu soru gelebilir: Bir yapıyı yönetimli referans türüne yani yönetimli bir sınıfa çevirebilir miyiz? Yukarıda çevrilen yapıyıdaki struct yerine class yazarsak olur. Ancak bunu metodlara geçireceğimiz zaman şuna dikkat etmeliyiz. API tarafından beklenen özgün yapının, hafıza kısmındaki düzeniyle birebir örtüşecek biçimde olması gerekir. Örneğin yapının kapladığı alanı tam tespit edip, buna hafızada aynı miktarda yer ayrılması çok önemlidir.

Şimdi SP_DEVINFO_DATA yapısını tekrar ele alarak, bunu yönetimli bir sınıfa çevirelim:
[StructLayout(LayoutKind.Sequential)]
public class SP_DEVINFO_DATA
{
public Int32 cbSize;
public Guid ClassGuid;
public Int32 DevInst;
public UIntPtr Reserved;
};
Dikkat ederseniz SP_DEVINFO_DATA yapısı yönetilen bir sınıfa çevrilmiş oldu. Başka bir deyişle, yapı referans türüne marshalling edildi. C# kod içerisinde kullanımı:
[DllImport("setupapi.dll")]
static extern Boolean SetupDiEnumDeviceInfo(
IntPtr DeviceInfoSet, Int32 MemberIndex, SP_DEVINFO_DATADeviceInfoData);
SP_DEVINFO_DATA devInfoData = new SP_DEVINFO_DATA();
devInfoData.cbSize = Marshal.SizeOf(typeof(SP_DEVINFO_DATA));
...
// Yapının SetupDiEnumDeviceInfo() fonksiyonuyla kullanılması.
// Fonksiyon parametreleri için bkz. Platform SDK
SetupDiEnumDeviceInfo (myHandle, myInt, devInfoData);


API fonksiyonları ve yapılarla ilgili olarak, buraya kadar anlatılanları daha iyi kavrayabilmek için bunları bir proje içinde kullanırsak, konu daha açık şekilde anlaşılabilir. Şimdi bir bilgisayara bağlı olan USB aygıtları bulan ve bunları listeleyen basit bir uygulama yapacağız. Bunun yapılabilmesi için aygıtın bilgisayara bağlı ve aktif olması gerekmektedir. USB’den bağlanabilen aygıtlara örnek olarak bluetooth aygıtı, yazıcı, hafıza kartı, harici disk, cep telefonu, kamera v.b. verilebilir. Windows bu cihazların bağlantı özelliklerini kayıt altına alır ve saklar, yoksa değişik zamanlarda bu cihazları çalıştırdığımızda tekrar nasıl hatırlayabilsin ki? Bağlanan aygıtın, Windows tarafından yönetilebilmesi ve buna uygun sürücülerin yüklenebilmesi için, cihaz tarafında işletim sistemine yarayacak bilgiler bulunmaktadır. Örneğin bir aygıt USB portundan bilgisayara bağlandığında, elsıkışma (handshaking) sırasında, Windows’a kimlik bilgilerini rapor etmek zorundadır. Bu bilgiler üretici - ürün ID’leri, protokol numarası ve sınıf - alt sınıf vb. bilgilerdir. Daha sonra gelen bilgiler, Windows’un içindeki aygıt sürücüleri tarafından bir tanımlama formatına çevrilir. Buna örnek vermek gerekirse bir USB donanım ID’si şu şekillerden birine sahiptir:

USB\Vid_XXXX&Pid_XXXX
USB\Vid_XXXX&Pid_XXXX&Rev_XXXX
VID: Donanım üreticisi kimlik numarası. Örnek, “8086” Intel, “03f0” Hewlett-Packard, “045e” Microsoft gibi.
PID: Her üretici tarafından kendi ürününe verdiği benzersiz ürün kimlik numarası. Aynı numara diğer üreticiler tarafından kullanılabilir.
REV: Ürün revizyon numarası.
XXXX: Hexadecimal sayı.


Örneğin bağlı bir USB aygıtın Windows’tan gelen aygıt adı şu şekilde olsun: \\?\USB#Vid_03f0&Pid_7804
03f0 değeri "Hewlett-Packard", 7804 değeri ise "HP D1360 Renkli Inkjet Yazıcı" olduğunu gösterir.

VID ve PID kodlarını, donanım üreticleri sitelerinden veya USB’ye destek veren sitelerden bulabilirsiniz. Örnek olması bakımından, bazı donanım üreticileri ve ürünlerinin ID’lerini içeren kısıtlı bir xml dosyası projeyle beraber verilmiştir.

Şimdi ne yapacağımızı bir daha özetleyelim:
İlk önce API fonksiyonları ve yapıların yardımıyla, bilgisayarımıza bağlı aktif durumdaki USB aygıtları bulacağız. Bulunan her bir USB aygıtın, satıcı veya üretici kimlik numarası (VID) ve ürün kimlik numarası (PID) alınacaktır. Daha sonra PID ve VID kimlik numaraları bir Xml dosyasından sorgulanacaktır. Bunu XLINQ ile gerçekleştireceğiz. Eğer PID ve VID numaraları Xml içinde bulunursa, üretici firma adı ve ürün tanımlamasını (adla beraber model, tip, seri vb.) çekeceğiz. Örnek Xml dosyasını projeyle beraber bulabilirsiniz. Bu dosya içinde daha önceden kaydedilmiş 38 firma adı ve 800 adet ürün bulunmaktadır. Xml dosyası içeriği ise aşağıdaki gibidir:
<?xml version="1.0" encoding="utf-8"?>
<vendors>
<vendor vid="0506" name="3Com Corp.">
<product pid="00a0" description="3CREB96 Bluetooth Adapter" />
<product pid="00a1" description="3Com Bluetooth USB Device" />
<product pid="00df" description="3Com Home Connect lite" />
<product pid="0100" description="HomeConnect ADSL Modem USB Driver" />
<product pid="4601" description="3C460B 10/100 Ethernet Adapter" />
<product pid="f003" description="3CP4218 ADSL Modem" />
</vendor>
<vendor vid="0a5c" name="Broadcom Corp.">
<product pid="2033" description="BCM2033 Bluetooth" />
<product pid="2035" description="BCM2035 Bluetooth" />
</vendor>
<vendor vid="04a9" name="Canon, Inc.">
<product pid="1051" description="BJC-3000 Color Printer" />
<product pid="105d" description="S450 Printer" />
<product pid="1062" description="S500 Printer" />
<product pid="106b" description="S520 Printer" />
<product pid="106d" description="S750 Printer" />
<product pid="2201" description="CanoScan FB320U" />
<product pid="2202" description="CanoScan FB620U" />


Sınıf içinde kullanacağımız API fonksiyonları ve yapılar şunlardır:
1- Fonksiyonlar:​


  • SetupDiGetClassDevs
  • SetupDiEnumDeviceInterfaces
  • SetupDiGetDeviceInterfaceDetail
  • SetupDiDestroyDeviceInfoList

2- Yapılar:​


  • SP_DEVICE_INTERFACE_DATA
  • SP_DEVICE_INTERFACE_DETAIL_DATA

Bunları kullanacak olan bir sınıf yaratalım. Bu sınıfta ilk önce, SetupDiGetClassDevs ve SetupDiEnumDeviceInterfaces kullanarak, bağlı bulunan aygıtları ve arayüz seti listesini alalım. Bunlardan verilen aygıt arayüzü sayesinde, SetupDiGetDeviceInterfaceDetail fonksiyonunu çağıralım. Fonksiyon parametlerinde yer alan yapılardan aygıt hakkında ayrıntılı bilgi alabiliriz. Örneğin, Windows içindeki aygıt yolunu veya bağlantı durumunu öğrenebiliriz. Sınıf ve diğer kavramlar, kodlar içerisinde yer alan yorum satırlarında açıklanmıştır.

Geçerli aygıt sınıflarını ve bunlara ait değerleri kayıt defteri içinde şu dizinden görebilirsiniz:
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\DeviceClasses\ Aşağıdaki listede bazı aygıt arayüz sınıf GUID’ leri verilmiştir:
Aygıt Arayüz Adı GUID
USB aygıtlar A5DCBF10-6530-11D2-901F-00C04FB951ED
HID 4D1E55B2-F16F-11CF-88CB-001111000030
Network kartı AD498944-762F-11D0-8DCB-00C04FC3358C
Diskler 53F56307-B6BF-11D0-94F2-00A0C91EFB8B


Tablo-2: Önemli sınıf GUID’leri.

Şimdi LINQ Preview şablonunu kullanarak, yeni bir LINQ Windows Application projesi oluşturalım. Daha sonra Win32API adlı sınıfımız yazalım. Sınıfla ilgili açıklamalar ve kavramlar, kodlar içerisinde yer alan yorum satırlarında açıklanmıştır. Daha sonra formumuzu tasarlayacağız. API fonksiyonlarını ve yapılarını sarmalayan yardımcı sınıfımızı (Win32API.cs) aşağıdaki gibi kodlayalım:
// Win32API.cs
using System;
using System.Collections;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace USBView
{
// Aygıt yönetimiyle ilgili bazı API fonksiyonlarını ve yapılarını
// sarmalayan sınıf.
public class Win32API
{
//***************************
// Kullanacağımız sabitler *
//***************************
// USB aygıtlar için sabit. Bkz. usbiodef.h
internal const string GUID_DEVINTERFACE_USB_DEVICE =
"A5DCBF10-6530-11D2-901F-00C04FB951ED";
// winbase.h içinden alınan sabit.
internal static IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
// setupapi.h içinden alınan sabitler.
internal const Int32 DIGCF_PRESENT = 0x00000002;
internal const Int32 DIGCF_DEVICEINTERFACE = 0x00000010;

//***************************
// Kullanacağımız yapılar *
//***************************
// Aygıt arayüzü tanımlayan yapı.
[StructLayout(LayoutKind.Sequential)]
internal class SP_DEVICE_INTERFACE_DATA
{
// Yapının uzunluğu, byte.
public Int32 cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DATA));
// Aygıt arayüzü sınıfı GUID numarası.
public Guid InterfaceClassGuid = Guid.Empty;
// Flags değişkeniyle aygıt hakkında bilgi verilir.
public Int32 Flags = 0;
public Int32 Reserved = 0; // Kullanılmıyor
};
// Bir aygıt arayüzünün yol bilgisini gösteren yapı.
[StructLayout(LayoutKind.Sequential, Pack = 2)]
internal class SP_DEVICE_INTERFACE_DETAIL_DATA
{
// Yapının uzunluğu, byte.
public Int32 cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DETAIL_DATA));
// Aygıtın yol bilgisini (device path) içeren
// sıfırla sonlandırılmış string’e işaret eden pointer.
public Int16 DevicePath;
}

//***************************
// P/Invoke Metodlar *
//***************************
// SetupDiGetClassDevs fonksiyonu, belirtilen sınıfa ait
// aygıtların arayüz setini alır.
[DllImport("setupapi.dll", SetLastError = true, CharSet=CharSet.Auto)]
internal static extern IntPtr SetupDiGetClassDevs(
// Aygıt sınıfının GUID numarası.
ref Guid ClassGuid,
// DIGCF_DEVICEINTERFACE değeriyle ilgili özel bir kullanımı var.
// Kullanılmayabilir, sıfır veya null değere sahip olabilir.
Int32 Enumerator,
// Aygıt arayüz detaylarıyla ilgili bir handle. Kullanmayacağız.
IntPtr hwndParent,
// Aygıt kontrol değerleri.
Int32 Flags
);
// SetupDiDestroyDeviceInfoList fonksiyonu, aygıt arayüz setini
// sonlandırır ve ayrılan hafızayı serbest bırakır.
[DllImport("setupapi.dll", SetLastError = true)]
internal static extern Boolean SetupDiDestroyDeviceInfoList(
// Aygıt arayüzüne referans.
IntPtr DeviceInfoSet);
// SetupDiEnumDeviceInterfaces fonksiyonu, bir aygıt için
// aygıt arayüz setinden, aygıtla ilgili arayüz yapısını alır.
[DllImport("setupapi.dll", SetLastError = true)]
internal static extern Boolean SetupDiEnumDeviceInterfaces(
// SetupDiGetClassDevs tarafından döndürülen, aygıt arayüz grubuna ait handle
IntPtr DeviceInfoSet,
// SP_DEVINFO_DATA yapısına ait gösterici. Kullanmayacağız.
IntPtr DeviceInfoData,
// Aygıt sınıfının GUID numarası.
ref Guid InterfaceClassGuid,
// Arayüz listesi dizini.
Int32 MemberIndex,
// SP_DEVICE_INTERFACE_DATA yapısı.
SP_DEVICE_INTERFACE_DATA DeviceInterfaceData
);
// SetupDiGetDeviceInterfaceDetail fonksiyonu, belirtilen aygıt
// arayüzünden detay veya yol bilgisini alır.
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern Boolean SetupDiGetDeviceInterfaceDetail(
// Aygıt arayüz grubuna ait handle
IntPtr DeviceInfoSet,
// SP_DEVICE_INTERFACE_DATA yapısı.
SP_DEVICE_INTERFACE_DATA DeviceInterfaceData,
// SP_DEVICE_INTERFACE_DETAIL_DATA yapısına ait gösterici.
// Alınacak veri (path gibi) bu yapıya doldurulacak.
IntPtr DeviceInterfaceDetailData,
// SP_DEVICE_INTERFACE_DETAIL_DATA yapısı + device path içine
// alan tampon uzunluğu.
Int32 DeviceInterfaceDetailDataSize,
// Yukarıdaki tampon için gerekli olan uzunluk.
ref Int32 RequiredSize,
// SP_DEVINFO_DATA yapısına ait gösterici. Kullanmayacağız.
IntPtr DeviceInfoData
);
//***************************
// Public Metodlar *
//***************************
// GetDevicesPath metodu USB aygıtları bulup, aygıt yollarını (device path)
// bir ArrayList içine dolduracak.
public static ArrayList GetDevicesPath()
{
IntPtr hDevInfo = IntPtr.Zero; // Aygıtlar için handle
Int32 iSize = 0; // Aygıt yolu (device path) uzunluğu
Int32 iDevCount = 0; // Aygıt sayısı
String sDevicePath = ""; // Aygıt yolu (device path)

// ArrayList içine bulunan aygıt yollarını (device path) dolduracağız.
ArrayList arrList = new ArrayList();
// USB aygıtları arayüz GUID numarası
Guid myGuid = new Guid(GUID_DEVINTERFACE_USB_DEVICE);
// Bilgisayar bağlı durumda olan USB cihazların aygıt bilgisini
// almak için SetupDiGetClassDevs fonksiyonunu çağırıyoruz.
// DIGCF_DEVICEINTERFACE: Verilen GUID’e göre arayüz sınıfını temsil eder.
// DIGCF_PRESENT: Hazır durumdaki USB aygıtlar.
hDevInfo = SetupDiGetClassDevs(ref myGuid, 0, IntPtr.Zero,
DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);

// Fonksiyondan dönen değer geçerli mi?
if (hDevInfo == INVALID_HANDLE_VALUE)
{
// Aygıt listesinin alınması başarısızlığa uğradı.
// Ayrıntılı hata bilgisi almak için GetLastWin32Error çağrılır.
throw new Win32Exception(Marshal.GetLastWin32Error());
}

try
{
// SP_DEVICE_INTERFACE_DATA yapı nesnesi tanımlıyoruz.
SP_DEVICE_INTERFACE_DATA devInterfaceData = new
SP_DEVICE_INTERFACE_DATA();
// SP_DEVICE_INTERFACE_DETAIL_DATA yapı nesnesi tanımlıyoruz.
SP_DEVICE_INTERFACE_DETAIL_DATA devInterfaceDetailData = new
SP_DEVICE_INTERFACE_DETAIL_DATA();
// USB aygıtları while tekrarlı yapısıyla arıyoruz.
// SetupDiEnumDeviceInterfaces fonksiyonuyla aygıt arayüzünü
// bulma şartı kontrol edilir.
//
// hDevInfo: SetupDiGetClassDevs fonksiyonundan gelen handle.
// SP_DEVINFO_DATA yapısı opsiyonel olduğu için sıfır değerine eşitlenmiş.
while (SetupDiEnumDeviceInterfaces (hDevInfo, IntPtr.Zero,
ref myGuid, iDevCount, devInterfaceData))
{
// Aygıt arayüzü hakkındaki detayları alabilmek için iki aşamalı
// bir yol izlenebilir.
// #1. Adım:
// SetupDiGetDeviceInterfaceDetail fonksiyonu,
// SP_DEVICE_INTERFACE_DETAIL_DATA yapısına sıfır veya null değeri
// verilerek çağrılır. Gerekli tampon uzunluğu için sıfır girilir.
// RequiredSize parametresinden gerekli olan uzunluk elde edilir.
// #2. Adım:
// Bellekte tampon için yeterli miktarda yer ayır.
// Daha sonra SetupDiGetDeviceInterfaceDetail fonksiyonunu
// tekrar çağır ve arayüz detayını al.
if (!SetupDiGetDeviceInterfaceDetail (hDevInfo, devInterfaceData,
IntPtr.Zero, 0, ref iSize, IntPtr.Zero))
{
// Tampon için bellekte dönen uzunluk değeri kadar yer ayır.
IntPtr buffer = Marshal.AllocHGlobal(iSize);
// StructureToPtr ile yapıdaki değerleri tampona kopyala.
// Burada yönetimli kod kısmından, yönetilmeyen hafıza
// bloğuna veriler marshall ediliyor.
Marshal.StructureToPtr(devInterfaceDetailData, buffer, false);
try
{
// SetupDiGetDeviceInterfaceDetail fonksiyonunu tekrar çağır.
// Tamponun gerçek uzunluğunu iSize parametresiyle geçir.
if (SetupDiGetDeviceInterfaceDetail(hDevInfo, devInterfaceData,
buffer, iSize, ref iSize, IntPtr.Zero))
{
// Tampon içindeki aygıtın yol bilgisini (device path) alabilmek için
// tamponun adresini 4 byte ötele ve daha sonra
// pDevicePath göstericisine aktar.
//
// Burada göstericinin adres bileşeni 4 arttırılıyor.
IntPtr pDevicePath = (IntPtr)((Int32)buffer +
Marshal.SizeOf(typeof(Int32)));
// Yönetilmeyen bellekte bulunan bir string içeriği
// yönetilen bir string içine kopyalanıyor.
sDevicePath = Marshal.PtrToStringAuto(pDevicePath);
// Bulunan aygıt yolu ArrayList değişkenine ekleniyor.
arrList.Add(sDevicePath);
}
else
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}//end if
}
finally
{
// AllocHGlobal ile bellekte ayrılan yeri serbest bırak.
Marshal.FreeHGlobal(buffer);
}
// Bir sonraki aygıta bakmak için sayacı arttır.
iDevCount++;
}
else
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}//end if
}//end while
}
finally
{
// Bellekteki aygıt bilgi setini serbest bırak.
SetupDiDestroyDeviceInfoList(hDevInfo);
}

return arrList;
}
}
}



Win32API adlı sınıfımızda, ilk önce sabitleri tanımladık. Bu sabitleri header dosyalarından veya Platform SDK içindeki ”Win32 Error Codes” bölümünden bulabilirsiniz. Daha sonra yapıları yönetimli bir sınıfa dönüştürdük. Bundan sonra dış metod çağrılarını yazdık. Şimdi bunları kısaca özetleyelim:



  • SetupDiGetClassDevs fonksiyonu, belirtilen sınıfa ait aygıtların arayüz setini alır ve geriye bir handle olarak çevirir. Buradaki handle, windows tarafından ayrılan hafıza bloku yerini gösterir. Fonksiyon çağrısı başarılıysa geriye bir handle, başarısız durumda ise INVALID_HANDLE_VALUE değerini çevirir.
  • SetupDiEnumDeviceInterfaces fonksiyonu, bir aygıt için aygıt arayüz setinden, aygıtla ilgili arayüz yapısını alır. Fonksiyon çağrısı başarılıysa geriye sıfır olmayan bir sayı, başarısız durumda ise sıfır değerini çevirir.
  • SetupDiGetDeviceInterfaceDetail fonksiyonu, belirtilen aygıt arayüzünden detay veya ayıtın yol bilgisini (device path) alır. Fonksiyon çağrısı başarılıysa geriye sıfır olmayan bir sayı, başarısız durumda ise sıfır değerini çevirir.
  • SetupDiDestroyDeviceInfoList fonksiyonu, aygıt bilgisi setini sonlandırır ve buna ayrılan belleği serbest bırakır. Fonksiyon çağrısı başarılıysa geriye sıfır olmayan bir sayı, başarısız durumda ise sıfır değerini çevirir.

Son olarak GetDevicesPath adlı bir public metod yazdık. GetDevicesPath metodu öncelikle bağlı durumdaki USB aygıtlarını buluyor. Daha sonra bunların aygıt yollarını (device path) bir ArrayList içine dolduruyor. Burada dikkat edilmesi gereken birkaç nokta var: Değişkenleri ve yapı nesnelerini tanımladıktan sonra, SetupDiGetClassDevs fonksiyonunu çağırıyoruz. Dönen handle geçerli ise SetupDiEnumDeviceInterfaces fonksiyonunu çağırıyoruz. İleride kullanacağımız gerçek aygıt arayüzü detaylarını alabilmek için SetupDiGetDeviceInterfaceDetail fonksiyonuna çağrı yapıyoruz. Burada iki aşamalı bir yol izliyoruz. Birinci adımda, SP_DEVICE_INTERFACE_DETAIL_DATA ve DeviceInterfaceDetailData parametresini sıfırlayarak fonksiyona giriyoruz. Bu çağrı başarısızlağa uğrayarak ERROR_INSUFFICIENT_BUFFER hatasını verir. İsteğe bağlı olarak hata mesajı ayrıca gösterilebilir. Bu hatayla beraber RequiredSize parametresi gereken uzunluğu almış olur. İkinci aşamada ise bellekte tampon için yeterli miktarda yer ayırdıktan sonra, SetupDiGetDeviceInterfaceDetail fonksiyonu bir kez daha çağırılır ve arayüz detayı alınmış olur. Bu çağrıda gerçek uzunluk referansı ve SP_DEVICE_INTERFACE_DETAIL_DATA göstericisi girilir. Sınıfımızı kodladıktan sonra şimdi formumuzu tasarlamaya başlayalım: Projemize yeni bir form ekleyelim. Bir adet ListView ve iki adet Button kontrollerini formun üzerine yerleştirelim. ListView için dört adet kolon ekleyelim ve View özelliğini Details olarak seçelim. Proje kodumuz aşağıdaki gibi devam etmektedir.
// Form1.cs
using System;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Text;
using System.Text.RegularExpressions;
using System.Query;
using System.Xml.XLinq;
using System.Data.DLinq;
using System.Runtime.InteropServices;
namespace USBView
{
public partial class Form1: Form
{
public Form1()
{
InitializeComponent();
}
// "Araştır" düğmesine basılınca, aktif olan USB aygıtlarının
// üretici ve ürün bilgilerini ListView üzerinde gösterir.
private void buttonSearch_Click(object sender, EventArgs e)
{
string strVID; // Üretici ID
string strVendorName; // Firma Adı
string strPID; // Aygıt ID
string strProductName; // Aygıt Adı
string strDevicePnPID; // Aygıt PnPID
// Aygıt yollarını tutacak bir ArrayList nesnesi oluştur.
ArrayList myAL = new ArrayList();
// ListView kontrolümüzün içeriğini temizliyoruz.
listView1.Items.Clear();
try
{
// Win32API içinden GetDevicesPath metodunu çağır.
// USB aygıt yollarını ArrayList içine doldurur.
myAL = Win32API.GetDevicesPath();
// ArrayList içindeki eleman sayısı sıfırsan büyük mü?
if (myAL.Count > 0)
{
// Aygıt yolları arasında gezinmek için foreach döngüsünü kullanıyoruz.
foreach (String str in myAL)
{
// GetDevicePnPID metodunu çağırarak USB donanım ID’si alınıyor.
strDevicePnPID = GetDevicePnPID(str);
// USB donanım ID’si içinden "vid_" kelimesini yakala ve
// üretici ID’sini strVID değişkenine kopyala.
strVID = strDevicePnPID.Substring(strDevicePnPID.IndexOf("vid_") + 4, 4);
// USB donanım ID’si içinden "pid_" kelimesini yakala ve
// aygıt ID’sini strPID değişkenine kopyala.
strPID = strDevicePnPID.Substring(strDevicePnPID.IndexOf("pid_") + 4, 4);
// Firma adını Xml içerikten sorgula.
strVendorName = XmlQueryByVID(strVID);
// Aygıt adını Xml içerikten sorgula.
strProductName = XmlQueryByVIDnPID(strVID, strPID);

// ListeView öğelerini tanımlıyoruz.
ListViewItem lvItem = new ListViewItem();
// Sorgulama sonucu donen bilgiler öğelere aktariliyor.
lvItem.Text = strVID;
lvItem.SubItems.Add(strPID);
lvItem.SubItems.Add(strVendorName);
lvItem.SubItems.Add(strProductName);
// Öğeleri ListView’e ekliyoruz.
listView1.Items.Add(lvItem);
}//end foreach
}
else
{
MessageBox.Show("Bağlı USB aygıt bulunamadı.");
}//end if
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
// "Kapat" düğmesine basılınca, uygulmadan çıkılır.
private void buttonClose_Click(object sender, EventArgs e)
{
Application.Exit(); // Uygulamayı sonlandır
}
// Aygıt yolu içindeki USB donanım ID’si desenlere göre bulunuyor
// ve bir string olarak çevriliyor.
private static string GetDevicePnPID(string sPath)
{
// Aygıt yolu VID & PID desenleri
Regex rgx1 = new Regex(@"vid_[0-9a-fA-F]{4}&pid_[0-9a-fA-F]{4}",
RegexOptions.IgnoreCase);

Regex rgx2 = new Regex(@"VID_[0-9a-fA-F]{4}&PID_[0-9a-fA-F]{4}");
// Desenlere göre karşılaştırma yap.
if (rgx1.Match(sPath.ToLower()).Success)
return (rgx1.Match(sPath).Value).ToLower();
else if (rgx2.Match(sPath).Success)
return (rgx2.Match(sPath).Value).Replace("VID", "vid").Replace("PID", "pid");
else
return String.Empty;
}//end GetDevicePnPID
// Xml verilerinden üretici ID’sine göre sorgulama yapılıyor ve
// üretici adı çekiliyor.
private static string XmlQueryByVID(string sVID)
{
// XElement sınıfından Load metoduyla usbdev.xml isimli dosya
// belleğe yüklenir.
XElement usbids = XElement.Load("usbdev.xml");
try
{
// vendorName nesnesi üzerinden LINQ sorgusunu çalıştır.
var vendorName =
(from
vend in usbids.Elements("vendor")
where
(string)vend.Attribute("vid") == sVID
select
(string)vend.Attribute("name")).First();
return vendorName;
}
catch (EmptySequenceException ex)
{
// Üretici - Satıcı adı kayıtlı değil.
return "Bulunamadı";
}
}//end XmlQueryByVID
// Xml verilerinden üretici ve aygıt ID’lerine göre sorgulama yapılıyor ve
// aygıt adı çekiliyor.
private static string XmlQueryByVIDnPID(string sVID, string sPID)
{
// usbdev.xml isimli dosya belleğe yükleniyor.
XElement usbids = XElement.Load("usbdev.xml");
try
{
// productName nesnesi üzerinden LINQ sorgusunu çalıştır.
var productName =
(from
vend in usbids.Elements("vendor"),
prod in vend.Elements("product")
where
(string)vend.Attribute("vid") == sVID &&
(string)prod.Attribute("pid") == sPID
select
(string)prod.Attribute("description")).First();
return productName;
}
catch (EmptySequenceException ex)
{
// Ürün adı kayıtlı değil.
return "Bulunamadı";
}
}//end XmlQueryByVIDnPID
}
}


Uygulamayı çalıştırıldığında, kendi bilgisayarımdaki ekran görüntüsü aşağıdaki gibidir.



Sizdeki USB bilgilerinin ve görüntünün farklı olması doğaldır. Araştır düğmesine basılınca, bilgisayara bağlı ve çalışır durumda olan USB aygıtların yollarını GetDevicesPath metoduyla alırız. Buradan dönen ArrayList içinde eleman varsa, GetDevicePnPID metodunu çağırarak tek tek USB donanım ID’si alıyoruz. Bunu bir örnekle açıklayalım: GetDevicesPath metodu çağrıldığında aşağıdaki gibi string tipinde bir device path geldiğini varsayalım:
"\\\\?\\usb#vid_0a12&pid_0001#5&4a26971&0&1#{a5dcbf10-6530-11d2-901f-00c04fb951ed}" GetDevicePnPID metodu çağrıldığında, string türünden dönen USB donanım ID’si şu şekildedir: vid_0a12&pid_0001

Bunun içinden string metodları kullanarak vid ve pid değerlerini alabiliriz. Buradan üretici ID’si için 0a12 ve aygıt ID’sinin 0001 olduğu açıkça görülmektedir. Bulunan vid değeriyle XmlQueryByVID metodunu kullanarak, bir LINQ sorgusuyla usbdev.xml içinden üretici adını çekebiliriz. Kayıt bulunamazsa veya bir hatayla karşılaşılırsa, EmptySequenceException istisnası yakalanır. Bunu takiben, bulduğumuz vid ve pid değerlerini kullanarak ve XmlQueryByVIDnPID metodunu çağırarak, yine bir LINQ sorgusuyla usbdev.xml içinden aygıt adını çekebiliriz. Daha sonra, ListView öğelerini tanımlayarak bunları ListView kontrolüne ekleriz.

Görüldüğü gibi USB aygıtlarımızla ilgili üretici ve ürün kodlarını, adlarını ekrana yazdırdık. Makalemin sizlere yararlı olması dileklerimle, hoşçakalın...
 

Users Who Are Viewing This Konu (Users: 0, Guests: 1)

Üst