1. 개요
Windows API는 Microsoft Windows에서 사용하는 C언어 기반의 API이다. 기본적으로는 C언어 기반이지만, C++에서도 사용 가능하다. 윈도우에서 실행되는 모든 종류의 애플리케이션들은 내부적으로 전부 이 윈도우 API 함수를 호출하는 형태로 바뀐다.[1]Windows 자체는 C, C++로 작성되어 있어 API또한 해당 언어로 제공되고 있으나, Windows 8 부터 등장한 UWP/WinRT 부터는 C, C++, VB.NET, JavaScript를 사용할 수 있으며 유저랜드 UI의 경우 Win32, WinUI, XAML, HTML, DirectX 를 통해 작성이 가능하다. #
2. 특징
2.1. 하드웨어 독립적
과거의 MS-DOS 환경에서는 프로그래머가 시스템에 연결된 장치의 종류를 모두 알아야 했다. 가령 한컴오피스 한글 같은 워드프로세서를 만든다고 가정해 보자. 문제가 되는 것은 모니터와 프린터인데, 과거에는 이 그래픽 출력 장치 및 프린터에 대한 제어 코드를 일일이 만들어 주어야 했다.[2] 만약 회사나 학교에서 사용 중인 워드 프로세서가 학교에 있는 프린터에 맞추어 설정되어 있다고 생각하여 보자. 누군가가 외국에서 프린터 좋은 거 있다고 해서 사 왔는데, 사용중인 워드 프로세서에서 지원하지 않는 프린터라면 사용이 불가능하거나, 혹은 워드프로세서에서 사용자 정의로 프린터를 설정할 수 있다면, 프린터 매뉴얼을 꺼내들고, 하나 하나 제어코드를 맞추어 워드프로세서에 인식시키면 된다. 귀찮고 시간이 오래 걸릴 뿐이지 하면 된다. 그리고 이렇게 사용하는 것이 기본이었으므로 아무도 불만이 없었다.그러나 Windows 환경에서는 이런 하드웨어 제어 코드는 디바이스 드라이버가 가져가게 되며, 문자 출력은 DrawText 또는 TextOut 함수 호출로 한큐에 끝내게 된다. 꼭 문자 출력뿐만 아니라 거의 모든 게 이런 식으로 하드웨어의 종류에 영향을 받지 않는다. 거꾸로 윈도우 환경으로 넘어오면서 하드웨어 인터럽트를 직접 건드려 제어하는 방식의 응용 프로그래밍 기법은 완전히 사장되었다.[3] 윈도우에서는 하나의 운영체제 위에 여러 개의 프로세스가 작동하는 멀티태스킹 및 멀티스레딩 환경이기 때문이다.
2.2. ntdll.dll
윈도우는 어느 영역에서 하느냐에 따라 커널 모드와 사용자 모드로 나뉜다. 운영체제, 각종 드라이버들은 커널 모드에서 실행되며 일반 프로그램들은 사용자 모드에서 실행된다.원칙적으로는 일반 프로그램들은 사용자 모드에서 실행되므로 커널 모드에 접근할 수 없다. 따라서 커널에게 이런 작업을 해줄 것을 요청할 수 있으며 이를 시스템 콜이라고 부른다. 윈도우에서는 ntdll.dll가 이러한 역할을 맡는다. ntdll.dll의 노출된 API 중에서 Nt이나 Zw 접두사를 가진 함수는 시스템 콜을 사용하여 커널에게 처리를 맡는다.
Kernel32.dll, Advapi32.dll, KernelBase.dll 등의 시스템 DLL들은 내부적으로 ntdll.dll의 함수를 호출한다. ntdll.dll의 시스템 콜이 실행되면 인터럽트가 발생하여 커널 모드에서 시스템 콜을 확인하고 처리한다. 예를 들어 CloseHandle의 경우 내부적으로 ntdll.dll의 NtClose 함수를 호출하고 커널 모드에서 실제 NtClose 함수가 호출된다.
ntdll.dll의 함수들은 대부분 문서화되어 있지 않다. (Windows.h에도 없다!) 사용하기 위해서는 GetModuleHandle 함수로 ntdll.dll의 핸들을 얻어온 뒤 GetProcAddress 함수를 사용하여 함수의 주소를 얻어와서 사용해야 한다.
GetModuleHandle와 GetProcAddress 함수를 사용하는 것 외에도 ntdll.lib를 링크하고 사용할 함수를 선언하는 방법도 있다. 다만 ntdll.lib는 Windows SDK에는 없고 WDK(드라이버 개발 키트)에만 있으므로 WDK가 설치되어 있지 않다면 설치하거나 인터넷에서 ntdll.lib를 구해와서 사용해야 한다.
2.3. 동적 링크 라이브러리
윈도우는 기본적으로 꼭 필요한 기능들을 정적 라이브러리 형태가 아닌 DLL 형태로 만들어 윈도우 운영체제 안에 내장시켜 놓았는데, 보통 %SystemRoot%\\System32 경로에 가 보면 이런 DLL들을 찾아볼 수 있다. 프로그램이 실행될 때 동적으로 불려오는 DLL의 특징상, 프로그램을 한 번 짜 놓으면 윈도우 버전이 올라가면서 윈도우 DLL이 개선되는 경우 그에 맞게 프로그램 기능도 개선되게 된다.자주 사용하는 DLL은 다음과 같다.
- ntdll.dll : 가장 핵심적인 라이브러리로 커널 모드(Ring 0, Kernel Mode)와 사용자 모드(Ring 3, User Mode)를 이어주는 시스템 콜 역할을 한다. 모든 윈도우 API들은 최종적으로 ntdll.dll의 함수를 호출하게 된다.
- kernel32.dll : 메모리 관리, 파일 입/출력, 프로그램 로드/실행 등 기본적인 기능이 내장되어 있다. Windows NT 부터는 Kernel32.dll은 트램폴린 역할만을 하며 실제 기능은 ntdll.dll이 가지고 있다. (Kernel32.dll → KernelBase.dll → ntdll.dll)
- advapi32.dll : 액세스 토큰, 레지스트리, 서비스 등, 고급 기능들이 내장되어 있다.
- win32u.dll : 커널과 별개로 그래픽(GDI) 엔진과 메시지 처리 등을 담당하는 커널 드라이버인 win32k.sys의 시스템 콜 역할을 한다. 아래의 gdi32.dll와 user32.dll는 최종적으로 win32u.dll의 함수를 호출한다.
- gdi32.dll : 화면/프린터의 그래픽 출력을 관리한다.
- user32.dll : 윈도우[4], 대화 상자, 메뉴 등을 관리한다.
2.4. 문자열 처리
MS-DOS와 Windows 9x에서는 문자열을 ASCII로 처리하였다. ASCII는 로마자와 특수문자 위주로 구성되어 있으며 1바이트의 남은 공간에는 각 나라의 자국 문자를 할당시켰다. 하지만 그 결과로 한 나라에서 다른 나라로 이메일을 보냈더니 내용이 모두 깨지는 문제가 발생하였다. 인터넷 홈페이지도 마찬가지로 다른 언어로 된 페이지를 다른 나라에서 보면 내용이 깨진 채로 나왔다. 이 때문에 Windows NT 기반 운영체제에서는 다국어를 지원하기 위해 유니코드로 문자열을 처리하게 되었다.Windows API 중 문자열을 처리하는 함수의 경우 맨 뒷자리에 A와 W가 붙여있는 두개로 나뉘어져 있는데, 이는 문자열 처리 방식 때문으로 A는 ASCII, W는 유니코드 함수임을 의미한다. 예를 들어 CreateFile의 경우 CreateFileA는 ASCII 함수, CreateFileW는 유니코드 함수다. 참고로 ASCII 함수를 호출할 경우 내부적으로 MultiByteToWideChar 함수를 통해 유니코드로 변환시킨 후 유니코드 함수를 호출한다. (예: CreateFileA -> MultiByteToWideChar -> CreateFileW) [5]
Windows NT의 유니코드 형식은 wchar_t이다. 다만 WCHAR로 형식 이름을 바꾸어 사용한다. 그리고 여러 유니코드 형식이 있는데 다음과 같다.
형식 이름 | 실제 형식 |
WCHAR | wchar_t |
LPWSTR | wchar_t* |
LPCWSTR | const wchar_t* |
참고로 윈도우 커널에서는 UNICODE_STRING 구조체를 사용하여 문자열을 처리한다. RtlInitUnicodeString 함수로 초기화시킬 수 있으며 Windows API에서 시스템 콜을 사용해야 할 때 UNICODE_STRING 구조체를 초기화한 후 인수로 넘겨준다.
UNICODE_STRING 구조체 안에는 Length, MaximumLength, Buffer가 있는다. Buffer는 문자열 버퍼에 대한 포인터, Length는 해당 문자열의 길이, MaximumLength는 해당 문자열의 버퍼 길이다.
2.5. 메시지 처리
MS-DOS 환경이나 콘솔 응용 프로그램에서는 프로그램의 실행 흐름이 프로그래머가 작성한 코드에 따라서 움직였다면, 윈도우에서는 프로그램 외부에서 발생하는 이벤트들을 메시지의 형태로 전달받을 객체에게 알려 준다. 하드웨어 이벤트[6]가 발생하면, 윈도우의 시스템 메시지 큐에 이것이 쌓이게 되고, 시스템 메시지 큐에 들어온 메시지를 운영체제가 해당하는 객체의 메시지 큐에 넣어 준다. 프로그램은 while문을 돌면서 계속 메시지를 읽어서, switch-case해서 어떤 메시지가 오면 어떻게 처리한다는 로직을 기술하는 형태를 가진다. 메세지는 3가지의 값이 저장되어 있으며 메세지를 구별하기 위한 숫자 데이터, 추가 데이터 2개의[7] 숫자 데이터로 이루어져 있다.[8]추가적으로 말하면 흔히 OS를 말하는 윈도우(Windows)가 아니라 UI객체를 프로그래밍 상에서 윈도우(Window)라고 부르게 되는데 이 UI개체는 모두 메세지를 받을 수 있다. UI객체는 버튼, 이미지, 창 등 UI를 구성하는 모든 부분이 나뉘어져 있으며 예를 들어서 프로그램 내부의 버튼을 클릭하게 된다면 OS가 프로그램에게 어느부분에 마우스가 눌림 이라는 메세지를 보내게 되고 프로그램은 해당 메세지의 데이터를 보고 좌표값을 계산해서 해당 위치의 UI객체가 있다면 해당 UI객체에게 그 메세지를 전달한다. 버튼을 클릭한다고 했으니 해당 버튼에게 메세지가 전달되게 되고 해당 버튼에서 클릭시 해야될 일을 하게 된다.
2.6. 커널 오브젝트 (Kernel Object)
커널 오브젝트는 윈도우 커널 내에서 존재하는 개체를 뜻한다. 모든 개체는 개체 관리자(Object Manager)에 의해 만들어지고 삭제된다. 커널 오브젝트 종류로는 파일, 프로세스, 스레드 등이다.모든 커널 오브젝트에는 참조 횟수라는 변수가 포함되어 있는데 커널 오브젝트에 접근하거나 핸들을 얻거나 하면 참조 횟수가 증가하고 반대로 커널 오브젝트에 더 이상 접근하지 하거나 핸들을 닫거나 하면 참조 횟수가 감소한다. 이 참조 횟수가 0이 되면 윈도우는 해당 커널 오브젝트를 삭제할 수 있게 된다.
예를 들어 프로세스의 경우 프로세스가 종료되었더라도 해당 프로세스에 대한 핸들이 아직 열려있을 경우 윈도우에서는 해당 프로세스의 플래그에는 삭제 플래그 (PSF_PROCESS_DELETE_BIT)를 포함시킬 뿐, 바로 삭제하지 않는다. 해당 프로세스에 대한 모든 핸들이 닫혀 있어야만 비로소 해당 프로세스는 메모리에서 사라진다.
2.7. 핸들 (Handle)
윈도우에서는 개체에 직접적으로 접근할 수 없다. 다만 핸들을 통해 간접적으로 개체에 접근할 수 있다. 개체마다 핸들 테이블을 가지고 있다. 핸들 테이블에는 핸들 번호, 액세스 권한, 참조 횟수 등이 포함되어 있다.OpenProcess나 OpenThread 등의 함수는 해당되는 개체에 대한 핸들을 생성한다.[9] 개체에 접근하는 함수들은 해당되는 개체에 대한 핸들이 요구된다. 또한 액세스 권한도 있는데 개체에 접근하는 함수는 핸들이 필요한 액세스 권한을 가지고 있는지 확인하며 가지고 있지 않을 경우 바로 액세스 거부로 실패하게 된다.
생성한 핸들은 더 이상 사용하지 않는다면
CloseHandle
함수를 호출하여 핸들을 직접 닫아줘야 한다. 안 그러면 프로세스가 종료될 때까지 핸들이 존재하게 된다. 물론 프로세스가 종료되면 윈도우는 해당 프로세스가 만든 핸들을 모두 닫아준다.다만 같은 핸들이어도 CloseHandle로 닫을 수 없는 예외가 몇 있는데
FindFirstFile
함수 또한 HANDLE을 반환하지만 CloseHandle로는 닫을 수 없고 FindClose
로 닫아야 한다. 또한 HANDLE이 NULL을 반환하는 경우 오류가 있거나 유효하지 않은 핸들을 의미하지만 특정 상황에서는 NULL HANDLE은 유효한 의미를 가지는 경우가 있으며 어떤 함수는 INVALID_HANDLE_VALUE
을 반환하는 식으로 일관성이 떨어지는 모습을 가지고 있으므로 사용에 주의가 필요하다. Win16 시절부터 존재하던 API들은 INVALID_HANDLE_VALUE
를 반환하지만 Win32 부터 추가된 API는 NULL을 반환한다.'의사 핸들'(Pseudo Handle)이라는 특수 핸들도 존재한다. GetCurrentProcess이나 GetCurrentThread에서 반환된 핸들이 바로 의사 핸들로 모든 액세스 권한을 가지고 있으며 CloseHandle로 닫을 수 없다. 호출해도 아무런 효과가 없다.
커널 모드에서도 핸들을 생성할 수 있는데 이 경우 커널 핸들(Kernel Handle)라고 하며 액세스 권한을 무시하고 바로 개체에 접근할 수 있다. 물론 커널 모드에서만 사용할 수 있다.
2.8. 서비스 핸들 (Service Handle)
서비스 오브젝트에 붙여주는 핸들로 위의 핸들과 차이점이라면 커널에서 관리하지 않고 서비스 제어 프로세스인 services.exe (Service Control Manager)에 의해 관리된다.OpenSCManager 함수를 호출하여 서비스 데이터베이스에 대한 서비스 핸들을 생성하며 서비스를 새로 만들거나(CreateService) 기존 서비스에 대한 핸들을 생성(OpenService) 또는 서비스 목록을 가져오는(EnumServicesStatus) 등을 하려면 OpenSCManager로 얻은 서비스 핸들이 필요하다.
OpenService 함수를 호출하여 서비스에 대한 서비스 핸들을 생성하며 서비스를 시작하게 만들거나(StartService) 명령을 전달하는(ControlService) 함수에서 OpenService로 얻은 서비스 핸들을 요구한다. 서비스 핸들에도 역시 액세스 권한이 있으며 필요한 액세스 권한이 없으면 바로 액세스 거부로 실패하게 된다.
모든 서비스 관련 함수들은 내부적으로 services.exe에 요청하여 services.exe에서 처리하도록 되어 있다. 한 예시로 DeleteService 함수를 호출하여 서비스를 제거하려고 하면 해당 서비스가 실행 중이거나 해당 서비스에 대한 핸들이 모두 닫혀지지 않았을 경우 삭제를 보류하며 서비스가 정지되어 있고 해당 서비스에 대한 핸들이 모두 닫혀있어야만 비로소 서비스를 삭제하게 된다. 그게 불가능하다면 부팅 과정에서 삭제하게 된다.
2.9. 리소스
윈도우 프로그램을 짜다 보면 메뉴, 대화 상자(다이얼로그 박스), 아이콘, 비트맵 이미지, 커서 등을 많이 사용한다. 그러나 얘네들은 메모리 용량을 많이 잡아먹기 때문에 프로그램이 메모리에 올라올 때 얘네들이 메모리에 전부 다 올라와버리면 메모리가 터져나가게 된다. 그렇기 때문에 이런 것들은 리소스로 취급하며, 리소스 컴파일러를 통해 프로그램 코드와는 별개로 컴파일되고, 이들을 사용할 때는 LoadMenu, LoadIcon, LoadBitmap, LoadCursor 등의 함수를 통해 메모리에 로드하는 과정을 거치게 된다. 프로그램 실행 중 사용하지 않는 리소스는 메모리에서 자동으로 내려간다.3. Hello, Windows 프로그램
밑의 예시에서 LNK2019 오류가 발생할 경우에는 프로젝트 속성 -> 링커 -> 시스템 ->하위 시스템에서 '콘솔'로 되어있는걸 지우거나 '창'으로 바꾸면 정상적으로 실행이 된다.3.1. 메시지 박스
이 프로그램은 아무 아이콘도 없고, 확인 버튼이 하나 존재하는 메시지 박스에 "Hello, Windows!"를 출력한다.#!syntax cpp
#include <tchar.h>
#include <windows.h>
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
MessageBox(NULL, TEXT("Hello, Windows!"), TEXT("Hello"), MB_OK);
return 0;
}
3.2. 윈도우
이 프로그램은 흰색 바탕의 클라이언트 영역의 정중앙에 GDI를 사용해 "Hello, Windows!"를 출력하는 윈도우를 하나 띄운다.#!syntax cpp
#ifndef UNICODE
# define UNICODE
#endif // UNICODE
#include <stdlib.h> // EXIT_FAILURE
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int APIENTRY wWinMain(
HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow)
{
static wchar_t szAppName[] = L"HELLOWINDOWS";
HWND hWnd;
MSG msg;
WNDCLASS wndclass = {};
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(COLOR_WINDOW + 1);
wndclass.lpszClassName = szAppName;
if ( !RegisterClass(&wndclass) )
{
return EXIT_FAILURE;
}
hWnd = CreateWindowW(
szAppName,
L"Hello Windows Application",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
while ( GetMessage(&msg, NULL, 0, 0) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hDC;
PAINTSTRUCT ps;
RECT rect;
switch ( message )
{
case WM_CREATE:
return 0;
case WM_PAINT:
hDC = BeginPaint(hWnd, &ps);
GetClientRect(hWnd, &rect);
DrawText(
hDC,
TEXT("Hello, Windows!"),
-1,
&rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hWnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
4. COM
Component Object ModelC++의 가상 함수 테이블을 이용하여 기능을 인터페이스로 노출시키고 객체를 사용할 수 있게 한다. C++의 가상 함수 테이블을 사용하지만 COM 인터페이스는 ABI가 C++의 thiscall이 아닌 stdcall이기 때문에 C언어로도 바인딩이 되어 타 언어에서도 사용할 수 있다.
순수 Windows API는 C언어 함수로 구성되지만 DirectX, Media Foundation, Windows Shell 같은 기능들은 COM 인터페이스를 기반으로 제공된다.
COM에서 보이는 프로그래밍 패턴은 WinRT 에서도 유지된다.
5. WinRT / UWP
Windows 8 부터 제공되기 시작한 API셋이다. 이 셋이 등장한 이후부터 새로운 기능들은 WinRT 에서만 사용이 가능하고 이와 동일한 기능을 하는 C기반 표준 Win32 API로는 제공되지 않는다.자세한 내용은 Windows Runtime 문서 참고하십시오.
6. Windows App SDK
Windows APP SDK유저레벨에서 전통적인 Win32를 대체하기 위하여 발표된 API셋.
Microsoft가 Windows 8를 함께 발표하면서 WinRT API셋 또한 함께 발표하였고 새로운 기능들은 WinRT 에서만 사용이 가능하였다.
이 API를 주력으로 사용하는 Windows Runtime와 이후 Windows 10에서 나온 Universal Windows Platform이 사실상 망한데다 기존 까지 잘 사용되던 Win32 기반 프로그램들에서 이 WinRT 관련 API를 섞어 쓰는것이 만만치 않게 제약사항이 많았고 이런 이유 등으로 Win32를 대체하려는 시도는 UWP/WinRT 와 같이 여러 차례 있었으나 샌드박스 강제 등 런타임 환경 특성에서 많은 제한이 있었으며 기존 존재하는 코드를 마이그레이션 하려면 사실상 코드를 새로 짜야 하는 수준이었다 보니 MS의 노골적인 Win32 차별에도 불구하고 성공적으로 전환되지 못하였다.
이에 따라 Project Reunion라는 코드네임으로 첫 등장하였다. 이후 App SDK에서 UI 부분을 담당하는 WinUI로도 잘 알려져 있다.
MFC, Qt, wxWidgets과 같은 UI라이브러리를 쓰지 않는 이상 전통적인 방법으로 Win32로 GUI를 작성하는 경우 UI코드는 C로 작성하거나 리소스 폼을 사용하는 방법밖에 존재하지 않았으나 WinUI의 경우 UWP, WinRT용 프로그램들 처럼 XML 문법으로 작성된 XAML를 사용해서 UI를 그리거나 C++, C#을 사용하는 것이 가능하고 원한다면 기존 Win32 코드들 위에서 WinUI를 사용할 수 있게 해 주는 XAML Islands 기능이 추가되면서 마이그레이션을 점진적으로 할 수 있도록 하고 있다.
7. 그 외
A
함수와W
함수가 나뉘어 있다. 예시로MessageBox()
매크로를 예로 들면 컴파일 옵션에 따라 최종적으로MessageBoxA()
또는MessageBoxW()
로 변환되게 되는데 전자의 경우 시스템 로케일을 사용하며 (ANSI의 A) 후자는 유니코드 (UTF-16LE, Wide의 W)를 사용하게 된다.- 이러한 특징으로
TCHAR
이라는 매크로 자료형이 사용된다. 명시적으로 A 또는 W를 프로그래머가 알고서 사용하는 경우는 아무 문제가 되지 않지만 이해 없이 섞어 쓰는 경우 런타임 오류를 내기 때문. Windows 98
과 같이Windows NT
커널이 탑재되기 이전의 OS의 경우 이 Wide 버전을 지원하지 않았으며 별도의 호환성 레이어를 설치해야 이런 유니코드 기반 프로그램을 동작시킬 수 있었다.A
버전의 함수를 호출해도 커널 내부적으로는W
함수를 최종으로 실행하게 된다. (A()->MultiByteToWideChar()->W();)- Windows API라는것 자체는 Windows 1.0 때부터 이어져 온 것이다 보니 시대가 지나며 기능이 추가되거나 없어진것도 있고 아무 의미 없이 존재하는것이 있다. 대표적으로 엔트리포인트인
WinMain()
의hPrevInstance
가 있다.[10] - 마찬가지로 원형과
Ex
버전이 별도로 존재한다. 기능이 추가되었지만 하위호환성을 위해 원형을 남겨 둔 것으로CreateWindow()
가 있다. 본래 함수였으나 현 SDK는 매크로로 되어 있으며 실제로는CreateWindowEx()
를 실행시킨다. 위의 경우와 마찬가지로 Win16 시절의 잔재들이 이런 경우다. MAX_PATH
라는 260로 정의된 매크로가 있다. 이 값은 Windows API나 그 API를 사용하는 프로그램 전역에서 경로 관련해서 사용되는데 260이라는 수에서 보이듯이 파일의 전체 경로가 260자를 넘을 수 없다. 이는 Windows NT 커널과는 관계가 없는데, NTFS문서에서 보이듯이 MAX_PATH보다 긴 경로를 가지는 것이 가능하지만 이 제한은 하위호환성에 따른 잔재이다.
이를 우회하기 위해서는 UNC경로를 사용하거나 Windows 10 1607 부터 추가된 Application Manifest를 사용해서 프로그램이 260자보다 긴 경로에 대한 대비가 되어 있음을 선언하거나 레지스트리를 통해 시스템 전역으로 사용할 수 있는데 이는CreateFileW()
와 같은 유니코드 기반 프로그램에서만 이 제한을 넘을 수 있고CreateFileA()
같은 ASCII 기반의 API로는 여전히 260자로 제한된다.
유니코드여도 이 매크로를 사용해서wchar_t path[MAX_PATH];
같은 식으로 작성된 프로그램들은 여전히 260자로 제한된다.- 경로나 레지스트리 상관 없이 개별 파일명은 MAX_PATH의 제한을 받는다. Windows API중 파일 목록을 받아 오는 API가 사용하는
WIN32_FIND_DATA
의 멤버인cFileName
이 MAX_PATH로 고정되어 있기 때문. - 16비트, 32비트 환경의 x86 한정으로 콜링 컨벤션이 뒤섞여 있다. 64비트인 AMD64나 AArch64의 경우는 주로 사용되는 호출 규약이 하나뿐인 반면 32비트의 경우 여러 컨벤션이 사용된다. 16비트 시절 컴퓨터의 메모리가 한정적일 때 함수 호출에 필요한 메모리 사용을 줄이기 위해 피호출 함수가 스택 포인터를 정리하는 파스칼 방식을 사용하다 32비트 시대로 넘어와서 이 방식을 약간 변경해
stdcall
이라는 변종을 만들어서 사용했다.
다만 피호출자가 스택을 정리하는 방식은 C 언어의 가변인자를 사용할 수 없기 때문에 가변 인자가 필요한 부분은 소스코드에 명시적으로 cdecl을 사용해 코드를 생성하도록 지정 해 줘야 했으며 이 때문에 특히 심볼 로딩을 사용해서 코드를 작성 하는 프로그램들의 경우 이 부분을 까먹고 작성하지 않는 경우 런타임에서 오류가 생기거나 스택 메모리가 손상되는 등의 여러 장애물들이 있었다. - 공식적으로 API 자체는 일단 개요 문단에서 언급 된 언어만을 지원하지만 Microsoft의 프로젝트중 하나로 Rust를 위한 래퍼가 제작되고 있다. Win32라 불리는 전통적인 API셋 부터 모던 API인 WinRT 까지 사용이 가능하며 DirectX관련 API도 지원하고 있다. 또한 커널 드라이버를 위한 WDK또한 래핑이 되어 있다. https://github.com/microsoft/windows-rs
- 공식 API 문서와 헤더 이외에도 명시적으로 설명되지 않은 숨겨진 API들도 존재한다. 이런 API들은 보통 OS내부적으로 사용되는 API들이며 보통은 공개된 API를 실행시키면 이와 동일한 역할을 하는 숨겨진 API를 실행시키는 식으로 동작하는 경우가 보통이라 어지간하면 이런 숨겨진 API를 직접 쓸 일도 없고 공식적으로도 사용을 권장하지 않는다.
그럼에도 이런 API를 직접 사용하는 프로그램들이 존재하는데 보통 안티치트, 안티바이러스 또는 게임 핵같이 메모리를 직접 건들면서 탐지를 우회하는 등의 목적으로 사용되기도 한다.
어쨌든 공식적인 API가 아닌 관계로 커널 버전이 업데이트 되는 경우 호환성 보장을 할 이유도 없다 보니 동작이 변경되거나 삭제되는 등의 이유로 Windows Insider Preview와 같은 베타 버전을 사용하는 경우 이러한 소프트웨어들이 실행이 되지 않는 경우도 생긴다.
8. 관련 문서
[1] MFC, 비주얼 베이식, 델파이 등 윈도우에서 사용할 수 있는 모든 라이브러리는 실행 시 전부 내부적으로 어떻게든 변환을 거치게 된다.[2] 허큘리스, CGA, EGA, VGA같은 그래픽 드라이버와 각각의 프린터에 대한 제어 코드를 일일이 다 만들어서 워드프로세서 프로그램 안에 내장시켜 주어야 했다.[3] 다만 산업용으로 ISA 버스 등 하드웨어를 직접 건드려야 하는 곳에서는 여전히 사용한다. 응용프로그램이 아닌 드라이버 개발을 하는 경우에도 마찬가지.[4] "창"의 의미로 사용하는 그 윈도우를 의미한다.[5] Windows 9x에서도 Windows NT와의 호환성을 위해 ASCII와 유니코드 함수 모두 존재하는데, Windows 9x에서 유니코드 함수를 호출하여 내부적으로 ASCII로 변환시킨 후 ASCII 함수를 호출한다.[6] 사용자가 키보드를 누르거나 마우스를 움직였다거나, 기타 여러 가지의 자잘한 이벤트를 의미한다.[7]
LPARAM
, WPARAM
[8] 예를 들어서 마우스를 클릭하면 메세지로, 마우스 클릭 이벤트 메세지, x좌표, y좌표라고 메세지가 날라온다.[9] 참고로 개체에 대한 핸들을 만들 때 자식 프로세스으로의 상속 여부를 설정할 수 있다. 상속하도록 허용할 경우 자식 프로세스가 해당 핸들을 사용하여 개체에 접근할 수 있다.[10] 첫번째 창을 띄운뒤 두번째 창부터 클래스 등록을 생략하고 기존 클래스를 재사용하기 위한 용도였으나 32비트 윈도우부터 무조건 클래스를 등록하는 것으로 변경되며 그 역할이 사라졌으나, 기존 시스템과의 호환성을 위해 남겨져 있다.