Win32 C/C++에는 문자열을 나타내는 자료형이 다양하다. 그리고 그 자료형마다 용도와 역할이 있기 때문에 어느 하나만을 선택해서 사용할 수는 없고, 적절한 형태로 변환하는 과정이 일반적으로 쓰인다. 본 시리즈에서는 Win32 C/C++에서 사용하는 문자열의 종류에 대해 설명하고 상호 변환 방법에 대해 설명하겠다.
1. char, char *, const char *
C/C++은 기본적으로 ASCII 코드로 인코딩된 ANSI 문자열을 지원한다. 가장 기본적인 문자와 문자열의 형태이다.
char
char는 ASCII로 인코딩된 문자 하나를 표현하는 자료형으로서 1바이트의 크기를 갖는다.
char *
char *는 ASCII 문자로 구성되며 항상 끝이 1바이트의 NULL 문자로 끝나는 문자열을 나타내는 자료형이다.
const char *
const char *는 ASCII 문자로 구성되며 항상 끝이 1바이트의 NULL 문자로 끝나는 문자열 상수를 나타내는 자료형이다.
문자열과 문자열 상수에 대해 좀 더 설명하자면, 둘 다 끝이 NULL 문자로 끝나야 하는 특징이 있지만 문자열은 중간의 일부 문자를 변경할 수 있고 문자열 상수는 변경이 불가하다. 또한 문자열은 주로 버퍼에 복사된 문자열을 뜻하고, 문자열 상수는 주로 소스 코드를 작성할 때 처음부터 정해진 문자열을 뜻한다. 소스 코드에서부터 정해진 문자열은 실행파일 내에 내장되고, 프로그램이 적재될 때 함께 메모리에 로드되며 이는 프로그램이 진행되는 내내 변경될 수 없다.
1-1. Multibyte ANSI String
영어권 이외의 국가에서는 자국의 문자를 표현하기 위해 확장 ASCII 영역을 새롭게 정의해서 사용한다. 그러나 이것으로도 글자를 표현하기에 부족한 경우(예: 완성형 한글 및 한자)에는 확장 ASCII 문자 2개를 묶어서 자국의 문자를 할당하기도 하였다. 여기에서 영문자는 1바이트, 한글/한자는 2바이트 크기라는 규칙이 생겼다. 이러한 인코딩으로 된 문자열을 부르는 명칭에는 여러가지가 있겠으나 여기에서는 Multibyte ANSI String이라 하겠다.
Multibyte ANSI String도 컴퓨터의 입장에서는 ASCII 코드이므로 char *, const char * 형으로 표현가능하다. 단 자국 문자는 기본 2바이트이기 때문에 1 문자를 char 단독으로 저장할 수 없다. 예를 들어,
char a = 'A'; // 가능하다.
char b = '가'; // 불가능하다.
2. wchar_t, wchar_t *, const wchar_t *
영어권 이외의 국가를 고려하여 모든 문자가 n바이트의 크기를 갖는 Widechar 문자열을 지원하는 형식이다. Multibyte가 문자의 종류마다 1 문자당 크기(바이트)가 달라졌다면, Widechar는 모든 문자가 1 바이트 이상의 동일한 크기를 갖는 차이가 있다. 일반적으로 Windows 운영체제에서는 wchar_t의 크기가 2 바이트이며 UCS-2로 인코딩된다. Linux 운영체제에서는 wchar_t의 크기가 4 바이트이며 UCS-4로 인코딩되지만 컴파일러 설정에 따라 Windows 방식과 동일하게 변경 가능하다. (그러나 wchar_t를 쓸 경우 반드시 유니코드로 처리된다는 규칙은 없으므로 코딩에 참고하기 바란다.)
wchar_t
wchar_t는 Widechar로 표현된 1개 문자이다.
wchar_t *
wchar _t *는 Widechar로 표현된 문자열이며 1바이트 이상의 크기를 갖는 NULL 문자로 끝난다.
const wchar_t *
const wchar_t *는 wchar_t *와 동일하지만 상수 특성을 갖는다.
2-1. 문자열 리터럴 접두어
문자열 상수를 유니코드 문자열로 정의하기 위해서는 문자열 앞에 접두어를 붙이면 된다. C++11에서 정의된 문자열 리터럴literal 접두어의 종류로는 아무것도 안 붙임, L, u8, u, U의 5가지가 있다.
"문자열"
표준 ASCII 문자 및 Multibyte 문자로 구성된 ANSI 문자열이다.
L"문자열"
Widechar 문자로 구성된 문자열이다. 일반적으로 유니코드로 인코드된다.
이외의 문자열에 대해서는 다음 문단에서 이어진다.
3. 유니코드 문자로서의 char, char16_t, char32_t
char는 경우에 따라 ASCII 문자를 나타낼 때도 쓰이지만 유니코드(UTF-8) 문자를 나타낼 때도 쓰인다. 이는 일반적인 로마자에 대하여 ASCII와 Unicode가 호환되기 때문이다. 물론 로마자 이외의 문자는 유니코드로 표현된 char라고 해도 문자의 종류에 따라 Multibyte처럼 여러 바이트에 걸쳐져서 표현된다.
char16_t와 char32_t는 C++11부터 도입된 자료형으로 uchar.h 또는 cuchar에 정의되어 있습니다. 각각 UTF-16과 UTF-32로 인코드된 문자 및 문자열을 표현할 때 사용된다.
2-1. 유니코드 문자열 리터럴의 접두어
문자열 상수를 유니코드 문자열로 인코드 하고자 할 때는 u8, u, U의 세가지 중 하나를 사용한다.
u8"문자열"
문자열 상수를 UTF-8로 저장한다. 이 때는 char 자료형으로 문자와 문자열을 표현한다.
u"문자열"
문자열 상수를 UTF-16으로 저장한다. 이 때는 char16_t 자료형으로 문자와 문자열을 표현한다.
U"문자열"
문자열 상수를 UTF-32로 저장한다. 이 때는 char32_t 자료형으로 문자와 문자열을 표현한다.
4. Multibyte - Widechar간 상호 변환
지금까지는 C/C++ 표준에 정의된 자료형에 대한 설명이었다. 위 세 종류의 자료형으로된 문자열 및 문자열 상수를 상호변환해 보겠다.
Multibyte(ANSI 문자의 char 형)와 Widechar(wchar_t 형)의 상호 변환은 wctomb, mbtowc, wcstombs 또는 mbstowcs를 사용한다. 전자는 1개 문자를 변환하고 후자는 문자열을 변환하며 stdlib.h 또는 cstdlib 헤더파일에 정의되어 있다.
int mbtowc (wchar_t * pwc, const char * pmb, size_t max); // Multibyte에서 Widechar로 1개 문자를 변환
/* C++ Source */
#include <cstdlib>
#include <cassert>
#include <clocale>
using namespace std;
/* ... */
wchar_t wc = L'가'; // 변환할 1개의 Wide Character
char mbc[8] = { 0, }; // 인코딩을 고려할 때 1개의 Wide Character에 대한 충분한 Multi Byte 공간을 제공합니다. 대체로 1개 문자당 8 바이트를 부여하면 충분하다.
int mbn = 0; // 해당 문자가 Multibyte Character로 변환시 실제로 몇 바이트를 차지하는지 확인한다.
setlocale(LC_ALL, ""); // 변환에 앞서 프로그램의 로케일을 시스템에서 기본으로 설정한 로케일로 리셋한다.
/* 1개의 Wide Character를 Multibyte Character로 변환한다. */
/* 이 때 변환된 Multibyte Character의 바이트 수를 반환받는다. */
/* 지정한 Wide Character가 현재의 로케일에서 표현할 수 없는 문자일 때, -1이 반환된다. */
assert((mbn = wctomb(mbc, wc)) != -1);
printf("Wide Character = %lc [%d byte(s)]\n", wc, sizeof(wc));
printf("Multibyte Character = %s [%d byte(s)]\n", mbc, mbn);
/* ... */
4-2. Wide String에서 Multibyte String으로 변환
다음은 Wide 문자열(wchar_t *)에서 Multibyte 문자열(char */ANSI)로 변환하는 예이다.
/* C++ Source */
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <clocale>
using namespace std;
/* ... */
wchar_t wcs[] = L"안녕하세요."; // 변환할 Wide String
char mbs[112] = { 0, }; // 인코딩을 고려할 때 1개의 Wide Character에 대한 충분한 Multi Byte 공간을 제공합니다. 대체로 1개 문자당 8 바이트를 부여하면 남아돕니다.
int mbn = 0; // 해당 문자가 Multibyte Character로 변환시 실제로 몇 바이트를 차지하는지 확인합니다.
setlocale(LC_ALL, ""); // 변환에 앞서 프로그램의 로케일을 시스템에서 기본으로 설정한 로케일로 리셋합니다.
/* Wide String을 Multibyte String으로 변환한다. */
/* 이 때 변환된 Multibyte String의 바이트 수를 반환받는다. */
/* 지정한 Wide Character가 현재의 로케일에서 표현할 수 없는 문자일 때, ((size_t)(-1))이 반환된다. */
/* ((size_t)(-1))은 컴파일러에 따라 0xFFFF, 0xFFFFFFFF, 0xFFFFFFFFFFFFFFFF 중 하나를 말한다. */
assert((mbn = wcstombs(mbs, wcs, sizeof(mbs))) != ((size_t)(-1)));
printf("Wide String = %ls [%d byte(s)]\n", wcs, sizeof(wcs));
printf("Multibyte Character = %s [%d byte(s)]\n", mbs, mbn);
/* ... */
4-3. Multibyte Character에서 Wide Character으로 변환
다음은 Multibyte Character를 Wide Character로 변환하는 예이다.
/* C++ Source */
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <clocale>
using namespace std;
/* ... */
char mbc[] = "가"; // 1개의 문자를 n바이트에 저장
size_t mbn = sizeof(mbc) - 1; // 끝의 NULL을 제외한 순수 1글자의 크기(바이트)
wchar_t wc[4] = { L'\0', }; // 1개의 문자를 1개의 Widechar에 저장, Surrogate로 변환될 것을 가정하여 1문자당 최대 4개의 wchar_t를 확보해 놓으면 넉넉하다.
setlocale(LC_ALL, ""); // 변환에 앞서 프로그램의 로케일을 시스템에서 기본으로 설정한 로케일로 리셋한다.
/* Multibyte Character를 Wide Character로 변환한다. */
/* 이 로케일에서 정의하는 문자가 Wide Character로 표현될 수 없을 경우 -1을 반환한다. */
assert(mbtowc(wc, mbc, mbn) != -1);
printf("Multibyte Character = %s [%d byte(s)]\n", mbc, mbn);
printf("Wide Character = %ls [%d byte(s)]\n", wc, sizeof(wc));
/* ... */
4-4. Multibyte String에서 Wide String으로 변환
다음은 Multibyte 문자열을 Widechar 문자열로 변환하는 예이다.
/* C++ Source */
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <clocale>
/* ... */
char mbs[] = "안녕하세요."; // 변환할 Multibyte String
wchar_t wcs[28] = { L'\0', }; // 인코딩을 고려할 때 1개의 Multibyte Character에 대한 충분한 Wide String 공간을 제공한다. Surrogate로 변환될 것을 가정하여 1개 문자당 최대 4개의 wchar_t를 확보해 놓으면 넉넉하다.
size_t wcn = 0; // 실제로 변환되어 생성된 문자 수를 받아온다.
setlocale(LC_ALL, ""); // 변환에 앞서 프로그램의 로케일을 시스템에서 기본으로 설정한 로케일로 리셋한다.
/* Multibyte String를 Wide String으로 변환한다. */
/* 이 로케일에서 정의하는 문자가 Wide String으로 표현될 수 없을 경우 -1을 반환한다. */
assert((wcn = mbstowcs(wcs, mbs, sizeof(wcs) / sizeof(wchar_t))) != ((size_t)(-1)));
/* ... */
4-5. (덧) Multibyte String과 Wide String의 문자 개수 세는 방법
strlen 함수는 문자열을 구성하는 모든 문자가 표준 ASCII 코드로 표현된 ANSI 문자일 경우에 유효하다. 그러나 KS 완성형 코드나 JIS 코드 등 특정 인코딩을 사용하는 시스템일 경우 strlen 함수가 반환하는 문자의 수가 실제와 다르다. Multibyte String일 때 사용하는 함수는 mblen이다.
int mblen (const char* pmb, size_t max);
pmb
문자의 개수를 구하고자 하는 Multibyte String이다.
max
pmb 버퍼의 최대 크기이다.
Wide String의 문자수를 세는 함수는 wcslen이다. 사용법은 strlen과 같다.
5. Wide Character - Unicode간 상호 변환
Wide Character/String과 Unicode간 변환을 지원하는 표준 함수는 이 포스트가 작성된 현재 없다. 이 경우 다음에 설명되는 Windows API를 사용한다.
6. Multibyte Character - Unicode간 상호 변환
Multibyte Character와 UTF-16/UTF-32간 변환은 표준 함수로 제공된다. 그 외 UTF-7/UTF-8은 이 포스트가 작성된 현재 표준에 없다. 이 경우 다음에 설명되는 Windows API를 사용한다.
6-1. Multibyte Character에서 UTF-16으로 변환
Multibyte Character에서 UTF-16 Character로 변환하기 위해서는 mbrtoc16 함수를 사용합니다.
딥웹 .onion 익명 사이트 개설(호스팅)하기 (Tor를 사용한 .onion 주소 할당 방법)
일반적인 검색엔진(구글, 네이버, 다음 등)으로 검색되지 않는 웹 사이트들이 존재하는 통신망을 딥웹(Deep Web), 심층 웹, 깊은 웹 등이라 부른다.
Tor 네트워크는 딥 웹의 한 종류이며 전용 브라우저인 Tor Browser를 사용하여 접속할 경우 여러 프록시 서버들을 거치며 그 내용 또한 암호화되어 전송되기 때문에 익명으로 인터넷을 이용할 수 있고, warning.or.kr처럼 특정 국가에서 차단해 놓은 사이트들을 접근할 수도 있다. Tor 네트워크에 존재하는 웹 사이트들은 통상적인 도메인이 아닌 .onion으로 끝나는 해쉬hash 형태의 주소를 가지는데, 이번 포스팅에서는 Windows 상에서 간단하게 웹 서버를 구축하고 .onion 주소를 할당받는 방법을 소개하겠다.
1 단계. 웹 서버 구성하기
포트가 충돌할 우려가 있으니 기존 사이트를 운영하던 서버에 Deep Web 사이트를 설정하는 것보다는 Deep Web 사이트만을 위한 서버를 따로 마련하는 것이 권장된다. 그리고 이 항목은 Apache나 IIS 등의 서버를 설치하고 구성한 경우라면 건너뛰어도 된다. 여기에서는 XAMPP(Apache + MySQL + PHP + Perl)을 이용해 서버를 구성해보겠다.
UAC 때문에 원활히 작동되지 않을 수 있으므로 Program Files 폴더는 피해서 설치할 것을 권장한다.[Next >] 버튼을 누른다.구성 요소를 선택한다. 특별히 고칠 것이 없으므로 [Next >] 버튼을 누른다.설치 경로는 그대로 두고 [Next >] 버튼을 누른다.[Next >] 버튼을 누른다.설치가 진행되고 있다. 시간이 다소 걸리므로 완료될 때까지 기다린다.제어판을 실행해야 하므로 선택 후 [Next >] 버튼을 누른다.언어 선택하고...
Onion 사이트의 페이지는 80번 포트를 통해 Tor 네트워크로 나갈 것이다. 이는 Apache가 내보내는 포트의 기본값과 같으므로 충돌을 방지하기 위해 Apache가 사이트를 내보내는 포트를 다른 번호로 바꿔줘야 하다. 다음과 같이 XAMPP 제어판이 실행되었다면, Apache에서 [Config] 버튼 -> [Apache (httpd.conf)]를 클릭한다.
XAMPP 제어판[Apache (httpd.conf)]를 클릭80번 포트에서 8080 포트로 수정방화벽 예외에 추가
다음과 같이 XAMPP 제어판에서 8080번 포트로 웹 사이트가 게시되고 있음을 확인할 수 있다.
8080번 포트로 게시되고 있음
xampp\htdocs 폴더에 웹 사이트로 게시되는 파일들을 저장한다. 기본 제공되는 테스트 파일들은 모두 제거한 후 다음과 같은 내용의 html 파일을 작성하여 index.html로 저장한다.
구 버전의 Tor에서는 Tor Browser라는 Tor 네트워크 브라우저와 Vidalia라는 Tor 네트워크 제어판이 분리되었는데, 현재 나오는 버전의 Tor는 Tor Browser로 일원화되어있다. 따라서 Tor Browser만 종료하지 않고 실행해 두면 항상 Tor 네트워크 접속 상태가 유지될 것이다. 서버의 유동 IP/고정 IP 여부도 염려할 필요가 없다. 외부에서 접속할 때는 IP가 아닌 onion 주소를 사용하며, IP 주소와 onion 주소의 대응은 Tor 네트워크에서 알아서 해 줄 것이다.
Tor Browser가 설치된 경로(\Tor Browser\Browser\TorBrowser\Data\Tor)에 들어가면 torrc라는 0 바이트짜리 파일이 보일 것이다. Tor Browser를 처음으로 실행한 후 이 파일을 다시 찾을 것이다.
torrc가 0바이트이다.
설치된 경로(\Tor Browser\Start Tor Browser.lnk)를 실행한다.
[Connect] 버튼을 눌러 기본값으로 Tor 네트워크에 접속한다.Tor 네트워크에 접속되었다면 다시 닫는다.
3 단계. 포트 설정하기
앞서 확인했던 경로(\Tor Browser\Browser\TorBrowser\Data\Tor)를 다시 열면 0 바이트였던 torrc 파일의 크기가 늘어난 것을 확인할 수 있다. 텍스트 편집기로 이를 열어서 내용을 확인한다.
크기가 증가한 torrc 파일을 연다.
아래의 두 줄을 새로 추가한다. 첫 번째 줄은 xampp로 게시되는 로컬 경로를 Tor 네트워크에게 알려주는 것이고, 두 번째 줄은 서버의 아이피와 xampp로 게시되는 일반 웹의 포트번호(여기서는 8080)를 딥 웹으로는 80번 포트로 재게시 하는 것이다.
HiddenServiceDir 사이트의로컬경로
HiddenServicePort 80 서버의아이피:서버의포트번호
torrc 파일을 수정한다.
Tor Browser를 다시 실행한다. 이 때부터는 htdocs의 로컬 경로가 하나의 Deep Web 사이트로서 Tor 네트워크에 내보내지고 있으므로 닫으면 외부에서 접속이 안 된다.
4 단계. onion 주소 확인하기
torrc 파일에서 HiddenServiceDir 항목으로 지정한 로컬 경로로 들어가보면 몇 개의 파일이 생성된 것을 볼 수 있다. 이 중 hostname 파일을 열어보면 이 사이트의 onion 주소를 확인할 수 있다.
서버 경로에 생성된 몇 개의 파일hostname에 기록된 현 서버의 onion 주소
Tor Browser를 실행하고 약 1분이 지난 후 이 주소를 접속하면 일반 웹 브라우저에서 http://localhost:8080로 보았던 것과 동일한 웹 페이지를 볼 수 있다. 즉, 외부 사용자도 이 onion 주소를 통해 같은 페이지를 보고 있다는 뜻이다. 이렇게 해서 하나의 Deep Web 사이트 구성이 완료되었다.
Tor Browser를 통해 접속한 웹 사이트의 모습
마무리
이번 포스팅에서는 Tor Browser와 xampp를 활용하여 딥 웹 사이트를 운영하는 서버를 구축하여 보았다. 다음 포스팅에서는 커스텀 onion 주소를 할당받는 방법에 대해 설명하겠다.
WM_PAINT(MFC: OnPaint) 이벤트는 창의 내용을 그릴 때 호출되는 이벤트이다. 이 이벤트에 DirectX 9.0을 적용하여 보겠다.
IDirect3DDevice9::TestCooperativeLevel
로스트 상태는, DirectX 객체와 실제 그래픽 어댑터 사이의 연결이 끊어진 상태를 뜻한다. DirectX 객체가 생성된 시점과 이 객체를 이용하여 실제로 그리는 시각 시점의 상대적으로 긴 시간이 있는 경우 이 사이에 여러가지 이유(예: 로그오프, 절전모드 등)로 객체와 장치간 연결이 끊어질 수 있는데 이는 IDirect3DDevice9::TestCooperativeLevel함수로 체크할 수 있다.
만일 DirectX와 장치간 연결이 정상이 아닐 경우 그 원인에 따라 D3DERR_DRIVERINTERNALERROR, D3DERR_DEVICELOST, D3DERR_DEVICENOTRESET 중 하나를 반환하는데, 이 때는 IDirect3DDevice9::CreateDevice에 사용되었던 D3DPRESENT_PARAMETERS 구조체를 다시 사용하여 IDirect3DDevice9::Reset 함수를 실행하여 장치와 객체의 연결을 리셋한다.
/* C++ Source */
IDirect3DDevice * pDirect3DDevice;
D3DPRESENT_PARAMETERS d3dPresentParameters;
HRESULT hResult;
// ...
hResult = pDirect3DDevice->TestCooperativeLevel();
switch (hResult)
{
case D3DERR_DRIVERINTERNALERROR:
pDirect3DDevice->Reset(&d3dPresentParameters); // D3DPRESENT_PARAMETERS *
// Reset 실패 시 각종 조치
break;
case D3DERR_DEVICELOST:
// Reset 실패 시 각종 조치
pDirect3DDevice->Reset(&d3dPresentParameters); // D3DPRESENT_PARAMETERS *
break;
case D3DERR_DEVICENOTRESET:
// Reset 실패 시 각종 조치
pDirect3DDevice->Reset(&d3dPresentParameters); // D3DPRESENT_PARAMETERS *
break;
}
IDirect3DDevice9::Clear
장치의 로스트 상태 확인 및 조치를 완료하였다면 지정된 위치의 버퍼를 특정 색상으로 지운다.
/* C++ Source */
IDirect3DDevice * pDirect3DDevice;
D3DPRESENT_PARAMETERS d3dPresentParameters;
DWORD dwRects;
D3DRECT * pRects;
HRESULT hResult;
// ...
hResult = pDirect3DDevice->Clear
(
0, // DWORD Count : pRects 배열의 원소 수
NULL, // const D3DRECT * pRects
D3DCLEAR_TARGET, //
D3DCOLOR_XRGB(0x00, 0xFF, 0xFF), // D3DCOLOR Color : 리셋 후 버퍼의 색상
0.0f, // float Z
0 // DWORD Stencil
);
if (FAILED(hResult))
{
// Clear 실패 시 각종 조치
}
DirectX 9.0을 시작하기 위해 객체의 생성을 하고 창을 닫을 때의 해제는 다음과 같이 한다. DirectX 객체는 COMComponent Object Model으로 제공되므로 C++ 언어를 사용한다.
Direct3DCreate9, IDirect3D9::Release
DirectX 9.0의 객체 생성은 Direct3DCreate9 함수를 실행한다. 성공하면 LPDIRECT3D9(struct IDirect3D9 *) 형식의 객체를 반환하고, 실패하면 NULL을 반환한다.
/* C++ Source */
#pragma comment(lib, "d3d9.lib") // DirectX 9.0 라이브러리를 링크
#include <Windows.h> // Windows API에서 제공되는 기본 자료형들을 불러오기
#include <d3d9.h> // DirectX 9.0 API를 불러오기
LPDIRECT3D9 lpDirect3D = (LPDIRECT3D9)NULL; // DirectX 9.0 객체
/* 창을 생성하거나 그래픽을 리셋하는 단계에서 */
lpDirect3D = Direct3DCreate9(D3D_SDK_VERSION); // DirectX 9.0 버전으로 객체를 생성한다.
if (!lpDirect3D)
{
// 생성에 실패한 경우 실행 작업
}
/* 생성에 성공한 경우 실행 작업 */
DirectX 9.0 객체의 해제는 IDirect3D9::Release를 호출한다.
/* C++ Source */
#pragma comment(lib, "d3d9.lib") // DirectX 9.0 라이브러리를 링크
#include <Windows.h> // Windows API에서 제공되는 기본 자료형들을 불러오기
#include <d3d9.h> // DirectX 9.0 API를 불러오기
LPDIRECT3D9 lpDirect3D = (LPDIRECT3D9)NULL; // DirectX 9.0 객체
/* 창을 닫거나 그래픽을 종료하는 단계에서 */
lpDirect3D->Release(); // 객체 해제
/* 이후 작업 */
실제 코드에 적용
다음은 MFC에서 DirectX 객체를 생성하고 해제하는 예이다. CTestFrame은 CWndFrame에서 상속된 클래스이고 m_pDirect3D는 사전에 클래스 헤더에 선언된 멤버 변수이다.
/* MFC Source */
int CTestFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
HRESULT hResult = NULL;
int ret = 0;
TRACE(TEXT("[SUCCESS] CTestFrame::OnCreate called.\n"));
ret = CFrameWnd::OnCreate(lpCreateStruct);
if (ret != 0)
{
TRACE(TEXT("[FAILURE] CTestFrame::OnCreate.\n"));
return ret;
}
this->m_pDirect3D = Direct3DCreate9(D3D_SDK_VERSION);
if (this->m_pDirect3D == NULL)
{
TRACE(TEXT("[FAILURE] Direct3DCreate9.\n"));
return ret;
}
return ret;
}
void CTestFrame::OnDestroy()
{
TRACE(TEXT("[SUCCESS] CTestFrame::OnDestroy called.\n"));
if (this->m_pDirect3D != NULL)
{
this->m_pDirect3D->Release();
}
CFrameWnd::OnDestroy();
}
MFC 프로그램의 실행 결과 (아직 빈 창)OnCreate와 OnDestroy이 실행된 흔적
어댑터 종류 열거하기
DirectX를 사용하기 위해 컴퓨터에서 지원 가능한 그래픽 어댑터의 종류를 모두 가져오려면 CreateDXGIFactory 함수로 생성된 IDXGIFactory형 객체를 사용한다.
CreateDXGIFactory
/* C++ Source */
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "dxgi.lib")
#include <d3d9.h>
#include <dxgi.h>
IDXGIFactory * pDXGIFactory = NULL;
IDXGIAdapter * pDXGIAdapter = NULL;
SIZE_T nDXGIAdapter = 0;
/* 창을 생성하거나 그래픽을 리셋하는 단계에서 */
hResult = CreateDXGIFactory
(
__uuidof(IDXGIFactory), // REFIID riid
reinterpret_cast<void **>(&pDXGIFactory) // void ** ppFactory
);
// 해당 인덱스 번호에 해당하는 어댑터가 없다면 DXGI_ERROR_NOT_FOUND를 반환하기 시작한다.
// 0번 어댑터부터 검색을 시작한다.
nDXGIAdapter = 0;
while (pDXGIFactory->EnumAdapter(nDXGIAdapter, &pDXGIAdapter) != DXGI_ERROR_NOT_FOUND)
{
nDXGIAdapter++; // 다음 번호의 어댑터를 확인한다.
}
실제 코드에 적용
그래픽 어댑터의 종류를 열거하는 위 코드를 응용하여 디버그 문자열로 그래픽 어댑터의 이름을 출력해보겠다.
/* MFC Source */
int CTestFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
HRESULT hResult = NULL;
int ret = 0;
TRACE(TEXT("[SUCCESS] CTestFrame::OnCreate called.\n"));
ret = CFrameWnd::OnCreate(lpCreateStruct);
if (ret != 0)
{
TRACE(TEXT("[FAILURE] OnCreate::OnCreate.\n"));
return ret;
}
// DirectX 객체를 생성
this->m_pDirect3D = Direct3DCreate9(D3D_SDK_VERSION);
if (this->m_pDirect3D == NULL)
{
TRACE(TEXT("[FAILURE] Direct3DCreate9.\n"));
return ret;
}
// 어댑터를 열거할 수 있는 IDXGIFactory 객체 생성
IDXGIFactory * pDXGIFactory = NULL;
hResult = CreateDXGIFactory
(
__uuidof(IDXGIFactory), // REFIID riid
reinterpret_cast<void **>(&pDXGIFactory) // void ** ppFactory
);
if (FAILED(hResult))
{
TRACE(TEXT("[FAILURE] CreateDXGIFactory.\n"));
return ret;
}
// 열거된 어댑터를 하나씩 배열에 보관
CArray<IDXGIAdapter *> dxgiAdapters;
IDXGIAdapter * pDXGIAdapter = NULL;
UINT i = 0;
while (TRUE)
{
hResult = pDXGIFactory->EnumAdapters
(
i++, // UINT Adapter
&pDXGIAdapter // IDXGIAdapter ** ppAdapter
);
if (hResult == DXGI_ERROR_NOT_FOUND) break;
dxgiAdapters.Add(pDXGIAdapter);
pDXGIAdapter = NULL;
}
// 배열에 보관된 어댑터의 이름을 디버그 문자열로 출력
for (INT_PTR i = 0; i < dxgiAdapters.GetCount(); i++)
{
DXGI_ADAPTER_DESC dxgiAdapterDesc;
TCHAR szBuffer[256];
dxgiAdapters[i]->GetDesc(&dxgiAdapterDesc);
TRACE(TEXT("[%2d] DXGI_ADAPTER_DESC.Description = %s\n"), i, dxgiAdapterDesc.Description);
}
// 사용이 끝난 어댑터 객체와 Factory 객체 해제
for (INT_PTR i = 0; i < dxgiAdapters.GetCount(); i++)
{
dxgiAdapters[i]->Release();
}
dxgiAdapters.RemoveAll();
pDXGIFactory->Release();
return ret;
}
본 컴퓨터에 장착된 그래픽 어댑터의 종류가 열거되는 것을 확인할 수 있다. 다만 [장치 관리자]에는 없는 Microsoft Basic Render Driver가 마지막에 있을 수도 있다.
컴퓨터에 장착된 그래픽 어댑터의 종류가 열거되는 결과
그래픽 어댑터의 색상 포맷의 지원 여부 확인
ARGB, RGB, HSV 등등의 색상 포맷을 사용하고자 할 때 그래픽 어댑터가 이를 지원하는지 여부를 확인한다. 여기서 XRGB는 RGB와 같은 것을 의미한다.
IDirect3D9::EnumAdapterModes
/* C++ Source */
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "dxgi.lib")
#include <d3d9.h>
#include <dxgi.h>
LPDIRECT3D9 lpDirect3D = (LPDIRECT3D9)NULL; // DirectX 9.0 객체
UINT uAdapterMode = 0;
/* 창 또는 그래픽을 리셋하는 단계에서 */
uAdapterMode = lpDirect3D->GetAdapterModeCount
(
D3DADAPTER_DEFAULT, // UINT Adapter : 시스템 기본 어댑터를 사용하겠다
D3DFMT_X8R8G8B8 // D3DFORMAT Format : 픽셀당 32비트 색상 포맷을 쓴다. 단, 실제 사용되는 비트는 R, G, B 24비트이다.
);
본 컴퓨터의 경우 RGB(X8R8G8B8) 색상 포맷으로 43가지의 어댑터 모드를 지원한다.
IDirect3D9::EnumAdapterModes
IDirect3D9::EnumAdapterModes 함수는 그래픽 어댑터가 지원 가능한 모드를 상세히 열거하는 함수이다. 지원 모드는 D3DDISPLAYMODE 구조체를 통해 반환된다.
/* C++ Source */
UINT uAdapterMode = this->m_pDirect3D->GetAdapterModeCount
(
D3DADAPTER_DEFAULT, // UINT Adapter
D3DFMT_X8R8G8B8 // D3DFORMAT Format
);
for (UINT i = 0; i < uAdapterMode; i++)
{
D3DDISPLAYMODE d3dDisplayMode;
hResult = this->m_pDirect3D->EnumAdapterModes
(
D3DADAPTER_DEFAULT, // UINT Adapter
D3DFMT_X8R8G8B8, // D3DFORMAT Format
i, // UINT Mode : 어댑터에 대해 얻고자 하는 모드의 번호
&d3dDisplayMode // D3DDISPLAYMODE * pMode : 반환 정보
);
}
실제 코드에 적용
기본 어댑터(D3DADAPTER_DEFAULT)의 RGB 컬러(D3DFMT_X8R8G8B8)로 몇 가지의 어댑터 모드가 지원 가능한지 조회해 보겠다. 실행 결과는 컴퓨터마다 상이하다.
/* MFC Source */
int CTestFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
HRESULT hResult = NULL;
int ret = 0;
TRACE(TEXT("[SUCCESS] CTestFrame::OnCreate called.\n"));
ret = CFrameWnd::OnCreate(lpCreateStruct);
if (ret != 0)
{
TRACE(TEXT("[FAILURE] OnCreate::OnCreate.\n"));
return ret;
}
this->m_pDirect3D = Direct3DCreate9(D3D_SDK_VERSION);
if (this->m_pDirect3D == NULL)
{
TRACE(TEXT("[FAILURE] Direct3DCreate9.\n"));
return ret;
}
IDXGIFactory * pDXGIFactory = NULL;
hResult = CreateDXGIFactory
(
__uuidof(IDXGIFactory), // REFIID riid
reinterpret_cast(&pDXGIFactory) // void ** ppFactory
);
if (FAILED(hResult))
{
TRACE(TEXT("[FAILURE] CreateDXGIFactory.\n"));
return ret;
}
UINT uAdapterMode = this->m_pDirect3D->GetAdapterModeCount
(
D3DADAPTER_DEFAULT, // UINT Adapter
D3DFMT_X8R8G8B8 // D3DFORMAT Format
);
TRACE(TEXT("[SUCCESS] GetAdapterModeCount = %u\n"), uAdapterMode);
for (UINT i = 0; i < uAdapterMode; i++)
{
D3DDISPLAYMODE d3dDisplayMode;
hResult = this->m_pDirect3D->EnumAdapterModes
(
D3DADAPTER_DEFAULT, // UINT Adapter
D3DFMT_X8R8G8B8, // D3DFORMAT Format
i, // UINT Mode : 어댑터에 대해 얻고자 하는 모드의 번호
&d3dDisplayMode // D3DDISPLAYMODE * pMode : 반환 정보
);
if (SUCCEEDED(hResult))
{
TRACE(TEXT("[%2u] EnumAdapterModes\n"), i);
TRACE(TEXT(" D3DDISPLAYMODE.Format = %x\n"), d3dDisplayMode.Format);
TRACE(TEXT(" D3DDISPLAYMODE.RefreshRate = %u\n"), d3dDisplayMode.RefreshRate);
TRACE(TEXT(" D3DDISPLAYMODE.Width = %u\n"), d3dDisplayMode.Width);
TRACE(TEXT(" D3DDISPLAYMODE.Height = %u\n"), d3dDisplayMode.Height);
}
}
return ret;
}
RGB 모드로 지원 가능한 43개의 어댑터 모드에 대해 프레임 속도와 해상도가 열거되고 있다.
어댑터 장치의 성능 확인하기
장치의 성능 중에서 특히 자주하게 확인할 것은 하드웨어로 버텍스 처리가 가능한지 여부이다. 하드웨어에서 버텍스 프로세싱이 가능하면 그대로 이용하고, 그렇지 않을 경우 소프트웨어(CPU)로 버텍스를 연산해야 한다. 이 경우 체감 속도가 다소 느릴 수 있다.
IDirect3D9::GetDeviceCaps
IDirect3D9::GetDeviceCaps 함수가 반환하는 구조체인 D3DCAPS9의 DevCaps 멤버를 통해 확인이 가능하며 D3DDEVCAPS_HWTRANSFORMANDLIGHT 비트의 설정 여부로 하드웨어의 버텍스 처리 성능을 판단할 수 있다.
/* C++ Source */
D3DCAPS9 d3dCaps;
ZeroMemory(&d3dCaps, sizeof(D3DCAPS9));
hResult = this->m_pDirect3D->GetDeviceCaps
(
D3DADAPTER_DEFAULT, // UINT Adapter
D3DDEVTYPE_HAL, // D3DDEVTYPE DeviceType
&d3dCaps // D3DCAPS9 * pCaps
);
if (FAILED(hResult))
{
// 장치 정보 얻기에 실패시 조치
}
if ((d3dCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) != 0)
{
// 하드웨어로 버텍스 처리가 가능하다.
}
else
{
// 소프트웨어로 버텍스 처리가 가능하다. (속도 느림)
}
실제 코드에 적용
실제 코드에 적용하여 보겠다.
/* MFC Source */
int CTestFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
HRESULT hResult = NULL;
int ret = 0;
TRACE(TEXT("[SUCCESS] CTestFrame::OnCreate called.\n"));
ret = CFrameWnd::OnCreate(lpCreateStruct);
if (ret != 0)
{
TRACE(TEXT("[FAILURE] OnCreate::OnCreate.\n"));
return ret;
}
this->m_pDirect3D = Direct3DCreate9(D3D_SDK_VERSION);
if (this->m_pDirect3D == NULL)
{
TRACE(TEXT("[FAILURE] Direct3DCreate9.\n"));
return ret;
}
D3DCAPS9 d3dCaps;
ZeroMemory(&d3dCaps, sizeof(D3DCAPS9));
hResult = this->m_pDirect3D->GetDeviceCaps
(
D3DADAPTER_DEFAULT, // UINT Adapter
D3DDEVTYPE_HAL, // D3DDEVTYPE DeviceType
&d3dCaps // D3DCAPS9 * pCaps
);
if (FAILED(hResult))
{
TRACE(TEXT("[FAILURE] IDirect3D9::GetDeviceCaps = %u"), hResult);
return ret;
}
if ((d3dCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) != 0)
{
TRACE(TEXT("[DEVCAPS] D3DCAPS9.DevCaps has D3DDEVCAPS_HWTRANSFORMANDLIGHT\n"));
}
else
{
TRACE(TEXT("[DEVCAPS] D3DCAPS9.DevCaps not D3DDEVCAPS_HWTRANSFORMANDLIGHT\n"));
}
return ret;
}
실행 결과 하드웨어 버텍스 처리가 가능하면 위와 같이 출력될 것이다.
IDirect3DDevice9 객체 생성
그래픽 어댑터를 호출할 수 있는 장치 인터페이스인 IDirect3DDevice9 객체를 생성한다. 앞서 확인한 각종 하드웨어 사항과 본인이 선정한 색상 포맷, 기타 필요한 사항들을 조합하여 하나의 컨텍스트를 만든 다음 이를 IDirect3D9::CreateDevice함수에 D3DPRESENT_PARAMETERS 구조체로 전달한다.
전통적인 배열 반복문은 역시 인덱스 변수를 선언하고 그 변수를 증감하면서 배열의 원소에 순차적으로 접근하는 방식이다.
/* C++ source */
#include <array> // std::array
#include <iostream> // std::cout
using namespace std;
int main(int argc, char * argv[])
{
std::array<int, 10> integers = { 2, 4, 6, 8, 0, 9, 7, 5, 3, 1 };
for (size_t i = 0; i < integers.size(); i++)
{
cout<<integers[i]<<'\t';
}
return 0;
}
2. 반복자 사용하기
STL Container 클래스내의 반복자(iterator)를 자동변수로 선언하여 배열의 원소에 순차적으로 접근할 수도 있다. 여기에서 반복자의 자료형은 std::array<int, 10>::iterator이지만 작성의 편의를 위해 auto 키워드로 대체하여 반복자를 선언하는 것이 보통이다.
OpenCL은 실행을 위한 세팅 과정이 너무 많다. 따라서 본 포스팅을 통해 OpenCL을 실행하는 예시 코드를 정리해 둔다.
0 단계. 사용 가능한 플랫폼 개수 확인하기.
OpenCL을 사용할 수 있는 플랫폼(CPU와 GPU 등 디바이스 조합)이 총 몇 개인지를 확인하기 위해 clGetPlatformIDs 함수를 사용한다. 2 번째 매개변수인 cl_platform_id * platforms에 NULL을 대입하면 플랫폼의 총 개수가 반환된다. 이 때 플랫폼의 개수는 1 번째 매개변수인 cl_uint num_entries 이하로 반환한다. 그러므로 최대의 개수를 얻고자 하면 1 번째 매개변수에 -1을 대입하면 된다. 이 매개변수의 자료형은 부호 없는 정수(cl_uint)이기 때문에 -1은 0xFFFF, 0xFFFFFFFF, 0xFFFFFFFFFFFFFFFF 중에 하나이고 부호 없는 정수의 최댓값과 같다.
사용 가능한 플랫폼의 수는 3 번째 매개변수인 cl_uint * num_platforms으로 반환된다. 함수 자체가 반환하는 값은 함수의 실행 결과로서, 정상 작동했다면 CL_SUCCESS를 반환한다.
/* C source */
// 새로 추가된 변수
cl_int errNo;
cl_uint nPlatformId = 0;
cl_platform_id * lpPlatformId = NULL;
/* ... */
// 0 단계. 시스템으로부터 OpenCL이 사용 가능한 플랫폼의 수를 확인한다.
errNo = clGetPlatformIDs
(
(cl_uint)(-1), // cl_uint num_entries
NULL, // cl_platform_id * platforms
&nPlatformId // cl_uint * num_platforms
);
if (errNo != CL_SUCCESS)
{
printf("[FAILURE] clGetPlatformIDs = %d\n", errNo);
// 실패 시 작업
}
else if (nPlatformId <= 0)
{
printf("[FAILURE] clGetPlatformIDs: nPlatformId = %u", nPlatformId);
// 실패 시 작업
}
else
{
printf("[SUCCEED] clGetPlatformIDs: nPlatformId = %u\n", nPlatformId);
}
/* ... */
사용 가능한 플랫폼의 수(여기서는 1개)를 확인할 수 있다.
1 단계. 사용 가능한 플랫폼 목록 가져오기.
앞서 0 단계에서 플랫폼 수를 확인하였다면, 메모리를 동적할당하여 플랫폼 목록을 가져온다. 마찬가지로 clGetPlatformIDs를 사용한다.
컨텍스트에는 CPU, GPU를 포함한 가속 장치들이 포함되어 있다. 현재 컨텍스트에 몇 개의 장치들이 있고, 각각의 장치가 무엇인지 확인하기 위해 다음과 같이 코드를 작성한다.
3-1. 장치 정보를 얻기 위한 버퍼의 크기 구하기
컨텍스트에 포함된 장치들의 목록을 얻기 위해 총 몇 바이트의 버퍼가 필요한지 확인하는 과정이다. clGetContextInfo를 사용하며 매개변수에 param_value_size = 0, param_value = NULL을 하고 param_value_size_ret =&변수를 하면 변수를 통해 메모리의 크기가 반환된다.
/* C source */
cl_int errNo;
cl_uint nPlatformId = 0;
cl_platform_id * lpPlatformId = NULL;
size_t i, j;
cl_uint nContextProperties = 0;
cl_context_properties * lpContextProperties = NULL;
cl_context context;
size_t cbDeviceBuffer = 0;
size_t nDeviceId = 0;
cl_device_id * lpDeviceId = NULL;
size_t nCommandQueue = 0;
cl_command_queue * lpCommandQueue = NULL;
cl_program program;
/* ... */
errNo = clBuildProgram
(
program, // cl_program program : 컴파일하고자 하는 프로그램 객체
0, // cl_uint num_devices : 컴파일 대상이 될 장치의 개수 (0개 = 컨텍스트에 있는 기본 장치들)
NULL, // const cl_device_id * device_list : 컴파일 대상이 될 장치 (NULL = 컨텍스트에 있는 기본 장치들)
NULL, // const char * options : 빌드 옵션
NULL, // void (*pfn_notify)(cl_program, void * user_data) : 콜백 함수
NULL // void * user_data : 콜백함수에 전달할 사용자 데이터
);
if (errNo != CL_SUCCESS)
{
printf("[FAILURE] clBuildProgram == %d\n", errNo);
// 실패 시 작업
}
else
{
printf("[SUCCESS] clBuildProgram\n");
}
/* ... */
clReleaseProgram(program); // 프로그램 객체 해제
free(lpDeviceId); // 장치 버퍼 메모리 해제
clReleaseContext(context); // 컨텍스트를 해제한다.
free(lpContextProperties); // 컨텍스트 속성 배열을 해제한다.
free(lpPlatformId); // 플랫폼 구조체 동적할당을 해제한다.
OpenCL 소스 컴파일 결과
5 단계. OpenCL에서 선언한 함수 실행하기
커널kernel은 OpenCL 소스에서 선언한 각 함수들의 진입점에 해당하는 개념이다. 앞서 컴파일한 OpenCL 소스에서 특정 함수의 이름을 통해 그 커널을 가져와 호출한다. 함수가 매개변수를 필요로 한다면 메모리 객체를 별도로 구성해야 한다.
5-1 단계. 커널 얻기
hello.cl 소스 파일에서 선언한 한 함수 hello를 호출해보도록 하겠다. 이를 위해 커널 객체를 생성한다.
/* C source */
cl_int errNo;
cl_uint nPlatformId = 0;
cl_platform_id * lpPlatformId = NULL;
size_t i, j;
cl_uint nContextProperties = 0;
cl_context_properties * lpContextProperties = NULL;
cl_context context;
size_t cbDeviceBuffer = 0;
size_t nDeviceId = 0;
cl_device_id * lpDeviceId = NULL;
size_t nCommandQueue = 0;
cl_command_queue * lpCommandQueue = NULL;
cl_program program;
// 새로 추가된 변수
cl_kernel kernel;
/* ... */
kernel = clCreateKernel
(
program, // cl_program program
"hello", // const char * kernel_name
&errNo // cl_int * errcode_ret
);
if (errNo != CL_SUCCESS)
{
printf("[FAILURE] clCreateKernel = %d\n", errNo);
// 실패 시 작업
}
else if (kernel == NULL)
{
printf("[FAILURE] clCreateKernel: kernel = %p\n", kernel);
// 실패 시 작업
}
else
{
printf("[SUCCESS] clCreateKernel\n");
}
/* ... */
clReleaseKernel(kernel); // 커널 객체 해제
clReleaseProgram(program); // 프로그램 객체 해제
free(lpDeviceId); // 장치 버퍼 메모리 해제
clReleaseContext(context); // 컨텍스트를 해제한다.
free(lpContextProperties); // 컨텍스트 속성 배열을 해제한다.
free(lpPlatformId); // 플랫폼 구조체 동적할당을 해제한다.
5-2 단계. 매개변수 구성하기
OpenCL 소스에서 선언한 hello 함수에서는 float * 형식의 3개의 매개변수(out, ptr1, ptr2)가 필요하다. 이를 구성해보겠다.
먼저 다음과 같이 테스트 배열을 마련한다. 하나는 1부터 50까지이고 다른 하나는 51부터 100까지의 각 50개의 실수로 구성된 배열이다.
/* C source */
float out[50] = { 0.0f, };
float ptr1[50] = { 0.0f, };
float ptr2[50] = { 0.0f, };
size_t i = 0;
for (i = 0; i < 50; i++)
{
ptr1[i] = (1.0f + (float)i);
ptr2[i] = (51.0f + (float)i);
}
C의 배열을 OpenCL 소스에서 작성한 함수로 보내기 위해서는 메모리 객체를 생성한다.
/* C source */
cl_int errNo;
cl_uint nPlatformId = 0;
cl_platform_id * lpPlatformId = NULL;
size_t i, j;
cl_uint nContextProperties = 0;
cl_context_properties * lpContextProperties = NULL;
cl_context context;
size_t cbDeviceBuffer = 0;
size_t nDeviceId = 0;
cl_device_id * lpDeviceId = NULL;
size_t nCommandQueue = 0;
cl_command_queue * lpCommandQueue = NULL;
cl_program program;
cl_kernel kernel;
// 새로 추가된 변수
cl_mem memOut;
cl_mem memPtr1;
cl_mem memPtr2;
/* ... */
memOut = clCreateBuffer
(
context, // cl_context context : 배열을 사용할 컨텍스트
CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, // cl_mem_flags flags : 각종 속성
sizeof(out), // size_t size : 배열의 크기 (단위 = 바이트)
out, // void * host_ptr : 배열
&errNo // cl_int * errcode_ret : 오류 코드
);
if (errNo != CL_SUCCESS)
{
printf("[FAILURE] clCreateBuffer = %d", errNo);
// 실패 시 작업
}
else if (memOut == NULL)
{
printf("[FAILURE] clCreateBuffer: memOut = %p", memOut);
// 실패 시 작업
}
else
{
printf("[SUCCESS] clCreateBuffer\n");
}
memOut = clCreateBuffer
(
context, // cl_context context
CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, // cl_mem_flags flags
sizeof(ptr1), // size_t size
ptr1, // void * host_ptr
&errNo // cl_int * errcode_ret
);
if (errNo != CL_SUCCESS)
{
printf("[FAILURE] clCreateBuffer = %d", errNo);
// 실패 시 작업
}
else if (memOut == NULL)
{
printf("[FAILURE] clCreateBuffer: memOut = %p", memOut);
// 실패 시 작업
}
else
{
printf("[SUCCESS] clCreateBuffer\n");
}
memOut = clCreateBuffer
(
context, // cl_context context
CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, // cl_mem_flags flags
sizeof(ptr2), // size_t size
ptr2, // void * host_ptr
&errNo // cl_int * errcode_ret
);
if (errNo != CL_SUCCESS)
{
printf("[FAILURE] clCreateBuffer = %d", errNo);
// 실패 시 작업
}
else if (memOut == NULL)
{
printf("[FAILURE] clCreateBuffer: memOut = %p", memOut);
// 실패 시 작업
}
else
{
printf("[SUCCESS] clCreateBuffer\n");
}
/* ... */
clReleaseMemObject(memOut); // float out[50]에 대한 메모리 객체 해제
clReleaseMemObject(memPtr1); // float ptr1[50]에 대한 메모리 객체 해제
clReleaseMemObject(memPtr2); // float ptr2[50]에 대한 메모리 객체 해제
clReleaseKernel(kernel); // 커널 객체 해제
clReleaseProgram(program); // 프로그램 객체 해제
free(lpDeviceId); // 장치 버퍼 메모리 해제
clReleaseContext(context); // 컨텍스트를 해제한다.
free(lpContextProperties); // 컨텍스트 속성 배열을 해제한다.
free(lpPlatformId); // 플랫폼 구조체 동적할당을 해제한다.
메모리 객체의 생성 결과
5-3 단계. 커널에 매개변수 전달하기
함수 진입점을 '커널'의 형태로 얻었고, 함수에 전달할 매개변수도 '메모리'의 형태로 구성하였다. 이제 커널과 매개변수를 연계해보겠다. OpenCL 소스에서 (out, ptr1, ptr2)의 순서로 선언하였으므로 이 순서대로 메모리 객체를 커널에 연계한다. 이 때 쓰이는 함수는 clSetKernelArg이다.
Windows API로 UI를 만들 경우 폰트가 다음과 같이 투박하게 보여짐을 확인할 수 있다.
기본적으로 투박한 폰트로 보여지는 Windows API 윈도우
본 포스팅에서는 Windows API로 작성한 윈도우에 폰트를 적용하는 방법에 대해 정리하겠다.
시스템 정의 기본 폰트 사용하기
시스템 정의 기본 폰트는 제목 표시줄, 메시지 박스, 메뉴, 상태 표시줄, 캡션 표시줄 등에 사용되기 위해 미리 정의된 폰트이다. 이를 불러오기 위해서는 SystemParametersInfo 함수를 SPI_GETNONCLIENTMETRICS 매개변수를 적용하여 호출한다.
WndProc 콜백 프로시저의 WM_CREATE 섹션에 다음과 같이 적는다.
/* WndProc: HWND, UINT, WPARAM, LPARAM */
// 새로 지정할 폰트 핸들
static HFONT s_hFont = (HFONT)NULL;
// 시스템 정의 폰트는 NONCLIENTMETRICS라는 구조체를 통해 전달된다.
NONCLIENTMETRICS nonClientMetrics;
/* ... */
// 구조체의 내용을 0으로 리셋한다.
ZeroMemory(&nonClientMetrics, sizeof(NONCLIENTMETRICS));
// cbSize 필드만 특별히 구조체의 크기로 지정한다.
nonClientMetrics.cbSize = sizeof(NONCLIENTMETRICS);
// 구조체를 통해 전달되는 폰트는 LOGFONT 형식이다.
// GDI에서 사용하기 위해 LOGFONT에서 기술된 폰트 정보를 바탕으로 HFONT를 생성한다.
s_hFont = CreateFontIndirect(&nonClientMetrics.lfCaptionFont);
// 창 스스로에게 WM_SETFONT 메시지를 전달한다.
SendMessage(hWnd, WM_SETFONT, (WPARAM)s_hFont, (LPARAM)MAKELONG(TRUE, 0));
자기 자신에게 WM_SETFONT를 전달했으므로 WndProc에서 WM_SETFONT 섹션을 추가하여 이벤트를 처리해보겠다. 다음은 그 예이다.
/* WndProc: HWND, UINT, WPARAM, LPARAM */
// 새로 지정할 폰트 핸들
static HFONT s_hFont = (HFONT)NULL;
// 시스템 정의 폰트는 NONCLIENTMETRICS라는 구조체를 통해 전달된다.
NONCLIENTMETRICS nonClientMetrics;
/* ... */
lResult = DefWindowProc(hWnd, uMessage, wParam, lParam);
// Static 윈도우에 폰트 변경 메시지를 보낸다.
SendMessage(s_hwndStatic, WM_SETFONT, wParam, lParam);
/* ... */
다음과 같이 윈도우의 폰트가 변경되었음을 확인할 수 있다.
시스템 정의 기본 폰트로 변경된 모습
LOGFONT 직접 지정하기
시스템 기본 폰트 외에 다른 폰트를 지정해보겠다. LOGFONT 구조체의 각 멤버들을 직접 지정하면 다양한 폰트들을 얻어서 윈도우에 적용 가능하다.
위와 같이 레이아웃을 구성한 다음 [찾아보기(B)...] 버튼(ID_BUTTON, s_hwndButton)을 클릭하여 폴더 다이얼로그를 띄우도록 하겠다. WM_COMMAND에서 LOWORD(wParam) 값이 ID_BUTTON인 경우에 다음과 같이 이벤트를 처리한다.
/* WndProc: HWND, UINT, WPARAM, LPARAM */
case WM_COMMAND:
switch (LOWORD(wParam))
{
case ID_BUTTON:
{
BROWSEINFO browseInfo;
ITEMIDLIST * pidlBrowse = NULL;
TCHAR szPath[MAX_PATH];
TCHAR szDisplayName[MAX_PATH];
ZeroMemory(&browseInfo, sizeof(BROWSEINFO));
// 본 다이얼로그를 소유한 창의 핸들이다.
browseInfo.hwndOwner = hWnd;
// 여기서 지정한 특정 폴더 이하의 경로만 선택 가능하다.
browseInfo.pidlRoot = (LPCITEMIDLIST)NULL;
// 폴더의 실제 이름과 보여지는 이름이 다를 때 지정한 버퍼로 보여지는 이름을 전달한다.
browseInfo.pszDisplayName = szDisplayName;
// 폴더 다이얼로그를 통해 사용자에게 보여질 메시지를 지정한다.
browseInfo.lpszTitle = TEXT("선택하고 싶은 폴더를 선택합니다...");
// 각종 옵션을 지정한다.
browseInfo.ulFlags =
BIF_NEWDIALOGSTYLE | // 새로운 형식의 다이얼로그([새 폴더 만들기] 버튼 있는 창)
BIF_RETURNONLYFSDIRS | // 로컬 경로의 디렉토리만 선택 가능하도록 함
BIF_EDITBOX; // 폴더 이름을 타자로도 입력할 수 있도록 텍스트 상자 제공함
// 다이얼로그 창에 대한 콜백을 지정한다.
browseInfo.lpfn = BrowseProc;
// 콜백 함수에 전달할 추가적인 데이터가 있다면 이것을 통해 전달한다.
browseInfo.lParam = (LPARAM)NULL;
// 폴더 열기 다이얼로그를 실행한다.
pidlBrowse = SHBrowseForFolder(&browseInfo);
if (pidlBrowse != NULL)
{
// 다이얼로그에서 선택한 폴더 항목을 문자열 경로로 변환한다.
if (SHGetPathFromIDList(pidlBrowse, szPath))
{
// 경로를 Edit로 출력하고
SetWindowText(s_hwndEdit, szPath);
// 선택한 폴더의 DisplayName도 Static으로 출력한다.
SetWindowText(s_hwndStatic3, szDisplayName);
}
else
{
OutputDebugString(TEXT("Invalid Path\n"));
}
}
}
break;
/* ... */
BIF_NEWDIALOGSTYLE를 적용한 다이얼로그BIF_NEWDIALOGSTYLE를 적용하지 않은 다이얼로그
콜백 프로시저
다이얼로그의 콜백 프로시저는 다음과 같이 선언한다.
int CALLBACK BrowseProc(HWND hWnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
다음은 콜백 함수의 구현 예시이다.
/* BrowseProc: HWND, UINT, LPARAM, LPARAM */
int CALLBACK BrowseProc(HWND hWnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
static TCHAR s_szPath[MAX_PATH];
int iResult = 0;
switch (uMsg)
{
case BFFM_INITIALIZED:
// 다이얼로그 상자가 이제 막 떴을 때 처리할 내용은 이 곳에 적는다.
// BFFM_SETSELECTION: 기본 값으로 선택하고 있을 폴더가 있을 때 사용한다.
// lParam은 선택하고 있을 폴더이다.
// lParam이 텍스트 형식의 경로라면 wParam = TRUE
// lParam이 PIDL 형식의 객체라면 wParam = FALSE
SendMessage(hWnd, BFFM_SETSELECTION, TRUE, lParam);
// BFFM_SEROKTEXT: [확인] 버튼의 텍스트를 변경한다.
// lParam은 [확인] 버튼에 새로 넣을 텍스트이다.
SendMessage(hWnd, BFFM_SETOKTEXT, 0, (LPARAM)TEXT("이걸로"));
break;
case BFFM_SELCHANGED:
if (SHGetPathFromIDList((LPITEMIDLIST)lParam, s_szPath))
{
if (_tcsstr(s_szPath, TEXT("blocked")))
{
// 예를 들어,
// 경로 중 일부 문자에 "blocked"가 포함되면 확인버튼 비활성화.
// BFFM_ENABLEOK: [확인] 버튼을 활성화/비활성화 한다.
// lParam은 [확인] 버튼의 활성화(TRUE) / 비활성화(FALSE) 여부이다.
SendMessage(hWnd, BFFM_ENABLEOK, 0, FALSE);
}
else
{
SendMessage(hWnd, BFFM_ENABLEOK, 0, TRUE);
// BFFM_SETSTATUSTEXT: 현재 선택한 폴더 경로를 다이얼로그의 Text 속성으로 지정함.
// lParam은 새로 선택된 폴더 경로
SendMessage(hWnd, BFFM_SETSTATUSTEXT, 0, (LPARAM)s_szPath);
}
}
break;
case BFFM_VALIDATEFAILED:
// 다이얼로그에 전달된 문자열이 잘못되었을 때 처리할 내용은 이 곳에 적는다.
// 다이얼로그를 계속 띄우고 있으려면 콜백이 Non-zero를 반환한다.
// 다이얼로그를 닫으려면 콜백이 0을 반환한다.
break;
}
return 0;
}
실행 결과
위 그림은 C:\Users를 선택한 후 [확인] 버튼을 누른 결과이다. 해당 폴더의 실제 이름은 Users이지만 한국어 모드에서는 사용자라는 이름으로 표시된다. 이것은 BROWSERINFO.pszDisplayName멤버로 전달된다.
'붉은별' 3.0에서 추출한 서체들 / Font files extracted from RedStar 3.0 Linux
붉은별 3.0에 내장된 원본 폰트 파일은 일부 글자glyph에서 버텍스vertex 관련 오류가 있어서 FontForge 등의 글꼴 제작 프로그램으로 열어 무결성 검사를 할 경우 "Self Intersection" 오류를 볼 수 있다. 이는 본 폰트의 개발자가 실수한 것으로 보인다. 때문에 Microsoft Windows에서 원본 파일을 열 경우 "올바른 글꼴 파일이 아닙니다."라는 오류 메시지를 볼 수 있다. 물론 유닉스 및 호환 운영체제에서는 원본 파일을 오류 메시지 없이 사용 가능하다. Microsoft Windows에서 사용하고자 하는 경우 원본 파일이 아닌 수정본 파일만을 사용 가능하다. 수정본 파일은 FontForge로 원본 파일을 열어서 True Type 포맷으로 다시 Export하는 방법으로 재생성되었다.
When opening original font files from RedStar 3.0 Linux by font development softwares, You may see "Self Intersection" error message. Maybe it is a mistake of developer of this fonts. So you may see "Invalid font format." error message, if you open the original files in Microsoft Windows. Of course, it is possible to use in Unix and its compatible OS. However, in Microsoft Windows, Only modified versions are possible to be used. Modified files attatched below are re-created by opening and exporting again in FontForge.
RealServer 8.0 설치하여 RealPlayer 스트리밍 서버 구성하기 (Part 2)
리얼플레이어(RealPlayer)는 자체적인 파일 포맷과 스트리밍 프로토콜을 지원해서 2000년대 초반까지 Windows Media Player와 함께 흔하게 쓰이던 오디오 및 비디오 플레이어이다. 이번 포스팅에서는 Windows NT 서버에 RealMedia 형식의 미디어를 게시하기 위해 기존 파일 형식에서 RealMedia로 변환하는 방법에 대해 소개한다.
오래된 프로그램이다보니 현재 사용되는 .mp4, .mkv 등의 코덱은 지원하지 않는다. Microsoft MPEG-4 Video v3 이하의 코덱을 사용하는 .avi 포맷을 기준으로 작업한다.
RealServer 8.0 설치하여 RealPlayer 스트리밍 서버 구성하기 (Part 1)
리얼플레이어(RealPlayer)는 자체적인 파일 포맷과 스트리밍 프로토콜을 지원해서 2000년대 초반까지 Windows Media Player와 함께 흔하게 쓰이던 오디오 및 비디오 플레이어이다. 이번 포스팅에서는 Windows NT 서버에 RealServer 8.0을 설치하여 스트리밍 서버를 구성하고 클라이언트에서 스트리밍으로 동영상으로 재생해보는 방법에 대해 소개한다.
3. 서버에 설치할 RealServer 8.0와 클라이언트에 설치할 RealPlayer 8.0
1, 2번의 경우 설치파일은 토렌트 또는 WinWorld PC(https://winworldpc.com/home) 등의 아카이브 사이트를 쉽게 구할 수 있다. 3번의 경우 구하기 힘들다. 특히 RealServer 8.0은 극히 레어한 자료이므로 더욱 그렇다. 하지만 본 블로그는 어렵게 준비하여 올린다.
1997년 1월 1일 ~ 2010년 3월 8일 사이에서 사용 가능한 RealServer 8.0의 라이선스 파일이다.
license2.lic
2000년 8월 1일 ~ 2030년 12월 31일 사이에서 사용 가능한 RealServer 8.0의 라이선스 파일이다.
rs801winnt.exe
Windows NT용 RealServer 8.0의 설치파일이다.
rp8en584.exe
RealPlayer 8.0 영어 버전의 설치파일이다.
rp8kr450.exe
RealPlayer 8.0 한국어 버전의 설치 파일이다. 영어 또는 한국어 둘 중 하나만 설치하면 된다.
rd8en200.exe
RealProducer 8.5 Basic 설치파일이다. 녹화, 녹음, 파일 변환 등의 작업을 여기에서 수행하면 된다. (일부 기능은 제한됨.)
rd8en851.exe
RealProducer 8.51 Plus 설치파일이다. 녹화, 녹음, 파일 변환 등의 작업을 여기에서 수행하면 된다. (기능 제한 없음.)
rd8en851.txt
RealProducer 8.51 Plus의 시리얼이다.
날짜가 경과하여도 다음과 같이 Windows NT 서버의 날짜를 변경한다면, 라이선스 파일을 계속 사용할 수 있다.
1. Windows NT 서버에 RealServer 8.0 설치하기
첨부한 파일의 압축을 풀어 rs801winnt.exe를 실행한다. 설치 마법사가 뜨면 [Next]를 클릭하여 설치를 시작한다.
라이선스 파일의 위치를 묻는다. [Browse...]를 눌러 동봉된 lic 파일을 선택한다.
라이선스 파일의 정보를 확인 후 [Accept >]를 누른다.
RealServer 8.0이 설치될 경로를 지정한다.
설치 후 관리 화면에 접속하기 위한 ID와 암호를 지정한다.
PNA 방식으로 스트리밍 서비스를 제공하기 위한 포트를 지정한다. 기본 값은 7070이다.
RTSP 방식으로 스트리밍을 제공하기 위한 포트 번호를 지정한다. 기본 값은 554이다.
HTTP 방식으로 접속하여 미디어를 제공하기 위한 포트를 설정한다. 기본 값은 8080이다.
RealServer 8.0의 관리 화면은 웹 브라우저를 통해 접속이 가능하다. 이 관리 화면에 접속하기 위한 포트를 지정한다. 이 값은 임의로 지정되며, 기존에 사용중인 포트와 충돌하면 안 된다.
RealServer 8.0을 NT 서비스로 시작할 것인지 여부를 묻는다. 서비스의 형태로 실행하면, 별도의 콘솔 창이 떠 있지 않고도 RealServer가 실행을 유지할 수 있다. 서버를 시작할 때마다 서비스 형태로 실행되게 하려면 이를 체크한다.
설정한 사항이 맞는지 확인 후 [Continue]를 누른다.
설치가 완료되었다 [OK]를 누른다.
시작메뉴와 바탕화면에 RealServer 항목이 추가되었다. 이제 관리자 화면으로 접속하기 위해 [RealServer 8.0 Administrator]를 클릭한다.
로그인 화면이 나타나면 설치 마법사에서 입력했던 관리자 아이디와 암호를 입력한다.
2. 관리자 화면 둘러보기 및 마운트 지점 설정하기
바탕화면의 RealServer 아이콘 또는 시작메뉴를 통해 RealServer 8.0 아이콘을 실행하면, 콘솔창만 떠 있거나 아무 반응도 없을 것이다. RealServer 그 자체는 백그라운드로 실행되기 때문에 그렇다. RealServer 8.0의 관리화면을 접속하면 다음과 같은 화면이 나타난다.
설치 당시 지정했던 포트를 바꾸고 싶다면 왼쪽 메뉴에서 [General Setup] > [Ports] 항목을 클릭한다. 아래와 같이 포트를 수정하고 [Apply] 버튼을 누르면 된다.
[General Setup] > [Mount Points] 메뉴를 접속하면 스트리밍 서버의 마운트 위치를 설정할 수 있다. 적당한 폴더를 마운트하여 미디어 서버를 시험한다.
C:\InetPub에 pnmroot라는 폴더를 하나 만든다.
C:\Program Files\Real\Content에 샘플 오디오와 비디오 파일들이 있는데 이들을 C:\InetPub\mediaroot에 복사한다.
다시 관리화면으로 돌아온다. "Mount Points" 부분에 있는 [Add New]를 클릭하면 마운트 위치가 하나 생긴다. 오른쪽에서 "Edit Mount Point"의 텍스트 박스를 /pnmroot로 수정 후 [Edit]를 누르면 마운트 지점이 변경되는 것을 확인할 수 있다. 그 다음 "Description"에는 간단한 설명을 붙이고, "BasePath"에는 실제 경로를 지정한다. 여기에서는 C:\InetPub\pnmroot를 스트리밍으로 내보낼 것이므로 그대로 경로를 적는다. 그 후 [Apply]를 누른다.
이런 화면이 뜨고 상태가 "Succeeded"라고 나타나면 마운트 지점 편집에 성공한 것이다. [Close]를 누른다.
변경 상태를 적용하려면 화면상단의 [Pending Changes]를 눌러서 단순 적용만 할 수도 있고 [Restart Server]를 눌러서 서버 재시작을 할 수도 있다. 여기서는 서버 재시작을 해 보겠다.
접속자가 0명임을 확인 후 [OK]를 클릭한다.
서버가 재시작되고 있다. 약 20초 후에 관리 화면으로 돌아간다.
3. 테스트하기
이제 관리 화면은 닫는다. 단 Windows NT 서버는 끄지 말고 계속 켜 둔다. 이 상태에서 클라이언트로 자리를 옮긴다. RealPlayer를 설치 후, 아래의 주소를 입력하고 [Enter]를 눌러 접속한다. 마운트 지점에 게시한 샘플 미디어 파일을 열어보는 과정이다.
pnm://서버주소:포트번호/pnmroot/real8video.rm
포트 번호는 관리화면에서 지정한 포트번호이며 기본값으로 둔 경우 생략 가능하다. 서버 주소는 Windows NT 서버의 콘솔창을 열고 "ipconfig /all"을 실행하면 확인 가능하다. 여기에서는 pnm://192.168.204.153:7070/pnmroot/realvideo8.rm으로 접속한다.
아래와 같이 약간의 버퍼링 후 동영상이 뜨면 스트리밍 서버가 정상적으로 작동됨을 확인할 수 있습니다.
sudo는 관리자 권한 상승 명령이다. 이 명령을 실행할 때 관리자 계정으로 접근하기 위한 암호를 요구하는데, 특별한 설정을 고치지 않았다면 키보드를 누를 때마다 별표('*')가 나타나지 않아 다소 불편할 수 있다. 이에 본 포스팅에서는 sudo로 관리자 암호를 입력할 때 키보드 터치에 의해 별표('*')가 나타나도록 설정하는 방법을 정리한다.
sudoer 파일 백업하기
/etc/sudoers의 내용을 수정함으로써 sudo로 암호 입력 시 별표가 나타나게 할 수 있다. 먼저 다음의 명령을 실행하여 sudoers 파일을 백업한다.
$ sudo cp /etc/sudoers /etc/sudoers.bak
sudoer 파일 수정하기
다음 관리자 권한으로 visudo를 실행한다. visudo는 vi 에디터이기는 하나 sudoers 파일을 안전하게 편집하기 위한 특별한 명령어이다.
$ sudo visudo
sudoers 파일을 백업한 뒤 visudo로 이를 편집한다.
Preserving HOME has security...로 시작하는 영역을 찾아보면, 아래 그림과 같이 Defaults env_reset 항목이 있다. 이 곳을 수정한다.
편집할 부분이다.
이 줄의 끝에 , pwfeedback를 추가한다. 즉,
Defaults env_reset, pwfeedback
이 되도록 수정하면 된다.
줄 끝에 pwfeedback을 추가함.
쉘을 리셋하기
visudo를 종료하고 쉘shell을 리셋한다.
$ sudo reset
쉘을 리셋함.
설정 수정의 효과 확인
이후 sudo를 실행하면 다음과 같이 관리자 암호를 입력할 때 키보드 터치마다 별표('*')가 나타남을 확인할 수 있다.
한국(대한민국)에서는 전산처리 과정에서 한글을 부호화하기 위하여 유니코드, KS X 1001(구 KS C 5601), KS X 1002(구 KS C 5657) 등의 문자집합charset이 쓰인다면 북한('조선민주주의인민공화국')에서는 유니코드와 KPS-9566(국규 9566) 코드가 쓰인다.
한글 자모 영역에 특정 한글 음절('김'A4E8, '일'A4E9, '성'A4EA, '김'A4EB, '정'A4EC, '일'A4ED)이 중복하여 따로 배정(북에서는 이 공간을 "존(엄?)함" 영역이라 별도로 부름)되어 있다고 해서 일명 "김일성김정일" 코드라고도 부르는 이 한글 코드는 한글 자모의 순서가 남한과 다르기 때문에 '가'를 제외한 모든 글자가 한국의 완성형 코드와 호환되지 않는다는 가장 중요한 특징을 갖고 있다.
본 포스트에서는 북한의 자체개발(?)로 만들었다는 운영체제인 붉은별 3.0(RedStar 3.0)을 기준으로 하여 이 운영체제에서 지원하는 국규 9566 한글 코드에 대해 다룬다. 보통의 2바이트 문자셋과 마찬가지로 0xA0 - 0xFF행과 0xA0 - 0xFF열에서 정의되며 실질적으로 문자가 배치되는 영역은 총 94행 x 94열로 되어 있다.
이전 버전인 붉은별 2.0에 내장된 문자코드와 비교했을 때 큰 차이는 없다. 특수 문자 영역에서 AEA0-AEAF 부분에 있던 문자가 삭제되었고, '존함' 영역에 선대 국왕의 이름을 이어 '그 사람' 이름이 추가된 것이 특징이다.
한국(대한민국)에서는 전산처리 과정에서 한글을 부호화하기 위하여 유니코드, KS X 1001(구 KS C 5601), KS X 1002(구 KS C 5657) 등의 문자집합charset이 쓰인다면 북한('조선민주주의인민공화국')에서는 유니코드와 KPS-9566(국규 9566) 코드가 쓰인다.
한글 자모 영역에 특정 한글 음절('김'A4E8, '일'A4E9, '성'A4EA, '김'A4EB, '정'A4EC, '일'A4ED)이 중복하여 따로 배정(북에서는 이 공간을 "존
엄
함" 영역이라 별도로 부름)되어 있다고 해서 일명 "김일성김정일" 코드라고도 부르는 이 한글 코드는 한글 자모의 순서가 남한과 다르기 때문에 '가'를 제외한 모든 글자가 한국의 완성형 코드와 호환되지 않는다는 가장 중요한 특징을 갖고 있다.
본 포스트에서는 북한의 자체개발(?)로 만들었다는 운영체제인 붉은별 2.0(RedStar 2.0)을 기준으로 하여 이 운영체제에서 지원하는 국규 9566 한글 코드에 대해 다룬다. 보통의 2바이트 문자셋과 마찬가지로 0xA0 - 0xFF행과 0xA0 - 0xFF열에서 정의되며 실질적으로 문자가 배치되는 영역은 총 94행 x 94열로 되어 있다.