31-08-2018, Saat: 15:20
(Son Düzenleme: 31-08-2018, Saat: 15:55, Düzenleyen: ismailkocacan.
Sebep: cpp kodu eklendi.
)
Fonksiyon çağırım düzeni (Calling Convention kısaca CC diyelim) özetle;
fonksiyon ya da procedure parametrelerinin hangilerinin CPU register veya stack'de, hangi sıra ile tahsis edileceğidir.
Delphi, C/C++ gibi dillerde fonksiyon ya da procedure'e ekleyeceğimiz CC ile (register, pascal, cdecl, stdcall, safecall, winapi, __fastcall gibi) fonksiyonun çağırım düzeni belirtilebilir.
Delphi'de varsayılan CC register'dır. Yani fonksiyon ya da procedure'nin sonuna herhangi CC belirtilmediğinde varsayılan olarak register çalışır.
Stack frame oluşturmak yerine, ECX, EDX, EAX gibi CPU register'ları kullandığı için, fonksiyonun çalışma hızı en efektif ve hızlı olanıdır.
Aşağıdaki fonksiyonda X ve Y parametreleri stack yerine CPU register'larda tahsis edilecektir.
C/C++'da varsayılan CC cdecl'dir. cdecl adı üstünde c decleration kısaltmasıdır.Tüm parametreler stack'de tutulur.
C/C++
Yukarıdaki bilgilere göre;
Varsayılan bir C fonksiyonu ile, varsayılan bir delphi fonksiyonunu karşılaştırdığımızda, mantıken ve de teknik olarak delphi fonksiyonun daha hızlı çalışması gerekir.
Çünkü C'de varsayılan olarak, fonksiyon parametreleri stack'de tahsis ediliyorken, delphi'de 3 adet parametreye kadarı CPU register'larda geriye kalanlar ise stack'de tahsis edilir.
CPU ise register'ları stack'den daha hızlıdır.
C'de delphi'de olduğu gibi parametreleri stack yerine CPU register'da tahsis etmek için __fastcall CC kullanılır.
Kısaca C'nin __fastcall'ı ile Delphin'nin register CC'ı aynıdır diyebiliriz.
Delphi
C/C++
ASM Çıktısı
Dokümanlar winapi ya da stdcall'ın aslında bir CC olmadığını söylüyor.Daha çok windows işletim sistemindeki API'lerde bu CC görebiliriz.
winwindef.h içerisinde tanımı şöyle;
fileapi.h
Delphi zaten bir çok windows api fonksiyonunu hali hazırda bize WinApi.Windows.pas uniti içersinde sunuyor.
Yine dokümanlar pascal CC'nin ise geriye dönük uyumluluk için korumaya devam edildiğini söylüyor.
Şimdi örnek procedure ya da fonksiyonların CPU penceresindeki kodunu inceleyelim.
(View -> Debug Windows -> CPU Windows -> Entire CPU) ya da CTRL + ALT + C
main.cpp
Yukarıdaki çıktıları incelediğimizde, parametrelerin hangi sırayla (sağdan sola veya soldan sağa doğru) stack'e ittirildiğini(push) ya da hangi CPU register'da (ECX,EDX, EAX) tahsis edildiğini somut bir şekilde görebiliriz.
Delphi, C/C++ gibi dillerde fonksiyonlarda fonksiyon çağırım düzenini kısaca bu şekilde ifade edebiliriz.
.net dillerinde ise fonksiyon çağırım düzeni, DLLImport attribute sınıfında bulunan CallingConvention özelliği kullanarak belirtilebilir.
Bu tür managed dillerden, umanaged system fonksiyonlarını çağırmanın diğer bir adı ise Platform Invoke ya da kısaca P/Invoke diye ifade edilir.
Java'da ise "bildiğim kadarıyla" "dil seviyesinde" .net'deki DllImport'a benzer bir sınıf yok.
Aşağıdaki kaynaklara göz atabilirsiniz.
fonksiyon ya da procedure parametrelerinin hangilerinin CPU register veya stack'de, hangi sıra ile tahsis edileceğidir.
Delphi, C/C++ gibi dillerde fonksiyon ya da procedure'e ekleyeceğimiz CC ile (register, pascal, cdecl, stdcall, safecall, winapi, __fastcall gibi) fonksiyonun çağırım düzeni belirtilebilir.
Delphi'de varsayılan CC register'dır. Yani fonksiyon ya da procedure'nin sonuna herhangi CC belirtilmediğinde varsayılan olarak register çalışır.
Stack frame oluşturmak yerine, ECX, EDX, EAX gibi CPU register'ları kullandığı için, fonksiyonun çalışma hızı en efektif ve hızlı olanıdır.
Aşağıdaki fonksiyonda X ve Y parametreleri stack yerine CPU register'larda tahsis edilecektir.
function Hesapla(X, Y: Integer): Integer; function Hesapla(X, Y: Integer): Integer; register;Yukarıdaki her iki fonksiyon da aynı CC sahiptir diyebiliriz.
C/C++'da varsayılan CC cdecl'dir. cdecl adı üstünde c decleration kısaltmasıdır.Tüm parametreler stack'de tutulur.
C/C++
int Hesapla(int X, int Y); int __cdecl Hesapla(int X, int Y);Delphi
function Hesapla(X, Y: Integer): Integer; cdecl;
Yukarıdaki bilgilere göre;
Varsayılan bir C fonksiyonu ile, varsayılan bir delphi fonksiyonunu karşılaştırdığımızda, mantıken ve de teknik olarak delphi fonksiyonun daha hızlı çalışması gerekir.
Çünkü C'de varsayılan olarak, fonksiyon parametreleri stack'de tahsis ediliyorken, delphi'de 3 adet parametreye kadarı CPU register'larda geriye kalanlar ise stack'de tahsis edilir.
CPU ise register'ları stack'den daha hızlıdır.
C'de delphi'de olduğu gibi parametreleri stack yerine CPU register'da tahsis etmek için __fastcall CC kullanılır.
Kısaca C'nin __fastcall'ı ile Delphin'nin register CC'ı aynıdır diyebiliriz.
Delphi
function Hesapla(X, Y: Integer): Integer; register; begin end; Hesapla(1,2);ASM Çıktısı
Project1.dpr.45: Hesapla(1,2); 00419500 BA02000000 mov edx,$00000002 00419505 B801000000 mov eax,$00000001 0041950A E881DDFFFF call Hesapla
C/C++
int __fastcall Hesapla(int X, int Y) { } Hesapla(1,2);
ASM Çıktısı
main.cpp.48: Hesapla(1,2); 0040125B BA02000000 mov edx,$00000002 00401260 B801000000 mov eax,$00000001 00401265 E8DEFFFFFF call Hesapla(int,int)
Dokümanlar winapi ya da stdcall'ın aslında bir CC olmadığını söylüyor.Daha çok windows işletim sistemindeki API'lerde bu CC görebiliriz.
winwindef.h içerisinde tanımı şöyle;
#define WINAPI __stdcallÖrneğin CreateFile fonksiyonun tanımında görebiliriz.
fileapi.h
WINBASEAPI HANDLE WINAPI CreateFileA( _In_ LPCSTR lpFileName, _In_ DWORD dwDesiredAccess, _In_ DWORD dwShareMode, _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, _In_ DWORD dwCreationDisposition, _In_ DWORD dwFlagsAndAttributes, _In_opt_ HANDLE hTemplateFile );
Delphi zaten bir çok windows api fonksiyonunu hali hazırda bize WinApi.Windows.pas uniti içersinde sunuyor.
function CreateFileA(lpFileName: LPCSTR; dwDesiredAccess, dwShareMode: DWORD; lpSecurityAttributes: PSecurityAttributes; dwCreationDisposition, dwFlagsAndAttributes: DWORD; hTemplateFile: THandle): THandle; stdcall; function CreateFileA; external kernelbase name 'CreateFileA';
Yine dokümanlar pascal CC'nin ise geriye dönük uyumluluk için korumaya devam edildiğini söylüyor.
Şimdi örnek procedure ya da fonksiyonların CPU penceresindeki kodunu inceleyelim.
(View -> Debug Windows -> CPU Windows -> Entire CPU) ya da CTRL + ALT + C
program Project1; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils; procedure Procedure1(A, B, C, D, E: Integer); begin end; procedure Procedure2(A, B, C, D, E: Integer); register; begin end; procedure Procedure3(A, B, C, D, E: Integer); pascal; begin end; procedure Procedure4(A, B, C, D, E: Integer); cdecl; begin end; procedure Procedure5(A, B, C, D, E: Integer); stdcall; begin end; procedure Procedure6(A, B, C, D, E: Integer); safecall; begin end; begin Procedure1(1, 5, 7, 9, 10); Procedure2(1, 5, 7, 9, 10); Procedure3(1, 5, 7, 9, 10); Procedure4(1, 5, 7, 9, 10); Procedure5(1, 5, 7, 9, 10); Procedure6(1, 5, 7, 9, 10); end.
Project1.dpr.40: Procedure1(1, 5, 7, 9, 10); 00419500 6A09 push $09 00419502 6A0A push $0a 00419504 B907000000 mov ecx,$00000007 00419509 BA05000000 mov edx,$00000005 0041950E B801000000 mov eax,$00000001 00419513 E878DDFFFF call Procedure1 Project1.dpr.41: Procedure2(1, 5, 7, 9, 10); 00419518 6A09 push $09 0041951A 6A0A push $0a 0041951C B907000000 mov ecx,$00000007 00419521 BA05000000 mov edx,$00000005 00419526 B801000000 mov eax,$00000001 0041952B E878DDFFFF call Procedure2 Project1.dpr.42: Procedure3(1, 5, 7, 9, 10); 00419530 6A01 push $01 00419532 6A05 push $05 00419534 6A07 push $07 00419536 6A09 push $09 00419538 6A0A push $0a 0041953A E881DDFFFF call Procedure3 Project1.dpr.43: Procedure4(1, 5, 7, 9, 10); 0041953F 6A0A push $0a 00419541 6A09 push $09 00419543 6A07 push $07 00419545 6A05 push $05 00419547 6A01 push $01 00419549 E87ADDFFFF call Procedure4 0041954E 83C414 add esp,$14 Project1.dpr.44: Procedure5(1, 5, 7, 9, 10); 00419551 6A0A push $0a 00419553 6A09 push $09 00419555 6A07 push $07 00419557 6A05 push $05 00419559 6A01 push $01 0041955B E870DDFFFF call Procedure5 Project1.dpr.45: Procedure6(1, 5, 7, 9, 10); 00419560 6A0A push $0a 00419562 6A09 push $09 00419564 6A07 push $07 00419566 6A05 push $05 00419568 6A01 push $01 0041956A E869DDFFFF call Procedure6 0041956F E8C4F1FEFF call @CheckAutoResult Project1.dpr.46: end.
main.cpp
#pragma hdrstop #pragma argsused #ifdef _WIN32 #include <tchar.h> #else typedef char _TCHAR; #define _tmain main #endif #include <stdio.h> void Procedure1(int A, int B, int C, int D, int E) { } void __fastcall Procedure2(int A, int B, int C, int D, int E) { } void pascal Procedure3(int A, int B, int C, int D, int E) { } void __cdecl Procedure4(int A, int B, int C, int D, int E) { } void __stdcall Procedure5(int A, int B, int C, int D, int E) { } void Procedure6(int A, int B, int C, int D, int E) { } int _tmain(int argc, _TCHAR* argv[]) { Procedure1(1, 5, 7, 9, 10); Procedure2(1, 5, 7, 9, 10); Procedure3(1, 5, 7, 9, 10); Procedure4(1, 5, 7, 9, 10); Procedure5(1, 5, 7, 9, 10); Procedure6(1, 5, 7, 9, 10); return 0; }
Yukarıdaki çıktıları incelediğimizde, parametrelerin hangi sırayla (sağdan sola veya soldan sağa doğru) stack'e ittirildiğini(push) ya da hangi CPU register'da (ECX,EDX, EAX) tahsis edildiğini somut bir şekilde görebiliriz.
Delphi, C/C++ gibi dillerde fonksiyonlarda fonksiyon çağırım düzenini kısaca bu şekilde ifade edebiliriz.
.net dillerinde ise fonksiyon çağırım düzeni, DLLImport attribute sınıfında bulunan CallingConvention özelliği kullanarak belirtilebilir.
Bu tür managed dillerden, umanaged system fonksiyonlarını çağırmanın diğer bir adı ise Platform Invoke ya da kısaca P/Invoke diye ifade edilir.
[DllImport("User32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)] public static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, int uType);
static void Main(string[] args) { MessageBox(IntPtr.Zero, "Bu bir mesajdır", "Bu da bir başlıktır", 0); }
Java'da ise "bildiğim kadarıyla" "dil seviyesinde" .net'deki DllImport'a benzer bir sınıf yok.
Aşağıdaki kaynaklara göz atabilirsiniz.
- Calling Conventions
- Pitfalls of converting
- Delphi Language Guide (Delphi for Microsoft Win32 Delphi for the Microsoft .NET Framework)