Windows



Bu derste Windows programlamaya ait bazı temelleri öğreneceğiz.

Giriş:
Windows, sistemdeki programlara, kendinin de kullandığı, sistem fonksiyonları (API) , kullanıcı arayüzleri(GUI, resource) ve nesnelerin(Object)  kullanımına izin vermiştir.

API:
Windows sistem fonksiyonları kernel32.dll,user32.dll, gdi32.dll vb gibi çeşitli dll'lerde olup , programlarımızdan bunlara dinamik olarak bağlanırız yani bu apilerin kodları programımıza gömülmez(statik linkleme). Onun yerine ilgili apilerin isimleri ve bulundukları dll bilgileri gömülür. Bu gömülecek bilgi ilgili apinin bulunduğu dll ile aynı isimde olan lib uzantılı dosyadan alınır. Bu yüzden kodlarımızda(veya proje ayarlarımızda ) bu libraryleri belirtmeliyiz. Bunu şu satırla yaparız :

#pragma comment(lib,"user32.lib") //user32.dll'den apiler kullandık, ilgili bilgileri kodlarımıza göm.
#pragma comment(lib,"kernel32.lib") // kernel32.dll'den apiler kullandık, ilgili bilgileri kodlarımıza göm.

Tabi aslında biz bir proje oluşturduğumuzda IDE(program yazıp geliştirmeyi kolaylaştıran arayüz) bu  temel dll'ler için ilgili bildirimleri yerimize yapar. Yapmadığı bildirimleri yukardaki gibi biz kendimiz yapacağız. Mesela OpenGL ile ilgili bildirimler:

#pragma comment(lib,"opengl32.lib") //opengl32.dll'den apiler kullandık, ilgili bilgileri kodlarımıza göm.
#pragma comment(lib,"glu32.lib") // glu32.dll'den apiler kullandık, ilgili bilgileri kodlarımıza göm.


Yine Windows apileri için sistem fonksiyon,yapı ve sabit  tanımlamalarının olduğu windows.h dosyasını dahil etmeyi unutmamalıyız.  Şöyle:

#include <windows.h> 

Sonraki derslerde OpenGL fonksiyon,yapı ve sabitlerini kullanmaya başladığımızda onun header dosyalarını ekleyeceğiz:

#include <gl/gl.h.h> 
#include <gl/glu.h.h>  
Header(tanımlama) dosyası nedir , ne içindir diye burada bahsetmeyeceğiz, bunu bilmiyorsan ilkin c/c++ dilini öğrenmelisin.

Resource:
Resourceler , Pencereler(DialogBoxlar),kontroller(düğme,metin kutusu vb.),ikon,kursör,bitmap,menü,toolbar, string tablosu,versiyon bilgisi,kısayol tuş tablosu vs... oluşmaktadır. Bunlar *.rc uzantılı script dosyalarında yazılıp(hazırlanıp) resource.h isimli dosyada programımızdan onlara başvuru tanımlamaları yapılır(bu süreçteki her şey resource editörü ile görsel olarak tasarlanıp ilgili çalışma otomatik yansıtılır). Tabi yine kodlarımıza resource.h dosyasını include etmeyi unutmamalıyız.Şöyle:

#include "resource.h" 

Nesneler:
Nesneler, ilgili API fonksiyonları(SelectObject vb) ile elde edilip kullanılan, Pen(Kalem),Brush(Fırça),Font(YazıTipi) vb 'den oluşmaktadır.

IDE ve YARDIM:
Ben kodlarımı yazmak için Microsoft Visual C++ 6.0 IDE'sini(express sürümlerini de çok az farklıda olsa kullanabilirsin hatta illerki derslerde buna ihtiyacımız bile olacak, en son header ve lib dosyaları için) ve  yardım için MSDN Library Visual Studio 6.0(Artık MSDN internette de var,ister bilgisayarına indirir ister internetten takip edersin) kullanıyorum. Ben bir fonksiyondan,mesajdan,yapıdan bahsettiğim zaman hemen sizinde MSDN'den ilgili konuyu bulup, ayrıntısını ve bağlı konuları buradan takip etmeniz şiddetle tavsiye edilir. Buradaki ingilizce sade ve google translator ile çevrilebilecek düzeyde. 

Bir Windows Programı Yazma:
Visual C++ 6.0'da bir Windows programı geliştirmek için ilkin yeni bir proje(File->New->Projects) başlatın ve projenize bir ad verin(project name) mesela windows penceresi ve proje türünü seçip("Win32 Application") OK’e tıklayın .

