Python’da Harici Kütüphane Kullanma

0 Yorum, 30 Mart 2018 18:39, by Cenk Tuna, in

Bu yazıda C/C++ ile yazılmış harici bir kütüphanenin Python’da ctypes modülü ile nasıl kullanıldığını ele alacağız.

Python’da C/C++ ile yazılmış dll'lerin fonksiyonlarına erişmeyi sağlayan built-in bir modül. Ctypes, C’nin veri tiplerini destekleyerek Windows platformunda DLL, Linux’ta SO denilen hazır binary kütüphanelerden fonksiyon çağırmaya yarar.

İşletim sistemi API’leri kullanmak  ya da donanım üzerinde doğrudan yürütülecek scriptler yani düşük seviye programlama yapmak gerektiğinde ihtiyaç duyulacaktır.

Başlamadan önce; Python ve C  / C++ dillerinin her ikisinin de temellerine hâkim olmanız gerekmektedir.

Kullanımı aslında oldukça basit. Fakat hafıza yönetimi, adresleme ve farklı veri tiplerinin baytlar seviyesinde ele alınmaları gerektiğinden hata ayıklama biraz karmaşıklaşıyor. Bilhassa iç içe(nested) class, structure ve array’ler söz konusu olunca kendinizi epey derinlerde bulabiliyorsunuz.

Aşağıdaki kaynaklar kütüphaneyi enine boyuna ele almışlar. Bunlarla birlikte çeşitli kaynaklarda birçok soru cevap da var. Önemli olan karşılaşılan problemi tanımlamak ve çözümünü ortaya koymaktır diye düşünüyorum.

https://docs.python.org/2/library/ctypes.html

https://dbader.org/blog/python-ctypes-tutorial-part-2

http://fatihmertdogancan.com/tag/python-ctypes-nedir/

http://www.keil.com/support/man/docs/armcc/armcc_chr1360775115791.htm

Aşağıda, .h kaynak dosyasındaki veri yapısı ve fonksiyon tanımlamaları var.

typedef struct {
	BYTE	abyIpAddress[4];	
	WORD	wPortNo;			
	BYTE	reserve[2];			
} ETHERNET_CONFIG;

typedef struct {
	BYTE byDataInfo;		
	BYTE byJudge;			
	BYTE byTimZero;		
	FLOAT fValue;			
} MEASURE_VALUE;

typedef struct {
	BYTE byYear;							
	BYTE byMonth;							
	BYTE byDay;							
	BYTE byHour;							
	BYTE byMinute;						
	BYTE bySecond;						
	BYTE byMillsecond;						
	DWORD dwPulseCnt;						
	BYTE byTotalJudge;						
	MEASURE_VALUE stMesureValue[16];	
} MEASURE_DATA;

DWORD GetVersion(void);
LONG GetMeasurementValue(MEASURE_DATA* pstMeasureData);
LONG EthernetOpen(ETHERNET_CONFIG* pstEthernetConfig);

C/C++’da yer alan veri tipi kavramının Python’da olmamasından dolayı öncelikle C kütüphanesinin kullanabileceği hafıza bloklarının oluşturulmasına ihtiyacımız var. Tip dönüşüm tablosundan yararlanarak C/C++’da tanımlanan tiplere karşılık olarak Python’da aşağıdaki sınıflar oluşturulur. Structure ve dizilerin oluşturulmasına dikkat edildiğinde ne yapılmaya çalışıldığı açıkça görülmektedir.

Bu noktada bilinmesi gereken önemli noktalardan birisi bellekteki adresleme mantığıyla ilgili “pragma pack (_pack_)” kavramıdır. C’de Structure ve Union alanları aynı hizadadır. Yukarıdaki resimde görüldüğü gibi 1 byte’lık char ve 2 byte’lık short, doldurularak(padding) 4 byte’lık integer ile hizalanıyor. Yani #pragma pack(4) olarak ayarlanmış ve bellekte 4’ün katları olarak adreslere yerleşmiş olduğu anlaşılıyor. Ancak 7 byte olması beklenen yapı ise bellekte 12 byte’lık bir alan kaplıyor.

>>>ctypes.sizeof(ex1)
>>>12

Hizalama yanlış ayarlanırsa derleyici, adresin düşük sıralı bitlerini sessizce yok sayar ve yanlış belleğe erişmesine neden olur. Dolayısıyla hatalı sonuç elde edilir.

Ctypes varsayılan olarak C derleyicisinin ayarlarını kullanır. Kullanılan en geniş alana göre bir tam sayı ile ayarlanabilir.

Son olarak, yukarıdaki veri yapısına göre yazdığımız python kodu aşağıdaki gibi olur:

from ctypes import *
class ETHERNET_CONFIG(Structure):
    #_pack_ = 1
    _fields_ = [
        ("abyIpAddress", c_ubyte*4),
        ("wPortNo", c_ushort),
        ("reserve", c_ubyte*2)
    ]
class MEASURE_VALUE(Structure):
    _fields_ = [
        ("byDataInfo", c_ubyte),
        ("byJudge", c_ubyte),
        ("byTimZero", c_ushort),
        ("fValue", c_float)
    ]

class MEASURE_DATA(Structure):
    _fields_ = [
        ("byYear", c_ubyte),
        ("byMonth", c_ubyte),
        ("byDay", c_ubyte),
        ("byHour", c_ubyte),
        ("byMinute", c_ubyte),
        ("bySecond", c_ubyte),
        ("byMillsecond", c_ubyte),
        ("dwPulseCnt", c_int),
        ("byTotalJudge", c_ubyte),
        ("stMesureValue", MEASURE_VALUE*16)
    ]

IP_ADDRESS = [169,254,0,4]
PORT_NUMBER = 50000
RESERVE = [0,0]

lib = windll.LoadLibrary(r"C:\Projects\tsm\C_IF.dll")

lib.GetVersion()

config = ETHERNET_CONFIG(
            abyIpAddress = (c_ubyte * 4)(*IP_ADDRESS),
            wPortNo = (c_ushort)(PORT_NUMBER),
            reserve = (c_ubyte*2)(*RESERVE)
        )

lib.EthernetOpen (byref(config))

measureData = MEASURE_DATA()

lib.GetMeasurementValue(byref(measureData))

Verileri dönüştürüp harici kütüphaneyi yükledikten sonra uygun şekilde metotların kullanıldığı görülüyor. "byref” ile değişkenin adresini parametre olarak göndermiş oluyoruz.

Yazar Hakkında

0 Yorum

Yorumunuzu Paylaşırsanız seviniriz.

Girdiğiniz e-posta adresi kimseyle paylaşılmaz. Sadece kimlik olarak kullanılır.