Delphi’de Fonksiyon ve Prosedür Çağırma Düzenleri (Calling Conventions)

Nedir bu “Calling Conventions” derseniz, Türkçe karşılığı Çağırma Düzeni/Biçimi gibi çevrilebilir. Ancak bu pek anlamını karşılamıyor. Yazımda zaman zaman İngilizce halini kullanabilirim. Anlaşılmazlığa sebep olursa affola. Yazıda bir çok yerde “Fonksiyon ve Prosedür” demek yerine Fonksiyon diyeceğim. Siz onu Fonksiyon ve Prosedür olarak algılayabilirsiniz 🙂

Calling Convention’lar için parametrelerin iletilme sırasını, yerini (yığıt/register), yığıt temizleyicisini ve hata/exception yakalayıcısını belirleyen fonksiyon çalışma modelidir diyebiliriz.

Prosedür veya fonksiyon tanımlarken “register; pascal; cdecl; stdcall; safecall” anahtar kelimeleri ile Calling Convention belirtebilirsiniz. Örneğin;

function MyFunction(X, Y: Real): Real; cdecl;

Yukarıda da belirttiğim gibi Delphi’de 5 farklı Convention var. Eğer Convention belirtmeden tanım yaparsanız varsayılan olarak register kabul edilir.
Register, özellikle de yığıt oluşturmadığı sürece acayip verimlidir. En verimlisidir.
C/C++ fonksiyonlarını çağırırken Cdecl kullanışlıdır.
Stdcall, Safecall dışarıdan çağrılan kodlarda genel olarak verimlidir. (Stdcall, Cdecl’den daha verimlidir.)
Ayrıca Win32 API’lar Stdcall ve safecall kullanır. Diğer işletim sistemleri genellikle Cdecl kullanır.
Near, far, export 16-bit Windows programlamadan kalan convention’lar da geriye dönük uyumluluk için desteklenmektedir. Fakat Win32’de bir özelliği olmadığı için yazımda onlara yer vermeyeceğim.

Cdecl
İsmi “C Declaration” kısaltmasıdır. İsimden de anlaşılacağı üzere, köken olarak C programlama dilinden çıkmıştır. x86 mimarideki bir çok C derleyicisi bunu kullanmaktadır.

Parametrelerin tamamı yığıtta iletilir. Parametreler yığıta ters yazılır (Sağdan sola). Fonksiyonu çağıran fonksiyondan geri dönüşü aldıktan sonra yığıtı temizler.

Dönüş değerleri ise;
Integer değerler ve bellek adresleri (Pointer) EAX register’ı ile,
Float değerler ise ST0 x87 register’ı ile alınır.

EAX, ECX, EDX Register’ları fonksiyonu çağıran tarafından kaydedilir. Diğerleri ise çağrılan fonksiyon tarafından kaydedilir.
ST0 .. ST7 (x87 floating point (Kayan Nokta) register’ları) fonksiyonu çağırırken boş olmalıdır. ST1 .. ST7 fonksiyondan çıkarken boş olmalıdır. ST0 da dönüş değeri için kullanılmamışsa boş olmalıdır.

Bazı derleyiciler Fonksiyon dönüş değeri olarak basit 2 register’a sığacak struct yapılarını EAX, EDX register’ları ile,
Büyük struct’ları ve exception yakalayıcısı tarafından özel muamele gereken sınıf nesnelerini ise bellek üzerinde geri döndürür.

Bellek üzerinden dönüş işlemi için, çağıran bellekte ilgili struct/class kadar yer ayırır ve gizli bir ilk parametre olarak fonksiyona bellek adresini (pointer) gönderir. Çağrılan fonksiyon ise bellekteki yeri ilgili struct/class ile doldurup, dönüş değeri olarak yine pointer’ı gönderir. (Bu ayrılan yerin çağıran tarafından temizleneceğini söylememe gerek yok sanırım 🙂 )

Son bahsettiğim (struct ve class dönüş değeri) kısım bazı derleyicilerde değişkenlik gösterebiliyor. Bu faktörler dolayısı ile farklı C derleycileri arasında uyumsuzluk, hatta aynı derleyicinin farklı işletim sistemlerine derlemeleri arasında farklılıklar söz konusu olabiliyor.

Stdcall
İsminin “Standard Call” kısaltması olduğu çok bellidir. Ancak isim bize pek açıklayıcı değil. Köken olarak aslında Pascal convention’dan gelir ancak biraz farklı.

