Stage-c
Görevler:
- Kontrol sisteminin belirlenmesi
- Gerekli bildirimlerin ve tanımlamaların yapılması
- Kontrol fonksiyonlarının prototiplerinin oluşturulması
- Kontrol fonksiyonlarının tanımlanması
Oluşturduğumuz nesneleri kontrol etme vakti geldi. Nesne tabanlı programlama yapabilmek için struct’lar ile oluşturduğumuz tüm nesneler işimizi kolaylaştıracak.
Uygulamamızın temel isteklerine tekrardan bakacak olursak eğer bizden index numarası varsa sub-index numarası vererek okuma ve yazma talebinde bulunacak bir girişimi yönetmekti.
Temel olarak iki işlevi (read, write) var gibi görünen bu sistemi kontrollü ve güvenli bir hale getirebilmek için inşaa etmemiz gereken bir takım farklı şeyler karşımıza çıkacaktır. Bunları tek tek ele almamız gerekmektedir.
Temel olarak ikiye ayırdığımız; read ve write bileşenlerinden oluşan sistemimizin içerisinde kullanılacak ortak kod içerikleri elbet olacaktır. Index ve sub-index objelerine ulaşmak gibi. Bu ortak kod içeriklerini her iki bileşen için ayrı ayrı yazmak yerine fonksiyonel hale getirmek sistemi daha modüler hale getirecektir. Bu ve bunun gibi diğer fonksiyonlar içerideki temel bileşenleri etkilediğinden ve kendi bulundukları kaynak dosyaları (.c file) dışında kullanılamayacağı için başlık dosyasına bu fonksiyonları bildirmeye gerek yoktur.
Bir nevi public ve private öğeler gibi düşünebiliriz.
O halde bu yardımcı fonksiyonların static tanımlı olacağı akla gelmiştir.
Tüm fonksiyonların görevini yerine getiriyor olması iyi bir sistem tasarladığımızı göstermez. Bu fonksiyonların beklenmedik parametreler karşısında herhangi bir işlem yapmasını istemeyiz. Örnek verecek olursak eğer read ve write fonksiyonlarına tabloda olmayan bir index numarası girişi yapıldığı zaman bunu işlememesi ve geri dönüşü olarak bir hata kodu dönmesini gerekmektedir. Bu hata kodları da işlenebilir hale getirilebilir. Sistemin log alma özelliği varsa oraya yönlendirilebilir ya da kullanılan haberleşme protokolünde mutlaka bu hata kodları bir anlam ifade eder ve bu datayı talep eden birime bu hata kodu geri döndürülür.
O halde temel bileşen fonksiyonlarının tamamı ve neredeyse bütün yardımcı fonksiyonlar geri dönüş değeri olarak hata kodu döner. Bu hata kodlarını yine enum ile tanımlamak da yine kodun ve sistemin anlaşılır olmasına elverişli kılar.
Fonksiyonlar geri dönüş değeri olarak hata tanımlamalarını dönecek ise geri dönüş değeri olarak data döndürmesini beklediğimiz fonksiyonlar bu datayı bize nasıl ulaştıracak? İşte, tam burada kalbine girmiş oluyoruz nesne tabanlı programlamanın. Oluşturduğumuz her temel ve yardımcı fonksiyon işlem yapılacak nesneyi parametre olarak kabul etmelidir. Bu parametre referans olarak ya da pointer olarak gönderilmelidir. Bu sayede oluşturduğumuz her nesneyi ilgili fonksiyonlara parametre olarak göndererek işlem yaptırabiliriz. Fonksiyonlar ise sonuçları ya da bildirmesi gereken tüm değerleri kendisine referans ya da pointer olarak gönderilen parametrelere yükler. Böylece fonksiyonlar sabit bir nesneye hizmet etmemiş olur.
Sisteme üstten bakan, temel nesnelerin adreslerini kendisinde tutan ve kullanıcı tabanlı değerleri değişken olarak bulunduran; uygulama katmanında (application layer) kullanılacak kontrol struct tanımlaması yapılması gerekmektedir.
Şimdi bu tanımlamaları yapmaya başlayalım.
Tanımlamaları object_control modülü içerisinde yapacağız. Bu modülün source kod ve header dosyasını (object_control.c/h) açtıktan sonra buraya hangi bildirimleri ve tanımlamaları yapacağımıza göz atalım.
1. Hata Listesi (Enum)
Hata dönüş listesini oluşturarak başlayabiliriz. Hata listemizi enum içerisinde listeleyeceğiz. Bir sistemi geliştirirken ne tarz hatalar elde edebileceğimizi belirlemiş olmamız gerekmektedir. Çoğu zaman bu hata listesinin bir kısmını kullanılan standartlar (haberleşme protokolleri vs.) sunmaktadır. Ancak yine de sistemi geliştirmeye başladığımızda mutlaka daha önce tespit edemediğimiz hata durumları karşımıza çıkacaktır. Bu sebepten dolayı hata listemiz sistemi kurgulamaya başladığımız gibi kalmayacak genişleyecektir. İlk başta sistemimizin temel özelliklerine göre hata listemiz aşağıdaki gibi oluşturulmalıdır.
Hata kodları da daha önce bahsettiğimiz 2^n sistemi ile numaralandırılıyor. Hatalı girişler için ve yetkisiz talepler için temel hata kodları eklenmiştir. Bu liste genişleyecektir.
Hata kodları belirtilirken hata olmaması durumu her zaman 0 değerinde tutulur. Bu kod tarafında bir hatanın olup olmadığının tespitini daha da kolaylaştırır ve hata kodu dönüşü olan bir fonksiyonu doğrudan bir şart bloğu içerisinde kullanma olanağı sağlar.
if( foo() ) {…} else {…} gibi.
2. Obje Argümanları Struct Referansı
Kullanıcı tarafından gelecek olan read/write talepleri doğrultusunda obje listesi içerisindeki bir obje ile çalışacağız. Bu objenin tüm içeriklerini kendi çalışma alanımızda toplayıp bunun yanında bu objeyi kontrol edebilmek için gerekli tüm bileşenleri içeren bir struct oluşturmak kontrolü daha kolaylaştıracaktır. Bir ressamın kullanacağı renkleri paletine alması gibi bu struct palet görevi görecektir.
Bu struct’ın elemanları:
Struct elemanları içerisindeki parametrelerden index, sub-index, attr ve lenght bildiğimiz değerlerdir. Burada index ve sub-index kullanıcıdan gelecektir. Diğer parametreleri bizim bulup bu struct içerisine kopyalamanız gerekmektedir.
data adındaki void pointer objemizin adresidir. Bu da bildiğimiz değerlerden biridir.
databuffer array pointer’ı okuduğumuz datanın değerini kopyalacağımız ya da yazma talebi üzerine objenin yeni değeri tutan array pointer’ı olarak hizmet edecektir. İşaretini tutacağı array bir üst katmanda tanımlanmış olmalıdır.
i_element değeri index ve sub-index bilgisi bilinen objenin object dizisi içerisinde kaçıncı indiste olduğunu tutacak elemandır.
3. Object Control Struct Referansı
Obje kontrol sisteminin uygulama katmanındaki her bileşenini içerisinde barındıran kontrol struct referansı uygulamanın ilk bölümünün son ve major kontrol sistemidir.
Obje hiyerarşisi aşağıdaki gibi olmalıdır:
Bu struct uygulamanın bu bölümünde (haberleşme protokolü olmadan) uygulama seviyesinde (application layer) katmanında kullanılacaktır. Daha sonra haberleşme protokolü eklendiği zaman bu struct haberleşme protokolünün uygulama seviyesindeki struct’ı içerisinde yer alacaktır. Struct elemanlarına göz atacak olursak eğer:
- Tüm index objelerinin olarak dizi haline getirildiği object index işaretlendirilmesi için pointer bulunuyor.
- nof_index değişkeni object index dizisinin eleman sayısını (indis sayısını) tutacaktır. Bu eleman sayısı sizeof() fonksiyonu ile de hesaplanabilir ancak eleman sayısını makro olarak obj_control.h dosyasında tanımlayacağız.
- databuffer dizisi object_read talebinde objenin değerini; yazma talebinde ise objeye yazılacak değeri tutan dizidir. Eleman sayısı maksimum değişken baz alınarak belirlenmiştir. Objelerin length değeri bilindiğinden dolayı maksimum olarak belirlenen eleman sayısı sorun yaratmayacaktır.
- obj_arg structı üzerinde çalışılacak objenin gerekli tüm bileşenlerini içeren yapıdır.
Referansları belirledikten sonra sistemi inşaa etmeye geçebiliriz. Buna aşağıdaki akışı incelemekle başlayabiliriz.
Sistemin kurgusu için yukarıdaki akış referans olacaktır. Kullanıcının (master) kullanım senaryosu (use case) şimdilik write ve read sorgularından ulaşıyor. Bir objenin datasının değerini okumak ya da değiştirmek için gelen talepleri kontrollü şekilde gerçekleşebilmesi için tanımladığımız referansları (struct’lar, enums, vs.) kullanacağız.
Akışa göre Master’dan gelen talep için öncelikle Object Control modülünde Master’ın kendine referans belirlediği Index Objects Struct Array Object control içerisinde işaretlenmiş şekilde görülmektedir. Object Control struct içerisinde bu yer almaktadır. Master’ın talep gerçekleşirken parametre olarak verdiği index ve sub-index değeri Object Control ünitesine aktarılıyor. Bu parametreler object argüman struct’ı içerisine kopyalanmalıdır. Object Arg. struct’ı içerisindeki diğer parametrelerin belirlenebilmesi bazı işlemlerin yapılabilmesi gerekmektedir. Akışta da görüleceği gibi Object Argument Struct elemanlarının belirlenmesi için olan kısım kesik çizgilerle belirtilmiştir. Bu işlevi yerine getiren fonksiyon ya da fonksiyonlar sadece object control modülüne aittir. Yardımcı fonksiyonlar kategorisinde değerlendirilebilir.
Tüm fonksiyonlarımız da nesnenin NULL parametre olup olmadığı kontrol edilmelidir. NULL olan parametre ile işlem yapmak ölümcül sonuçlar doğurabilir. Bu düsturu tüm projeler için edinmek gerekmektedir.
Önce read/write fonksiyonu daha sonra da akıştaki sırayla fonksiyonlar:
1. Object Read Function:
Kullanıcı (master) tarafından erişilebilen obje okuma fonksiyonudur.
Bu fonksiyona master, object control modülünü işaretler ve index, subindex parametrelerini gönderir. Fonksiyon içerisinde objenin okunma işlemi yapılması için temel hazırlıklar yapılmaktadır. Akışta da görüleceği gibi object control içerisindeki argüman struct doldurulabilmesi için initWriteRead statik fonksiyonu çağrılıyor. Daha sonra okuma işlemi yapabilmek için object arg. struct’ının databuffer, data ve lenght değerleri geçici olarak kopyalanıyor ve objenin okunabilir bir obje olup olmadığı kontrol ediliyor. Bu aşamadan sonra lenght değeri kadar data kopyalanıyor.
2. Object Write Function
Kullanıcı (master) tarafından erişilebilen obje yazma fonksiyonudur. Object Control struct’ı içerisindeki databuffer dizisine objeye yazılması istenen değer kullanıcı tarafından giriliyor.
Bu fonksiyon, read fonksiyonu ile benzerlik göstermektedir. Yazma işleminin fonksiyonun okuma işlemi fonksiyonu ile benzerlik göstermesinin en büyük sebebi modüler şekilde sistemi geliştirmiş olmamızdır.
Read fonksiyonundan farklı olarak sadece objenin yazılabilir olup olmadığı kontrol ediliyor ve read fonksiyonundaki data kopyalamanın aksine objeden data-buffer’a değil de databuffer’dan objeye kopyalanma gerçekleşiyor.
Object Control modülü içerisindeki statik fonksiyonlara geçebiliriz:
1. InitWriteRead Fonksiyonu
Read ve Write fonksiyonlarında ortak kullanılan ve argüman struct elemanlarının doldurulduğu fonksiyondur.
Bu fonksiyonda index ve sub-index değerleri arg. struct’ına yazılıyor. Daha sonra akıştaki sırayla diğer fonksiyonlar çağrılıyor. Bu fonksiyonda objenin gerekli bileşenleri arg. dizisine kopyaladıktan sonra control struct’ı içerisindeki databuffer dizisinin adresi de arg. struct’ı içerisinde databuffer pointer’ına işaretlendiriliyor. Bu kısımdan object_arg struct’ını anlatırken bahsetmiştik.
2. Find Object Fonksiyonu:
Bu fonksiyon index değerinin index objects dizisinin hangi indisinde olduğunu hesaplar.
Yukarıdaki kod detaylı incelendiği zaman en başından beri oluşturduğumuz struct’ların işlem yaparken/kod yazarken ne kadar fayda sağladığı görülecektir.
Kullanıcı tarafından verilen index parametresi initWriteRead statik fonksiyonunda obj_arg. struct’ı içerisine kopyalanmıştır. Bu değer ile object_index struct dizisi içerisinde bu index değerine denk gelen indisi object_arg. içerisindeki indis değerine kopyalanıyor. Sonraki tüm işlemlerde bu indis numarası üzerinden doğrudan objeye erişme gerçekleşecektir.
3. Get Object Address Fonksiyonu
Index ve sub-index değeri bilinen objenin adresinin elde edileceği fonksiyondur. Adres içeride arg. struct’ı içerisinde point’lenecektir.
Indis numarası artık bilinen objenin object_index dizisi içerisinden Obj_index_t tipinde bir pointer’a adresi işaretleniyor. (object)
Bu adresi kullanarak objenin index objesi mi yoksa sub-index içerikli bir obje olduğu kontrol ediliyor.
Burada dikkati eğer obje sub-index’li bir objeyse kısmına vermek gerekiyor. Eğer indisteki elemanın data pointer’ı sub_index struct adresine eşitlenecekse type_casting yaparak erişmemiz gerekiyor. Object dizimizi oluştururken bu kısımlardan bolca bahsetmiştik. Bu fonksiyonun her aşamasının detaylı incelenmesinde çok fayda var. Bu projenin en önemli kısmı burasıdır. Hemen hemen her detayı içermektedir. Özellikle bu fonksiyonun içeriğini geçiçi parametreler vererek tetikleyin ve Debug ekranında type_cast yaptığınız içeriklere erişiniz.
Diğer tüm fonksiyonlarımız bu temadadır. Akıştan görülecek diğer fonksiyonları kendiniz yazarsanız eğer projeyi tamamen özümsemiş olursunuz.
Temel ve yardımcı fonksiyonlarımızı belirleyip yazdık. Artık küçük eklemeler yapıp test etme zamanı geldi. Final bölümünde görüşmek üzere!
Bu kısma kadar olan kodların olduğu github repo linki:
https://github.com/mekzum/40ambar/tree/master/Section-1/stage-c