[WIN32 API] 'APIENTRY'와 'CALLBACK' 의 의미..
API정복이라는 책을 처음 읽어 가면서 ...
APIENTRY 와 CALLBACK이 어떤 의미를 가지며 어떤 역할을 하는지가
궁금했습니다.
함수명과 리턴타입 사이에 지시자가 들어가는것도 새로웠구요
devpia에서 검색을 해봤습니다.
"Argument Passing and Naming Conventions"
라는 검색어로 msdn을 살펴보니..
_stdcall과 _cdecl의 차이점이 바로
스택을 청소하는 책임이 caller에 있느냐 callee에 있느냐라고 나와있습니다.
-Microsoft C++ Language Reference for eMbedded Visual C++ [__cdecl] Because the stack is cleaned up by the caller, it can do vararg functions. The __cdecl calling convention creates larger executables than __stdcall, ... The callee cleans the stack, so the compiler makes vararg functions __cdecl.
이것이 사실 이해가 안됩니다.
제가 알기로 스택은 cleaning이 되는게 아니고 스택포인터의 위치만 옮김으로써 마치 스택이 지워진것과 같은 효과를 내는것으로 알고 있습니다.
예를들어
http://bbs.kldp.org/viewtopic.php?t=26042&highlight=
제가 전에 kldp사이트에 올렸던 질문에서처럼
스택을 지우는것은 함수가 진행됨에 따라서 스택포인터가 자연히 이동이 되는것이고 나중에 return 주소값(Location Counter최초 호출 위치) 위치가 저장된 스택의 값을 마지막으로 참조해서 돌아간후 스택이 원위치 되는것으로 알고 있습니다. (물론 과거에 메모리상에 저장된 내용은 엄밀하게 말해서 지워진것은 아닙니다.)
첫째,
따라서 "스택을 clear하는 책임은 caller 또는 callee에 있다" 라고 볼수 없는것 아닌가요?
둘째,
APIENTRY와 CALLBACK 모두 __stdcall에 에 해당한다면
두가지의 차이점은 무엇인지 모르겠습니다.
왜냐면..
두가지를 없애고 테스트 해봤고 바꿔가면서 해봤지만 모든경우에
실행이 되었습니다.
#include<windows.h> LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM); HINSTANCE g_hInst; LPSTR lpszClass="First"; int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance, LPSTR lpszCmdParam,int nCmdShow) { HWND hWnd; MSG Message; WNDCLASS WndClass; g_hInst = hInstance; WndClass.cbClsExtra = 0; WndClass.cbWndExtra = 0; WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); WndClass.hCursor = LoadCursor(NULL,IDC_ARROW); WndClass.hIcon = LoadIcon(NULL,IDI_APPLICATION); WndClass.hInstance = hInstance; WndClass.lpfnWndProc = (WNDPROC)WndProc; WndClass.lpszClassName = lpszClass; WndClass.lpszMenuName = NULL; WndClass.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&WndClass); hWnd = CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT, NULL,(HMENU)NULL,hInstance,NULL); ShowWindow(hWnd,nCmdShow); while(GetMessage(&Message,0,0,0) ){ TranslateMessage(&Message); DispatchMessage(&Message);} return Message.wParam; } LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam){ switch(iMessage){ case WM_DESTROY: PostQuitMessage(0); return 0; } return (DefWindowProc(hWnd,iMessage,wParam,lParam)); }
두가지 답변해주시면 정말 감사하겠습니다.
Linux 쪽에서는 모두 caller 가 청소하기 때문에 __stdcal
Linux 쪽에서는 모두 caller 가 청소하기 때문에 __stdcall과 __cdecl 의 구분이 없습니다.
말씀하신대로 스택 포인터 값을 복귀 시키는 책임이 caller에 있느냐 callee에 있느냐의 차이가 있는 것입니다.
두 차이는 caller가 청소하는 경우 자기가 넘긴것은 자기가 청소하므로 확실합니다. 하지만 callee가 청소하는 경우에는 부르는 쪽과 불리는 쪽이 스택에 들어간 값을 명확히 하지 않으면 나중에 스택포인터가 잘못되는 경우가 생깁니다.
대부분의 Windows API가 stdcall 방식을 사용하고 이 방식이 define 문으로 여러 형태로 나타나곤합니다.
이 관례는 DOS 시절 이전부터 사용된 것을 아직도 그대로 사용하지 않나 싶습니다. callee가 스택을 제거하면 부를 때 마다 제거하는 것보다 몇 바이트 코드를 줄일 수 있거든요...
어떤 것이든 사용자 마음대로 선언할 수 있지만, printf 같이 가변인자를 넘기고 인자 개수를 넘기지 않는 구조에서는 caller가 제거해야만합니다. 따라서 모두 stdcall 이 가능할지라도 가변인자가 사용된 곳은 cdecl 일 수 밖에 없습니다.
---
http://coolengineer.com
stdcall과 cdecl 만 있는 것이 아니라.. 비슷하게 fa
stdcall과 cdecl 만 있는 것이 아니라..
비슷하게 fastcall, C++에서 사용하는 thiscall 등이 있고, 과거에는 그 위치에 pascal 이라는 calling convention도 있었습니다.
---
http://coolengineer.com
[quote="pynoos"]stdcall과 cdecl 만 있는 것이 아
__stdcall 이 바로 pascal calling conversion 입니다.
thiscall은 C++의 멤버 함수가 사용하는 거니까 어차피 선택사항이 되지 않고(x86 CPU 기준으로 DX레지스터가 this 포인터를 갖았던걸로 기억함->정확치 않음))
fastcall(인자전달에 ECX, EDX 사용)은 VC에서 사용하는 거지만... 예전에 DOOM등을 만들때 쓰였던 Wacom C는 레지스터 4개(eax, ebx, ecx, edx)를 인자 전달로 사용하였었습니다. 두 방식다 2개나 4개 이후의 인자는 스택을 사용합니다.
참고로 pascal(__stdcall) 방식이 c방식 (__cdecl)보다 속도가 빠릅니다.
그래서 Windows에선 pascal 방식을 __stdcall 이라고 이름 붙이고 표준으로 사용합니다.
답변감사합니다.
스택 포인터를 되돌려 놓는 책임이.. caller 또는 callee 에게 있을
수 있군요..
__stdcall과 _cdecl의 차이점에 관해서는 개괄적으로나마
이해하겠습니다.
전에 찾아본 내용중에..
CALLBACK == PASCAL == WINAPI == __stdcall
이런 내용이 있었는데요..
PASCAL이 있는 이유를 알겠습니다.
그런데 위 내용이 맞다면..
제가 질문한 APIENTRY 와 CALLBACK도 서로 '같다' 라고 보면
되는것인가요?
글 주제와는 별 상관없지만, 언제부터인가 국내에서는 API가 Win32
글 주제와는 별 상관없지만, 언제부터인가 국내에서는 API가 Win32 API를 의미하는 말이 된거 같군요. GTK+나 다른 API는 API가 아닌가요? -_-;;;
C FAQ: http://www.eskimo.com/~scs/C-faq/top.html
Korean Ver: http://cinsk.github.io/cfaqs/
[quote="pynoos"]Linux 쪽에서는 모두 caller 가 청
이거 맞는 말인가요?
기본 값이 __cdecl 이겠지 구분이 없는건 아니겠죠?
g++ 이 콜링컨버전이 안된다는 말씀인가요?
값을 명확히 해야한다는건 프로그래머가 명시적으로 사이즈를 알려 주는 방법이 있다는건가요?
컴파일러가 알아서 계산하는거 아닌가요?
caller 가 청소하는 것과 callee 가 하는 것을 별도로 만든것은 안정성 문제가 아니고(둘다 안정적입니다. 컴파일러에 버그가 없다면) caller 가 스택을 정리하게 되면 함수콜이 일어날때 마다 스택을 정리하는 인스트럭션이 소스에 포함되게 되서 실행코드가 커지게되고, callee가 하게 되면 콜된 함수 내부에 스택 정리 인스트럭션이 있어서 caller 가 하는 것 처럼 함수콜에 스택정리 인스트럭션이 필요가 없어서 실행코드가 작아지고 메모리캐쉬 히트율도 높아지는 장점이 생기지요, 하지만 printf 와 같은 파라미터가 가변인것은 함수 콜 할때 결정되는거라 이거는 callee에서 해결할 방법이 없어서 어쩔 수 없이 caller 가 청소를 해야 하지요.
이런 특징 때문에 구별을 하는 것으로 알고 있습니다.
이 말이 제가 위에서 설명드린 것이죠.
[quote="devilhero"][quote="pynoos"]Linux
표현이 명확하지 않아서인데, caller가 청소하는 방법만 있으므로 Linux 쪽에는 __stdcall 이라는 방법이 없다는 말이었습니다.
이 말은 무슨 뜻이지요?
이론상 그렇기 때문에 caller와 callee사이에서 사이즈가 협상이 되어야하며,
이것은 실제로 function naming mangling에 들어 있습니다. __stdcall 로 정의된 함수의 mangling (decoration)에는 스택청소해야하는 크기가 포함됩니다. 따라서 일반 __cdecl 의 경우 c compiler에서는 암묵적인 형선언으로 올바른 link가 일어나지만, __stdcall 과 같이 mangling이 들어가는 경우에는 명시적인 형선언이 앞에 있어야만 링크가 가능합니다.
이 말은 제가 위에 쓴 글에도 다 있는 내용입니다.
---
http://coolengineer.com
[quote="cinsk"]글 주제와는 별 상관없지만, 언제부터인가 국내
저도 cinsk님과 똑같은 생각을 가지고 있습니다. 왜 API가 Win32 API를 가리키는 말로 통하는지 모르겠습니다. :?
Re: 답변감사합니다.
APIENTRY나 CALLBACK, WINAPI등은 단순히 __stdcall을 #define으로 재정의한것에 불과합니다...
windef.h 헤더 파일을 한번 살펴보세요...
windows API들이나 콜백함수가 __stdcall로 정의된 이유는 다른 언어에서도 해당 API함수들을 사용할 수 있게 하기 위해서라고 알고 있습니다.
------------------------
http://agbird.egloos.com
[quote="marten"][quote="cinsk"]글 주제와는 별
용어를 잘못 사용하는 사람들이 있긴 하지만 국내에서 통용된다고 생각하시는 것은 좀 확대 해석한 것 같네요...
최초 질문하신 분이 언급한 'API 정복'이라는 책도 정식 명칭은 'Windows API 정복'입니다...
------------------------
http://agbird.egloos.com
답변 감사합니다.
WIN 32 API라고 구체적으로 적지 않은것 죄송합니다.
수정했습니다.
windef.h 파일을 보고
APIENTRY 와 CALLBACK 이 완전 같다는걸 알았습니다.
서로 바꿔서 컴파일 했을때 정상 수행이 되는게 당연한 거였군요..
그런데..
위 내용에서..
어짜피 사용자 정의 함수들도 파라미터개수가 고정이긴 하지만
개수 자체!를 전달하지는 않는데요..
printf 함수의 경우는 다른 함수와 달리 파라미터개수가
가변적이긴 하지만..
그렇더라도..
호출된 printf 함수 안에서 전달된 파라미터의 개수를 파악할수 있는것 아닌가요?
따라서 printf함수등에서도 stdcall이 사용될 수 있는것 아닌가요?
설명 부탁드립니다.
가변인자 함수들은 함수들의 목적코드가 생성 될 때 인자의 갯수를 알 수
가변인자 함수들은 함수들의 목적코드가 생성 될 때 인자의 갯수를 알 수 없습니다. 하지만 caller가 printf를 호출 할 때 그 인자의 갯수를 알게 되죠. 그래서 caller가 정리해야 합니다.
하지만 인자가 고정되어 있는 경우 함수의 목적 코드를 만들 때 그 갯수를 이미 알게 됩니다.
Re: 답변 감사합니다.
음... 요즘 들어 글을 쓸 때 비교되는 대상에 대해 정확히 써야겠다는 생각이 듭니다. :)
"printf 같이 가변인자를 넘기고 인자개수를 넘기지 않는 구조" 라고 표현한 것이 "존재할지 모르는 어떤 calling convention에서는 인자개수를 넘기는 구조"와 비교하여 표현한 것은 아닙니다.
현재 대부분의 구현에서는 인자로 개수를 넘기지는 않습니다.
printf 는 넘겨 받은 파라미터의 개수를 파악할 수 없습니다. gcc 같은 훌륭한(?) 컴파일러가 보여주는 printf 계열의 함수를 컴파일 할 때 "%"로된 포맷과 인자 개수를 비교하여 warning을 내어주기는 합니다만 이것은 printf가 파악하는 것이 아니라 컴파일러가 훌륭한 것이죠.
다음 프로그램은 적절합니다. printf 에 포맷만 있고 변수는 넘기지 않는 것에 주의해보시고 실행해보세요.
그리고 실행 인자를 주다보면, argc 값에 해당하는 숫자가 바뀌는 것이 있음을 알 수 있습니다.
만약 printf가 넘겨 받은 인자 개수를 안다면 오류를 내고 return 하는 방법이 표준에 들어 갔겠지만, 불행히도 포맷과 넘겨 받는인자의 개수에 대한 오류처리는 없습니다.
이런 문제를 stdcall에서는 어떻게 해결하냐면...
잘보시면 _func_a , _func_b 라는 심볼 뒤에 @4, @12라는 mangling(decoration)이 붙어 함수 명을 다르게 만들어 주는 것으로 자기가 불리울 때 청소하게될 스택의크기를 이름에 넣어둡니다. 따라서 compiler는 저런 stdcall 류의 함수를 부를 때 이미 몇 바이트를 해제할 것인지 알 뿐, 몇 바이트 해제하라고 지시하지는 않습니다....
---
http://coolengineer.com
댓글 달기