Cdecl convention’ın aksine yığıt temizleme işini çağrılan fonksiyon yapmaktadır. Aslında bu yazıda bahsedeceğim tüm Convention’larda (Cdecl hariç) yığıtı çağrılan fonksiyon temizler.

Cdecl gibi parametreler yığıta ters yazılır. (Sağdan sola)

EAX, ECX, EDX register’larını fonksiyon içinde kullanmak üzere tasarlanmıştır. Dönüş değerleri ise EAX register ile alınmaktadır.

Stdcall, Win32 API’larının standart convention’ıdır.

Bonus: Winapi
Hadi yine iyisiniz, kaptınız bonusu. Delphi’de “Winapi” isminde bir convention daha var. Aslında kendisi bir convention değildir.

Win32 API Convention’ının Stdcall olduğunu yukarıda belirttim. Windows CE API Convention’ı ise Cdecl. “Winapi” ise Stdcall veya Cdecl arasında seçimi derleyiciye yaptırmakta.

Yani, kendisinin gerçek adı Stdcall ve kendisi gizli convention’dır. (şaka!)

Safecall
Delphi ve Free Pascal’da COM (Component Object Model) veya OLE hata yönetimi sağlayan bir Convention’dır. Exception’lar fonksiyonu çağırana gönderilmez ancak HResult dönüş değeri içinde bir hata raporu bulunur. Safecall bir fonksiyon Delphi’de çağırıldığı zaman Delphi otomatik olarak HResult değerini de kontrol eder ve gerekliyse exception oluşturur.

Safecall, stdcall ile aynıdır. Farkı ise fonksiyon çağırana EAX üzerinde HResult değerini gönderir. Dönüş değeri ise yığıtta refarans edilen bellek adresinde döner.

Bu convention ile bir Delphi fonksiyonunu Delphi’den çağırırsanız, aynı diğer fonksiyonlar gibi dönüş değerini size iletip, aynı zamanda EAX ile gelen HResult değerini kontrol ederek gereken exception’ı otomatik olarak oluşturur.

Yani,

function Fonksiyon(Parametre: DWORD): DWORD; safecall;

Şeklinde tanımladığımız fonksiyon aslında budur:

function Fonksiyon(Parametre: DWORD; out Result: DWORD): HResult; stdcall;

Tabi HResult değerini biz hiç görmüyoruz, Delphi gerekli müdahaleleri kendisi yapıyor.

Register
“Borland Register” olarak da bilinir. Delphi’nin 32-bit Windows derleyicisinde varsayılan convention’dır. Linux kernel’ının bazı versiyonları i386’da bu convention’ı kullanır.

3 parametreye kadar yığıt kullanmadan EAX, EDX, ECX (Bu sırayla) register’larını parametre iletmek için kullanır. Geriye kalan parametreler ise yığıt ile iletilir. Parametre iletimi yukarıdakilerin aksine düz bir şekilde gerçekleşir. (Soldan sağa) Yığıtı çağrılan fonksiyon temizler.

Fakat Real, Method Pointer, Variant, Int64 ve Yapısal tipler(Record/Class) register ile iletilmeye müsait olmadığı için bunlar da yığıt ile iletilecektir.

Örneğin;

procedure Test(A: Integer; var B: Char; C: Double; const D: string; E: Pointer);

A: EAX (32-bit Integer), B: EDX (Char Pointer), D: ECX (Long-string bellek alanına Pointer)
C(Double) ve E(32-bit Pointer) ise yığıta yazılır. (E, boş register kalmadığı için yığıta yazılır. C, her halukarda yığıtta iletilecekti)

Fonksiyonlar EBX,ESI, EDI, EBP register’ları korumak zorundadır. Fakat EAX, EDX, ECX üzerinde değişiklik yapabilir.

Pascal
İsmi zaten olayı ele veriyor. Pascal programlama dilinin convention’ı. Artık kullanılmamaktadır, geriye dönük uyumluluk için bulundurulmaktadır. OS/2 1.x 16-Bit ve Windows 3.x 16-Bit API’larda bu convention kullanılmaktadır. Ayrıca Borland Delphi 1 de bu convention’ı kullanmaktadır.

Parametreler yığıta düz bir şekilde yazılarak iletilir. (Soldan sağa) Çağrılan fonksiyon dönüş değerini iletmeden önce yığıtı temizlemekle yükümlüdür.

 

Sanırım konudan bu kadar bahsetmek yeterli. Görüşmek üzere kendinize iyi bakın.

Tags: , , , , , , , , , , , , , ,

No comments yet.

Leave a Reply

*