Ve yeni gelen pencerede "An empty project"  seçeneğini seçip Finish’e tıklayın(boş bir proje oluşturuyoruz , onu dolduracağız merak etmeyin). 

Böylece ekranın solunda  bir hiyerarşide bir çalışma alanı(workspace) oluşmuştur. "Source Files" altında kodlarımızı(*.cpp), "Header Files" altında tanımlama dosyalarımızı(*.h) ve "Resource Files" altında resource script dosyalarımızı(*.rc) takip edebiliriz. Şuan projemize dahil hiç bir kod, tanımlama dosyası ve resource yok. 



Şimdi onları ekleme zamanı: "Source Files"’e sağ tıklayıp "Add Files to Folder" seçilip dosya adı olarak kod.cpp olarak yazılıp kod dosyamızı ekleriz. "Resource Files" ’e sağ tıklayıp "Add Files to Folder" seçilip dosya adı resorce.rc olarak yazıp resource script(penceremizi ve üzerindeki kontrolleri göresel olarak hazırlayacağımız dosya) dosyamızı projeye ekleriz. "Header Files" ’e sağ tıklayıp "Add Files to Folder" seçilip dosya adı resorce.h olarak yazılıp kod dosyamızın resourcelere ulaşabilmesi(başvurabilmesi) için resource tanımlama dosyasını ekleriz. Artık projemiz var , projeye dahil ilgili dosyalar var ama dosyalar boş. Şimdi onları doldurma zamanı: ilk önce , Resource.rc : Bu dosya görsel olarak resourcelerimizi oluştuğumuz dosyadır. Projemize bir pencere(dialogbox) eklemek için sağda resource.rc’e sağ tıklayıp "ınsert" seçeneğini seçelim.

Ve açılan pencereden "Insert Resource" penceresinden Dialog ‘u seçip "New" ‘e tıklayalım.


Ve işte penceremiz karşımızda, "Controls" panelinden penceremiz üzerine istediğimiz kontrolü(düğme(button), metin kutusu(edit),liste kutusu (listbox), vs) ekleyebiliriz ama şu an eklemeyeceğiz(var olanlarıda silin).  Penceremizin(ve kontrollerimizin) özellikleri görmek ve değiştirmek için onun üzerinde sağ tıklayıp "properties" seçeneğini seçeriz. Şimdi bu penceremiz üzerinde sağ tıklayıp "properties"'i seçelim:

