extern "C"
C뿐만 아니라 모든 프로그래밍 언어는 목적 파일(obj)을 만들고 링크에 의해 목적 파일을 연결함으로써 최종 실행 파일을 만든다. 이때 목적 파일로 자신이 정의한 함수의 명칭과 주소를 공개하도록 되어 있고 이 명칭 공개 방식이 언어에 상관없는 표준 포맷으로 규정되어 있다. 그래서 C와 파스칼처럼 각각 다른 언어로 컴파일된 오브젝트 파일들이 링크될 수 있는 것이다.
초기의 C++도 마찬가지로 이런 범용 링커(linker)를 사용하도록 디자인되었다. 그런데 범용 링커는 함수를 오로지 이름으로만 찾기 때문에 C++의 오버로딩된 함수들을 제대로 구분하지 못한다. 그래서 C++은 함수의 이름을 외부로 공개할 때 인수의 개수와 타입에 대한 정보까지 함수명에 포함시키는데 이런 명칭을 작성하는 것을 이름 장식(name mangling)이라고 한다. 그래서 C++로 컴파일한 목적 파일의 함수명을 보면 함수 이름외에도 앞뒤로 이상한 기호들이 붙어 있다. 앞에서 만든 Overload.cpp의 목적 파일에는 세 개의 Add함수의 이름이 다음과 같이 작성된다.
Add라는 함수 이름 외에도 뒤쪽에 인수의 개수나 타입에 대한 정보들이 부호화되어 표기되어 있다. 오버로딩된 함수들의 외부 명칭이 이렇게 작성되므로 링크 단계에서는 각 함수들이 다른 함수처럼 인식되는 것이다. 그런데 모든 언어가 오버로딩을 지원하지 않기 때문에 C++의 이런 기능이 다른 언어와 혼합 프로그래밍을 할 때는 문제가 될 수 있다. 그래서 다른 언어와 링크되어야 하는 모듈은 이런 이름을 작성하지 말고 함수의 명칭을 이름만으로 외부로 공개할 필요가 있다.
이때 사용하는 지시자가 바로 extern "C"이다. 함수앞에 이 지시자를 사용하면 오버로딩 기능은 사용할 수 없지만 다른 언어와 링크될 수 있는 범용 함수를 만들 수 있다. 이렇게 만들어진 함수는 C에서도 호출 가능하다. 지금 당장 여러분들이 이 지시자를 사용할 일은 없겠지만 차후에 윈도우즈 환경에서 DLL을 만들거나 할 때는 이 지시자가 필요할 것이다. 윈도우즈의 DLL 포맷은 이름으로 함수를 찾기 때문에 오버로딩을 지원하지 않는다.
extern "C" 지시자는 C++이 C나 다른 언어를 위해 이름 장식을 하지 않도록 할 때 사용하기도 하지만 반대의 경우에도 이 지시자가 필요하다. 즉, 이미 C로 만들어진 함수를 C++에서 호출하고자 할 때 이 함수의 원형 앞에 extern "C"가 있어야 한다. 그렇지 않으면 링커가 장식된 이름으로 함수를 찾게 되므로 C의 함수를 제대로 연결하지 못한다. C++로만 함수를 작성한다면 굳이 이 지시자를 사용할 필요가 없다.
WINAPI의 의미(__stdcall)
function calling에 있어서 convention이 여럿 있게 마련인데요.
그런 게 있는 이유는 언어별로 다 틀리기 때문이죠.
따라서 님이 만약 어떤 dll을 만들어서 이런 저런 s/w에서 쓸 수 있게 하려면,
basic용, c++용, pascal용 등등을 따로 만들어야겠죠?
그렇게 되면 시간 및 비용이 많이 들게 됩니다.
그리고 dll의 본래 취지에서 벗어나게 되죠.
그래서 WINAPI라게 선언을 하는 거죠.
WINAPI를 찾아보시면..
#define WINAPI __stdcall
라고 되어 있는데요..
__stdcall 이라는 keyword로 선언된 함수는 함수가 return되기 전에 stack을 정리하게 됩니다.
좋은 비교 대상으로 __cdecl이라는 keyword가 있는데요..
__cdecl 이라는 keyword로 선언된 함수는 함수가 return된 후에 stack을 정리하게 됩니다. ( c/c++ )
따라서 __cdecl keyword를 사용하게 되면 stack을 정리하는 code가 필요하게 되죠.
이런 이유로, CALLBACK macro나 PASCAL macro도 찾아보면 __stdcall 이죠.
( 문자 그대로 standard calling 입니다. )
이외에도 차이점은 있을 거라고 생각됩니다만, 어쨌거나 님께서 질문하신 WINAPI,
즉 __stdcall의 용도는 서로 다른 compiler로 제작된 binary에 공통된 interface를
( - 정확히는 pascal 형식으로 알고 있습니다.. ) 제공하기 위한 것에 목적이 있다고 보시면 됩니다...
참고가 되셨길..
- 네이버에서 greathjm 님의 글을 발췌했습니다.
ps.
#define WINAPI FAR PASCAL
#define FAR _far
#define PASCAL __stdcall
출처 : Tong - 중허니님의 API통
///////////////////////
1. 다른 파일에 선언된 변수나 함수를 선언할 때
◆ 기본적인 얘기
extern이라는 것은 저장공간에 대한 선언에 사용되는 키워드로서,
여러개의 소스파일에 대해서 작업할 때 필요한 것입니다.
무슨 얘긴고 하니... 쉽게 말해서 다른 파일에 선언된 변수가 있다
고 선언하는 겁니다.
[file1.cpp]
int x = 1;
int f() { /* do something */ }
int y = 2;
extern z;
[file2.cpp]
extern int x;
int f();
extern int y = 3; // 링크 에러.
extern z; // 링크 에러
void g() { x = f(); }
extern int x; 라는 줄은 정수 x가 여기서 정의(definition)되지
않는다는 것을 지시하며 단지 선언(declaration)을 해주는 일을 수
행합니다. 위에서 y는 링크시 에러가 나게 되는데, extern 키워드
를 사용하더라도 초기화를 하면 그 선언은 정의가 되기 때문입니다.
즉, y는 file1.cpp와 file2.cpp에서 모두 정의되게 됩니다. 반대
로 z는 두 파일에서 모두 선언만 되고 정의가 되지 않았기 때문에 역
시 링크에러가 나게 됩니다.
참고로 file2.c에서 f는 extern int f(); 라고 지정해도 상관없습니다.
◆ const와 typedef
const와 typedef는 기본적으로 internal linkage입니다. 즉, 다
른 파일에서 영향을 받지 않는다는 것이죠. const에서 이미 살펴봤
죠? typedef도 마찬가지입니다.
const에서 다뤘던 포인터 예제는 적절하지 못한 예였습니다. -_-;
여기서 다시 보도록 하죠.
[t1.cpp]
typedef int T;
const char* pText0 = "SCV";
char* const pText1 = "marine";
extern char* const pText2;
extern char* const pText3 = "tank";
extern char* const pText4 = "zealot";
[t2.cpp]
typedef void T;
const char* pText0 = "SCV";
char* const pText1 = "lurker";
extern char* const pText2;
extern char* const pText3;
extern char* const pText4 = "dragoon";
T : t1.cpp에서는 int, t2.cpp에서는 void로 사용할 수 있음.
pText0 : 조심! const char*의 의미는 const char에 대한 const
가 아닌 포인터. 즉, const가 아님. 따라서 external linakage. 링크 에러.
pText1 : char* const의 의미는 char에 대한 const 포인터. 즉,
internal linkage. t1.cpp에서는 "marine", t2.cpp에서
는 "lurker"으로 잘 동작함.
pText2 : 양쪽 모두 선언만 되고 정의는 안함. 실제로 사용하게 되면 링크 에러.
pText3 : t1.cpp에서 초기화를 했기 때문에 정의가 됨. "tank"
pText4 : 양쪽 모두 초기화를 했기 때문에 양쪽 모두 정의가 되서 중복 정의 에러가 생김.
◆ extern을 사용가능한 경우
그럼 마지막으로 어떤 경우에 static이, 어떤 경우에 extern이 사용
가능한지 정리한 표를 보죠. (from MSDN)
Construct static extern
Function declarations within a block No Yes
Formal arguments to a function No No
Objects in a block Yes Yes
Objects outside a block Yes Yes
Functions Yes Yes
Class member functions Yes No
Class member data Yes No
typedef names No No
2. 다른 언어로의 연결을 지시할 때
◆ 기본적인 얘기부터
가장 대표적인게
extern "C" int Calc(double, int, int);
이런 식으로 C의 함수를 선언하는 것입니다.
아시겠지만 C와 C++은 함수를 호출하는 방식이 다릅니다. 인자를 처
리하는 순서라거나 스택에서 관리하는 방법 같은게 다르죠. (자세한
건 다음 기회에...) 따라서 C++ 컴파일러에게 이 함수는 C++의 규칙
에 어긋나는 함수는 아니지만 사실은 C의 함수라고 알려줘야 C++ 컴
파일러가 실수하지 않고 처리할 수 있게 됩니다. (실제로 이 선언을
중요하게 처리해야 하는건 링커입니다.)
그리고 Fotran에서 만든 함수를 Visual C++에서 사용하기 위해서
도 extern "C"로 선언해야 하는 것 같습니다. VC++이 extern 다음
에 허용하는 문자열이 "C"와 "C++" 뿐이라네요. (제가 해본건 아니
라서...)
extern "C"
{
#include < string.h> // C 헤더 파일을 포함
int Calc(double, int, int); // C 함수를 선언
int g1; // 정의(definition)
extern int g2; // 선언(declaration, not definition)
}
이렇게 해서 여러개의 선언을 묶어주기도 하죠.
참고로 아랫줄은 정의가 아니라 선언입니다.
extern "C" int g3; // 선언(declaration, not definition)
C코드를 만들면서 C와 C++에서 다 사용할 수 있도록 헤더 파일을 만
들기 위해서는 다음과 같은 방법을 사용합니다.
#ifdef __cplusplus
extern "C" {
#endif
// 실제 함수나 변수들의 선언
#ifdef __cplusplus
}
#endif
C++ 소스를 컴파일할 경우에는 __cplusplus 가 자동으로 정의되므
로 전처리기에 의해 extern "C" { } 선언이 들어가게 됩니다.
◆ 함수 포인터의 경우
case by case로 생각해보겠습니다. (책을 베끼겠습니다.)
typedef int (*FT)(const void* , const void*); // (1)
extern "C" {
typedef int (*CFT)(const void* , const void*); // (2)
void qsort(void* p, size_t n, size_t sz, CFT cmp); // (3)
}
void isort(void* p, size_t n, size_t sz, FT cmp); // (4)
void xsort(void* p, size_t n, size_t sz, CFT cmp); // (5)
extern "C" void ysort(void* p, size_t n, size_t sz, FT cmp); // (6)
int compare(const void* , const void*); // (7)
extern "C" int ccmp(const void* , const void*); // (8)
(1) FT는 (const void*, const void*)를 받고 int를 리턴하는 C++로 링크되는 함수.
(2) CFT는 (const void*, const void*)를 받고 int를 리턴하는 C로 링크되는 함수.
(3) cmp는 CFT 타입. 즉 C로 링크되는 함수. qsort도 C 함수.
(4) cmp는 FT 타입. 즉 C++로 링크되는 함수. isort도 C++ 함수.
(5) cmp는 CFT 타입. 즉 C로 링크되는 함수. xsort는 C++ 함수.
(6) cmp는 FT 타입. 즉 C++로 링크되는 함수. qsort도 C 함수.
(7) compare는 C++로 링크되는 함수.
(8) ccmp는 C로 링크되는 함수.
따라서... 다음과 같은 결과가 나옵니다.
void (char* v, int sz)
{
qsort(v, sz, 1, &compare); // error
qsort(v, sz, 1, &ccmp); // ok
isort(v, sz, 1, &compare); // ok
isort(v, sz, 1, &ccmp); // error
}
[The C++ Programming Language]의 설명에 따르면 C와 C++이 함
수를 호출하는 방법이 같을 경우에는 위의 에러들도 확장으로 인정해
줄 수 있다고 하네요. 참고하세요.
◆ 다른 언어와의 연결에 대한 조금 더 시시콜콜하고 Visual C++적인 얘기
포트란이나 파스칼 함수에 대해서는 __stdcall 를 사용해서 선언해줘야 합니다.
(모든 윈도우 API 함수들은 __stdcall을 사용해서 선언되어 있습니다.)
함수의 선언 앞에 붙이는 PASCAL, WINAPI, CALLBACK이라는 선언자
는 모두 __stdacll이라는 의미를 가지고 있죠.
반면에 C나 C++의 함수는 기본적으로 __cdecl이 됩니다.
__cdecl로 선언된 함수의 경우 인자를 전달한 스택을 정리하는건 호
출한 함수(caller)입니다.
__stdcall로 선언된 함수의 경우 인자를 전달한 스택을 정리하는건
호출된 함수(callee)입니다.
양쪽다 인자는 오른쪽 인자부터 왼쪽으로 스택에 들어가지요.
그렇다면 스택을 정리하는게 누구인지가 왜 중요할까요?
다른 언어가 다 호출된 함수가 스택을 정리하니까 C나 C++도 똑같이
하면 편할 텐데요.
일단 치명적인 부분은 vararg를 사용하는 함수입니다. 가장 대표적인게 printf죠.
printf 는 다음과 같은 형을 가집니다.
int printf(const char *format, ... );
... 는 인자의 수가 정해져 있지 않다는 뜻이죠? 하지만 인자의 갯수
나 종류같은게 정해져 있지 않기 때문에 호출된 함수가 스택을 정리하
는 __stdcall의 경우에는 제대로 청소를 할 수가 없게 됩니다. 따라
서 인자의 수가 정해져 있지 않은 함수는 __cdecl을 사용하게 됩니다.
참고로 __cdecl의 경우에는 각 함수 호출마다 스택을 정리하는 부분
이 들어가야 하기 때문에 컴파일된 코드의 크기가 더 커질 수 있다고 합니다.
그리고 코드를 C++로 작성중이더라도 라이브러리 파일을 만들어서 다
른 언어에서도 사용할 수 있게 해주려면 extern "C"와 __stdcall
을 사용해서 선언해주는게 좋겠죠?
이런 부분에 대해 자세히 알고 싶으신 분은 MSDN에서 "Mixed-
Language Programming Topics"이라는 글을 참고하시기 바랍니다
출처 : http://cafe.naver.com/prostudy12