Açılan pencereden penceremiz özelliklerini görüp değiştirelim. "General" sekmesi altında "Caption" bölümü bu kontrolün(pencerenin) üzerinde yazacak yazı yani başlıktır oraya mesala Windows penceresi yazalım. Bu kısımmdaki en önemli yer "ID" kısmıdır. Bu bizim kodlarımızdan(kod.cpp) bu pencereye(resourcelere) başvurmamızı sağlayan bir tanıtıcıdır(bizim set ettiğimiz tanıtıcıya ID, sistemin set ettiği tanıtıcıya handle denir).  Bu dialog penceresi için IDD_DIALOG1 ismini otomatik verilmiştir istersek bunu değiştirebiliriz ki mesela ANAPENCERE gibi(hangi ID'i verirsek verelim bu akılda kalıcı ve kolay olmalı) .

Properties altındaki diğer tablara bakalım şimdi. "Styles" tabı altında bu resourcenin stillerini görüp değiştirebiliriz. "More Styles" tabı altında ekstra stillerini görüp değiştirebiliriz. Aşağıda gördüğünüz değişiklikleri sizde yapın. Yaptığınız değişikliklerin resource.h dosyasına otomatik yansıması için ana menüden "Save all" seçeneği ile çalışmanızı kaydetmeyi sakın unutmayın.


Şimdi geldik Resource.h: Bu dosyada aslında bizim yaptığımız hiçbir şey yok resource.rc’de çalışma sonunda "Save all" seçeneğini seçersek kodlarımızdan onlara başvuru tanımlamaları otomatik yazılacak bu dosyaya. 


Şimdi geldik işin gerçekten yapıldığı yere kodlara(kod.cpp):  İlkin bu bir Windows programı olduğu için ve sistem fonksiyonları(API) kullanılacağı için bu sistem fonksiyonların  deklare edildiği , yapıların tanımlandığı, Sabitlerin belirtildiği tanımlama dosyası olan Windows.h ‘ı kodlaramıza dahil etmemiz(include) gerekir bunu şöyle yapıyoruz(kod.cpp’ın en üstünde):

#include <Windows.h>

Tabi bu program için resourceler kullandığımız için resource.h dosyasınında include edilmesi gerekir, yani şöyle:

#include <Windows.h>
#include "resource.h"

Sonra kodlamaya geçeriz. Peki size bir soru: Kod sayfasında kodlar işletilmeye nerden başlar? Başından mı, sonundan mı veya ortasından mı yani hangi prosedürden başlayacak? Cevap WinMain prosedürüdür.  WinMain entrypoint(giriş noktası) fonksiyonudur. Yani sistemin(Windows'un) kodlarımızdan İLK çağırdığı fonksiyondur(Aslında böyle değildir , nasıl olduğunu birazdan açıklayacağım).

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
return 0;
}

WinMain bir callback(geriçağırma gibi tercüme edilebilsede çağrı artığı demek daha uygun gibi) fonksiyonudur yani bir işlemi gerçekleştirme bizim dışımızdan(bu başka bir modülde dll'de olabileceği gibi bizim kodlarımıza gömülmüşde olabilir) başlanıp bu bizim callback fonksiyonuyla devam ettirilip gerekliyse bizim dışımızdakince tamamlanmasıdır. WinMain kodlarımızda giriş noktası değil o zaman. Evet değil. C derleyici bizim kodlarımıza kendi giriş fonksiyonu gömer ki burada ilgili ilklemeleri yapar mesela kendi dll'leri yükler sonra kontrolü bizim WinMain callback prosedürüne bırakır ve bu prosedürden return ile döndüğümüzde kendi kapanış kodlarını işletir ki burada büyük ihtimal yüklediği dll'leri bellekten kaldırmak ve ExitProcess fonksiyonu ile bizim programımızı sonlamak vardır. WinMain prosedürünün parametreleri ve dönüş değeri şuan için çok önemli değildir. 4 parametre alan ve int tipli bir değer döndüren bir callback prosedürüdür demek yeterli. Bu prosedürde biz penceremizi çağırıp gösterticeğiz bunun için şu API fonksiyonunu kullanacağız.

int DialogBoxParam(
  HINSTANCE hInstance,  // programımız handleyi(sistemin atadığı T.C. no)
  LPCTSTR lpTemplateName,  //resourcede pencere için atadığımız tanıtıcı no ID 
  HWND hWndParent,      // ana pencerenin handleyi(bu pencereyi çağıran )
  DLGPROC lpDialogFunc, // sistem mesajlarının işleneceği prosedür
  LPARAM dwInitParam    // sistem mesajlarının işleyecek prosedüre(DialogProc) geçirilen bilgi;


Kodlarımız şöyle olacak:

#include <windows.h>
#include "resource.h"

BOOL CALLBACK DialogProc( HWND hwndDlg,UINT uMsg,WPARAM wParam, LPARAM lParam)
{
            return 0;
}

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
            DialogBoxParam(hInstance,(LPCSTR)ANAPENCERE,0,&DialogProc,0);
            return 0;
}
 Penceremizi göstermek için WinMain prosedürümüzde DialogBoxParam API sini kullandık.
Ilk parametresi programımıza(modül) sistem tarafından atanan tanıtıcı nosu(handle). İkinci parametre penceremizin resource scriptindeki bizim atadığımız tanıtıcı no(ID), üçüncü parametre bu pencerenin ana penceresinin handleyi ki herhangi ana pencere yok biz ana pencereyiz o yüzden bu parametre sıfır. Dördüncü parametre sistemde penceremiz için olan mesajları işleyecek DialogProc prosedürünün adresi ve son parametre bu prosedürü geçmek istediğimiz ek bilgidir ve herhangi ek bilgi geçirmek istemediğimize oda sıfır. Burada DialogProc prosedürünü biraz açıklayalım:  Windows , bir anda birden fazla programın çalıştığı multithread bir işletim sistemidir. Aslında bir anda birden fazla program çalışmaz. Windows her programa bir zaman dilimi verir çalışmak için ve bu zaman dilimi dolduğunda diğer programa geçirir işletme hakkını. İşletme hakkı diğer pencerelerdeyken ilgili dışsal müdahalelerden(fare,klavye vb) haberdar olmamız için Windows ilgili müdahaleleri mesaj kuyruğu adı verilen bir yerde saklar tekrar işletme hakkı bize geldiğinde, Windows, mesajlarının işleneceği bir callback prosedürüne bunları yollar. Bu callback DialogProc'dur. DialogProc'un ilk parametresi penceremizin handleyi(Sistem tarafından penceremize atanmış tanıtıcı no), ikinci parametre bize olan mesaj, üç ve dördüncü parametre bu mesajla ilgili ek bilgilerdir. Şimdi DialogProc'un içini dolduralım, Kodlarımızın son hali:


#include <windows.h>
#include "resource.h"

BOOL CALLBACK DialogProc(HWND hwndDlg,  UINT uMsg,  WPARAM wParam,LPARAM lParam )
{
    switch (uMsg)
    {
          case WM_CLOSE:
                      EndDialog(hwndDlg,0);
                      break;
           default:
                      return FALSE;
       }

return TRUE;
}

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
       DialogBoxParam(hInstance,(LPCSTR)ANAPENCERE,0,&DialogProc,0);
return 0;
}

DialogProc içinde sistemin bize yolladığı(bizim için aldığı) mesajları işlemek için bir uMsg için bir switch döngüsü(Çünkü mesajlar onda) kurarız. Ve gelen mesajlardan birinin WM_CLOSE mesajı olup olmadığı kontrol ederiz eğer öyleyse EndDialog API fonksiyonu ile pencereyi kapatıp sonlarız. WM_CLOSE mesajı, kullanıcı, penceremizden x sembolüyle penceremizi kapatmak istediğinde bunu bize bildirmek için sistemce bize yollanan mesajtır. Bizde ilk parametresi kapatılacak pencerenin handleyi olan EndDialog API çağrısıyla penceremizi kapatıp programı sonlarız. Şuan WM_CLOSE dışında herhangi mesaj ilgimizi çekmiyor switch döngüsü içinde default anahtar kelimesi altında sisteme FALSE döndürerek diğer mesajların sistemce cevaplanmasını sağlarız(çünkü program çalıştığı sürece başkaca yüzlerce mesaj alır , bizim onları işlemek istememiz onları işlenmesinin zorunlu olmadığı anlamına gelmez , sistemin varsayılan yanıtlarla bu mesajların yanıtlanması gerekir en azından). Döngü sonunda TRUE döndürerek herşeyin yolunda olduğunu bildiririz sisteme.

Burada sadece kullanıcı kapatma ikonuna(x) bastığında penceremize gönderilen WM_CLOSE mesajından bahsettik. Oysa penceremize başka vesilelerle yollanan birçok mesaj var. Bunları MSDN'den window messages başlığı altında görebiliriz. Mesela kullanıcı penceremizin konumunu değiştirdiğinde gelen mesaj WM_MOVE, penceremizin boyutu değiştirildiğinde gelen mesaj WM_SIZE, penceremiz yaratıldığında gelen mesaj WM_CREATE, penceremiz gösterildiğinde gelen mesaj WM_SHOWWINDOW vs. Bu mesajlar pencere prosedürümüze(DialogProc) uMsg ile gelirken yukardada bahsedildiği gibi wParam ve lParam'da bu mesajla ilgili ek bilgilerle gelir. Mesala WM_SIZE mesajı geldiğinde wParam penceremizin maksimize veya minimize edilip edilmediği hakkında bilgiyi içerirken , lParam penceremizin yeni oluşan genişlik ve yüksekliğini içerir. Yine Mesela WM_MOVE mesajı geldiğinde lParam penceremizin masaüstünde yeni oluşan x ve y pozisyonlarını içerir. Diğer mesajlar ve ayrıntılar için lütfen MSDN'e başvurun.

Programımız derleyip işletilebilir dosyaya(exe) çevirmek için F7'e basın(veya Ana menüden Build->Build ...), programı çalıştırmak için F5'e basın(veya Ana menüden Build->Execute..).

Hepsi bu , sadece iki fonksiyon.
Şuana  kadar olan basit penceremizin örnek kodlarını yüklemek için tıklayın.

Windows'da OpenGL desteği


Sistemdeki tüm fiziki donanımlar(yazıcı ekran vb gibi) Windows'un(işletim sistemi) kontrolü altındadır. Kontroldeki başarısıyla güvenlik problemleri arasında ters bağıntı vardır. Bu kontrol ne kadar sıkıysa o kadar yüksek sistem güvenliği demektir ama bir o derece donanınım kabiliyetlerini aksettirelememesi demektir. Microsoft, sistemin sahibi olduğu kadar donanım imalatçısıda olsa sorun yok, ama değil. Bu yüzden bir çıkar çatışması ihtimalide var. Bunun için belli şartlarda katılık ve derecesinde çok ısrarcı olamaz.

Donanım en etkin kullanıldığı-yönetildiği-aksettirildiği arayüzü ya kendi oluşturacak(gdi-directx) yada yapanlara izin verecek.
Windows veya diğerleri bu gibi durumlar için bu izni; "içten koruduğu" sistem güvenliği ve diğer sebebler dolayısıyla "bazısı değiştirilemez hatta ulaşılamaz" bir "VERİ YAPISINI" , "bazısı değiştirilebilir varsayılan değerlerle" diğerlerin kullanımına açararak verir. Bu veri yapısına genel olarak "CONTEXT" denir.

Windows'un ekran ve kartlar için olan bu veri yapısına "Device Context" olarak isimlendireceğiz.
OpenGL'nin veri yapısına "Render Context" olarak isimlendireceğiz.

Velhasıl ilkin sistemin(Windows) programımıza OpenGL desteğini açması(kullanmaya izin) gereklidir.
Bunun için GetDC API 'si ile Windows'un device context handleyini(hDC) elde etmeli(serbest bırakmak için RelaseDC fonksiyonu). Ekran piksel formatına çift bufferlama ve OpenGL desteği eklenmek için ChoosePixelFormat API 'si çağrılıp, contexte bunu set etmek için SetPixelFormat API 'si çağrılır.


Bu özellikler eklenmiş contextden OpenGL'nin render contextini(hRC) yaratmak için wglCreateContext API'si(silmek için wglDeleteContext) çağrılmalı ve bu OpenGL contextin Windows'un Device Context'i yerini alması için wglMakeCurrent API'si(eski hale getirmek için yine aynı fonksiyon ama parametrelerinin değerleri sıfır) çağrılmalıdır.

Not: Çift bufferlama(görüntüde titreme olmaması için bir imajın tam olarak hazır oluncaya kadar sahnelenmeyip önceden tamamlanıp hazırlanmış karesinin gösteriminin devam edilmesi yani iki buffer'ın kullanılması tekniği, hazır olan ve hazırlanmakta olan bufferlar).


Kodlara geçersek ; önceki dersteki kodları aynen kullanıp iki ek prosedür ekleyeceğiz. Biri, OpenGL'i Windows altında aktif edecek OpenGLAktif() prosedürü. Diğeri, Pasif edecek olan OpenGLPasif() prosedürü.
Tabi ilk önce kodlarımıza OpenGL'nin fonksiyon,yapı, sabit tanımlamalarının olduğu header dosyasını ve kütüphanesi eklemeliyiz:

//Kodlarımıza OpenGL kütüphanelerini eklemek için:
#pragma comment(lib,"opengl32.lib")
#pragma comment(lib, "glu32.lib")

//Kodlarımıza OpenGL header dosyalarını eklemek için:
#include <gl/gl.h>
#include <gl/glu.h>

Farklı prosedürlerden başvurduğumuz üç adet global değişkenimiz var:

HGLRC hRC=NULL; // OpenGL Render Context
HDC hDC=NULL; // Windows Device Context
HWND hWnd=NULL;// pencere handleyimiz


Prosedürlerimize bakalım:

void OpenGLAktif(void)
{
PIXELFORMATDESCRIPTOR pfd;// çizim yüzeyinin pixel formatını tanımlayan yapı

hDC=GetDC(hWnd); // Device Context'i elde et
pfd.dwFlags=PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER;//pixel formata şu özellikleri ,
GLuint PixelFormat=ChoosePixelFormat(hDC,&pfd); //ekle
SetPixelFormat(hDC,PixelFormat,&pfd); // Device contexet'e bunu set et

hRC=wglCreateContext(hDC); // yukardaki özellikler eklenmiş DC'den OpenGL Render Context'ini yarat
wglMakeCurrent(hDC,hRC); // Windows Device context yerine Render Context'i aktif et

}




void OpenGLPasif(void)
{

wglMakeCurrent(NULL,NULL); // OpenGL RC yerine Windows DC'i aktif et
wglDeleteContext(hRC); // OpenGL RC'i sil
ReleaseDC(hWnd,hDC); // Windows Device Contexti serbest bırak

}


Peki bu prosedürleri, kodlarımızın neresinden çağırmak uygun? Penceremiz(dialog) ilklenirken henüz görüntüye gelmeden önce Windows bize WM_INITDIALOG mesajını yollar; Bu mesaja yanıtta OpenGL'i aktif etmek uygun gibi. Penceremizi sonlamadan(kapatmadan) önce pasif etmek de yerinde.

switch (uMsg)
{
case WM_INITDIALOG:
{
hWnd=hwndDlg;
OpenGLAktif();
break;
}
case WM_CLOSE:
{
OpenGLPasif();
EndDialog(hwndDlg,NULL);
break;
}

Buraya kadar olan kodları ve projeyi yüklemek için tıklayın.

Ders Tamamlanacak...