1. 특정 디렉토리 뒤지기
지정한 디렉토리에 있는 모든 파일을 찾아내는 코드를 만들려면 어떻게 해야 합니까 ?
이 때 사용할 수 있는 API가 바로 FindFirstFile과 FindNextFile, FindClose라는 API들입니다. 사용 예제는 다음과 같습니다.
WIN32_FIND_DATA findFileData;
HANDLE hFileHandle;
// szDir에 뒤지고자 하는 디렉토리의 경로명을 준다. 예를 들면 "C:\\TEMP\\*.*"
// 찾아진 파일의 속성은 findFileData의 dwFileAttributes를 살펴본다.
hFileHandle = FindFirstFile(m_szDir, &findFileData);
if (hFileHandle != INVALID_HANDLE_VALUE) // 파일을 찾은 경우
{
// 찾은 파일의 이름은 cFileName 필드로 들어온다.
...
// 다음 파일을 찾는다.
while(FindNextFile(hFileHandle, &findFileData)) {
...
}
FindClose(hFileHandle);
}
2. API를 이용하는 유니코드와 ANSI 문자열간의 변환 방법
API를 이용해서 유니코드와 ANSI 문자열간의 변환은 어떻게 수행합니까 ?
Visual C++에서 유니코드 문자열은 BSTR이란 타입으로 표시됩니다. 또 유니코드와 ANSI 문자열간의 변환을 위해서 윈도우 시스템에는 MultiByteToWideChar와 WideCharToMultiByte라는 API가 존재합니다. MFC에서의 BSTR 타입 변환방법이나 ATL로 하는 BSTR 타입 변환도 참고하시기 바랍니다.
ANSI 문자열에서 유니코드로의 변환 방법
// sTime이란 ANSI 문자열을 bstr이란 이름의 유니코드(BSTR 타입) 변수로 변환
char sTime[] = "유니코드 변환 예제";
BSTR bstr;
// sTime을 유니코드로 변환하기에 앞서 먼저 그 길이를 알아야 한다.
int nLen = MultiByteToWideChar(CP_ACP, 0, sTime, lstrlen(sTime), NULL, NULL);
// 얻어낸 길이만큼 메모리를 할당한다.
bstr = SysAllocStringLen(NULL, nLen);
// 이제 변환을 수행한다.
MultiByteToWideChar(CP_ACP, 0, sTime, lstrlen(sTime), bstr, nLen);
// 필요없어지면 제거한다.
SysFreeString(bstr);
유니코드에서 ANSI 문자열로의 변환 방법
// newVal이란 BSTR 타입에 있는 유니코드 문자열을 sTime이라는 ANSI 문자열로 변환
char *sTime;
int nLen = WideCharToMultiByte(CP_ACP, 0, newVal, -1, sTime, 0, NULL, NULL);
sTime = malloc(nLen+1);
WideCharToMultiByte(CP_ACP, 0, newVal, -1, sTime, 128, NULL, NULL);
// 필요없으면 메모리를 제거한다.
free(sTime);
유니코드 문자열을 UTF-8으로 변환하기
WideCharToMultiByte 함수를 호출할 때 첫 번째 인자로 CP_UTF8을 지정하면 된다. UTF-8은 유니코드의 인코딩 스킴 중의 하나로 쉽게 말하자면 문자열 스트림에서 0을 빼고 표현하는 방법이라고 볼 수 있다.
3. 레지스트리 읽기/쓰기
API를 이용해서 레지스트리에 한 항목을 생성하거나 기존 항목의 값을 읽어들이려면 어떻게 해야합니까 ?
레지스트리 관련 API를 사용하려면 winreg.h라는 헤더 파일을 소스에 포함해야 합니다. 레지스트리에 키를 생성하는 방법과 레지스트리에 존재하는 키의 값을 읽는 방법을 차례로 살펴보겠습니다.
레지스트리 키 생성 예제
// 예를 들어 HKEY_LOCAL_MACHINE밑의 System\CurrentControlSet\Services\GenPort라는 키를
// 생성하고 거기에 DWORD 타입의 값으로 Type을 만들고 문자열 타입의 값으로 Group
// 을 만들어 본다.
#include "winreg.h"
LONG error = 0;
HKEY hKey;
DWORD dwDisp, dwData;
char lpData[] = "Write this down";
// 먼저 만들려는 키가 이미 존재하는 것인지 살혀본다.
error = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\GenPort",
0, KEY_ALL_ACCESS, &hKey);
if (error != ERROR_SUCCESS) // 없다면 새로 생성한다.
{
// 키를 생성한다.
error = RegCreateKeyEx(HKEY_LOCAL_MACHINE,
"System\\CurrentControlSet\\Services\\GenPort", 0, "REG_BINARY",
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, 0, &hKey, &dwDisp);
// 위의 키 밑에 Type이란 DWORD 타입의 값을 만들고 1로 초기화
dwData = 0x1;
error = RegSetValueEx( hKey, "Type", 0, REG_DWORD,&dwData,4);
// 위의 키 밑에 Group이란 문자열 타입의 값을 만들고 lpData의 값으로 초기화
error = RegSetValueEx( hKey, "Group", 0, REG_SZ, lpData, strlen(lpData));
// 키를 닫는다.
RegCloseKey(hKey);
}
기존의 레지스트리 키에서 값 읽기
// HKEY_CURRENT_USER\Software\Netscape\Netscape Navigator\Main 밑의 Install Directory
// 값의 문자열 값을 읽어들인다.
DWORD dwType, cbData;
HKEY hSubKey;
long lRet;
char pszString[255];
// 키를 오픈한다.
if ((lRet = RegOpenKeyEx(HKEY_CURRENT_USER,
"Software\\Netscape\\Netscape Navigator\\Main",
0, KEY_READ | KEY_QUERY_VALUE , &hSubKey)) == ERROR_SUCCESS)
{
cbData = 255; // 문자열 값을 읽어올 데이터의 크기를 준다.
if ((lRet = RegQueryValueEx(hSubKey, "Install Directory",
NULL, &dwType, pszString, &cbData)) == ERROR_SUCCESS)
{
// 제대로 읽힌 경우
}
else
{
// 에러가 발생한 경우
}
RegCloseKey(hSubKey);
}
레지스트리 키 삭제하기 - RegDeleteKey 함수를 사용한다.
4. 윈도우 탐색기로부터의 Drag&Drop을 받으려면
윈도우 탐색기로부터 제가 만든 윈도우로의 drag&drop이 가능하게 하려면 어떻게 해야 합니까 ?
다음 순서를 따라서 프로그래밍하시면 됩니다.
프로그램의 초기화시에 DragAcceptFiles(hWnd, TRUE) 함수를 호출한다. 첫 번째 인자인 hWnd는 드롭의 타겟이 되는 윈도우의 핸들이다.
탐색기로부터 파일이 드롭되는 순간에 WM_DROPFILES 메시지가 날라온다. 이를 처리한다.
case WM_DROPFILES :
{
POINT pt;
// 어느 위치에 드롭되었는지 그 항목을 알아낸다.
if (DragQueryPoint((HDROP)wParam, &pt))
{
UINT i = 0;
// 모두 몇 개의 파일이 드롭되었는지 알아낸다.
// 만일 폴더가 드롭되었다면 폴더의 이름만 넘어온다.
UINT uCount = DragQueryFile((HDROP)wParam, 0xFFFFFFFF, NULL ,0);
for(i = 0;i < uCount;i++)
{
// 드롭된 파일의 이름을 알아온다.
DragQueryFile((HDROP)wParam, i, buffer ,255);
// 드롭된 파일 이름을 출력해본다.
MessageBox(hWnd, buffer, "File Name", MB_OK);
}
}
// drag and drop 작업을 끝낸다.
DragFinish((HDROP)wParam);
break;
}
Drag&drop을 더 사용할 필요가 없어지면 DragAcceptFiles를 호출한다.
DragAcceptFiles(hWnd, FALSE);
5. 시스템의 모든 드라이브 알아내기
현재 시스템에 붙어있는 모든 드라이브(네트웍 드라이브 포함)에 대한 정보를 알아내고 싶습니다.
GetLogicalDriveStrings로 시스템에 마운트되어있는 모든 드라이브 정보를 알아낸다. 두 번째 인자인 buffer로 드라이브 정보가 들어오는데 그 구조는 c:\,d:\과 같은 형식이며 리턴값으로 그 버퍼의 크기가 들어온다.
char buffer[256];
DWORD dwRet;
LPSTR token;
dwRet = GetLogicalDriveStrings(256, buffer);
루프를 돌면서 드라이브별 정보를 알아낸다. 이 때는 GetVolumeInformation 함수를 이용한다.
token = buffer; // token이 지금 처리해야할 드라이브를 가리킨다.
while (dwRet > 0)
{
DWORD FileSystemFlag;
char FileSystemName[64];
strcpy(DriveString, token);
// VolumeName으로 드라이브에 대한 설명 문자열이 넘어온다.
if (GetVolumeInformation(token, VolumeName, 255, NULL, NULL,
&FileSystemFlag, FileSystemName, 63))
{
// 원하는 작업을 수행한다.
}
dwRet -= (strlen(token)+1);
token = token + strlen(token)+1; // 다음 드라이브로 진행한다.
}
6. 드라이브/디렉토리/파일의 이미지 리스트 인덱스 얻기
특정 드라이브/디렉토리/파일이 시스템 이미지 리스트에서 어떤 인덱스를 갖는지 알고 싶습니다.
각 파일이나 드라이브 및 디렉토리에 대한 정보는 Shell 라이브러리에서 제공해주는 SHGetFileInfo 함수를 이용하면 됩니다. 다음의 함수는 첫 번째 인자인 lpFileName으로 주어진 파일에 대한 설명을 두 번째 인자로 받아오고 세 번째 인자로는 시스템 이미지 리스트에서의 인덱스를 얻어옵니다.
일단 리스트뷰 컨트롤의 생성시 윈도우 스타일로 LVS_NOSORTHEADER를 주지 않는다.
리스트뷰로부터 칼럼 헤더가 눌렸을 때 오는 이벤트를 받아들인다.
NM_LISTVIEW *pnmtv = (NM_LISTVIEW FAR *)lParam;
switch(pnmtv->hdr.code)
{
case LVN_COLUMNCLICK :
{
// 어느 항목(pnmtv->iSubItem)이 눌렸는지부터 검사한다.
// g_iSubItem은 어느 항목이 눌렸는지 기록해두는 인덱스이다.
g_iSubItem = pnmtv->iSubItem;
// 정렬함수를 호출한다. CompareFunc가 정렬함수이다.
ListView_SortItems(hListView, (PFNLVCOMPARE)CompareFunc, (LPARAM)this);
break;
}
리스트뷰 항목을 정렬하는데 사용되는 CompareFunc라는 함수를 만든다. 이는 보통 C 함수로 만들거나 클래스를 사용할 경우에는 클래스 내의 static 함수로 만든다. CompareFunc의 코드는 다음과 같다.
int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
LV_FINDINFO lvfi;
int iFirstItem, iSecondItem;
// g_iSubItem 컬럼의 성격에 따라 비교한다. 문자열이라면 아래와 같이 한다.
int iRet = strcmpi(lpFirst, lpSecond);
return iRet;
}
10. 버전 정보 알아내기 코드
파일의 버전을 API를 통해 알아내려면 어떻게 해야합니까 ?
Resource의 한 타입으로 VERSIONINFO라는 것이 존재합니다. 여기에 해당 파일의 버전 정보를 기록하도록 되어있습니다. 이 버전 정보를 읽어오는데 ver.dll이라는 DLL에 들어있는 API들을 사용합니다. 주의할 점은 버전 리소스는 언어별로 설정이 되기 때문에 영어로도 읽어보고 한국어 로도 읽어봐야 한다는 것입니다. 다음 예제를 참고하시기 바랍니다.
// szDrvName이란 파일에 들어있는 버전 정보를 읽어온다.
#include
DWORD dwSize, handle;
LPSTR lpstrVffInfo;
HANDLE hMem;
LPSTR lpVersion; // 이 변수로 파일의 버전 정보가 들어온다.
char szDrvName[80]; // 버전 정보를 알아내고자 하는 파일 이름이 여기에 들어온다.
....
// 버전 정보 블록의 크기를 알아온다.
dwSize = GetFileVersionInfoSize(szDrvName , &handle);
if (dwSize) // 버전 정보 블록이 존재하면
{
// 버전 정보 블록을 포함할 메모리 블록을 할당 받아둔다.
hMem = GlobalAlloc(GMEM_MOVEABLE, dwSize);
lpstrVffInfo = GlobalLock(hMem);
// 버전 정보 블록의 내용을 읽어온다.
GetFileVersionInfo(szDrvName, handle, dwSize, lpstrVffInfo);
// 버전 정보 블록에서 버전 정보를 읽어온다.
VerQueryValue((LPVOID)lpstrVffInfo,
(LPSTR)"\\StringFileInfo\\041204B0\\FileVersion",
(void FAR* FAR*)&lpVersion, (UINT FAR *)&dwSize);
// lpVersion에 들어있는 버전 정보를 사용한다.
....
GlobalUnlock(hMem);
GlobalFree(hMem);
}
위에서 041204B0가 바로 버전 리소스에 사용된 언어가 무엇인지를 나타냅니다. 이는 영어를 나타내며 한국어의 경우에는 040904B0를 사용하면 됩니다. 이 밖에도 version.lib를 링크의 라이브러리 항목에 추가해야 합니다.
11. 시스템 사양 알아내기
현재 시스템에 부착되어 있는 메인 메모리의 양과 CPU와 운영체제의 종류를 알고 싶습니다.
먼저 시스템에 부착되어 있는 메인 메모리의 크기는 GlobalMemoryStatus라는 API를 이용하면 됩니다. 예제 코드는 다음과 같습니다.
//===========================================================
// lMemTotal : 실제 메모리의 전체 크기 (KB 단위)
// lAvailMemTotal : 사용 가능한 실제 메모리의 크기 (KB 단위)
// lVirtualTotal : 가상 메모리의 전체 크기 (KB 단위)
//===========================================================
void GetMemoryStatus(long *lMemTotal, long *lAvailMemTotal, long *lVirtualTotal)
{
double var;
MEMORYSTATUS memoryStatus;
다음으로 현재 사용 중인 운영체제의 종류를 알아내는 코드는 다음과 같습니다.
//===============================================================
// GetOSVersion : OS의 버전을 얻어온다.
// --------------------------------------------------------------
// lpstInfo
// lpstBuildNumber
// lpstServicePack
//===============================================================
void GetOSVersion (LPSTR lpstInfo, LPSTR lpstBuildNumber, LPSTR lpstServicePack)
{
int stat = 0;
TCHAR data [64];
DWORD dataSize;
DWORD win95Info;
OSVERSIONINFO versionInfo;
HKEY hKey;
LONG result;
if (lstrcmpi (data, "WinNT") == 0)
strcpy(lpstInfo, "Windows NT Workstation");
else if (lstrcmpi (data, "ServerNT") == 0)
strcpy(lpstInfo, "Windows NT Server");
else
strcpy(lpstInfo, "Windows NT Server - Domain Controller");
// NT 버전을 알아낸다.
if (versionInfo.dwMajorVersion == 3 || versionInfo.dwMinorVersion == 51)
strcat(lpstInfo, " 3.51");
else if (versionInfo.dwMajorVersion == 5) // 윈도우 2000의 경우
strcat(lpstInfo, " 5.0");
else
strcat(lpstInfo, " 4.0");
// Build 번호를 알아낸다.
wsprintf(lpstBuildNumber, "%d", versionInfo.dwBuildNumber);
}
else if (versionInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
{
strcpy(lpstInfo, "Windows 95");
if ((versionInfo.dwMajorVersion > 4) || ((versionInfo.dwMajorVersion == 4)
&& (versionInfo.dwMinorVersion > 0)))
{
strcpy(lpstInfo, "Windows 98");
}
// 윈도우 95는 Build 번호가 하위 워드에 들어간다.
win95Info = (DWORD)(LOBYTE(LOWORD(versionInfo.dwBuildNumber)));
wsprintf(lpstBuildNumber, "%d", win95Info);
}
else
wsprintf(lpstInfo, "Windows 3.1");
// 서비스 팩 정보를 얻어낸다.
strcpy(lpstServicePack, versionInfo.szCSDVersion);
}
12. IE의 설치 여부와 버전 확인
현재 시스템에 IE가 설치되었는지 여부와 그 버전을 알려면 어떻게 해야합니까 ?
사실 동작시켜보지 않고서는 IE가 제대로 설치되어있는지 알아내는 방법은 없지만 레지스트리를 통해 IE가 설치되었는지 여부와 버전을 확인할 수 있습니다. 그 함수는 다음과 같습니다.
//===========================================================================
// GetIEVersion : IE의 버전을 얻는다. 정보를 찾을 수 없으면 FALSE를 리턴한다.
//===========================================================================
BOOL GetIEVersion(LPSTR lpVer)
{
LONG result;
HKEY hKey;
DWORD dwType;
char data[65];
DWORD dataSize = 64;
// --------------------
// IE의 버전을 얻는다.
// --------------------
result = ::RegOpenKeyEx (HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Internet Explorer", 0, KEY_QUERY_VALUE, &hKey);
if (result == ERROR_SUCCESS)
{
result = ::RegQueryValueEx (hKey, "Version", NULL, &dwType, (unsigned char *)data, &dataSize);
strcpy(lpVer, data);
}
else
return FALSE;
RegCloseKey (hKey);
return TRUE;
}
13. IE의 보안 설정 보기
IE에 보면 네 단계의 보안 영역이 있습니다. 그 영역별로 설정되어있는 보안 설정값을 읽으려면 어떻게 해야합니까 ?
IE에는 다음과 같은 네 가지 보안 영역이 존재합니다.
인터넷 영역
로컬 인터넷 영역
신뢰할 수 있는 사이트 영역
제한된 사이트 영역
IE는 보안 영역 설정과 관련하여 Internet Security Manager와 Internet Zone Manager라는 인터페이스가 존재합니다. 이를 이용해 보안 영역의 보안을 설정하고 특정 IP나 도메인 이름을 등록할 수 있습니다. 자세한 사항은 레퍼런스를 찾아보기 바랍니다.
// Internet Security 인터페이스 초기화
hr = CoCreateInstance(CLSID_InternetSecurityManager, NULL, CLSCTX_ALL, //INPROC_SERVER,
IID_IInternetSecurityManager, (void**)&pSecurityMgr);
if (hr != S_OK)
{
return;
}
hr = CoCreateInstance(CLSID_InternetZoneManager, NULL, CLSCTX_ALL, //INPROC_SERVER,
IID_IInternetZoneManager, (void**)&pZoneMgr);
if (hr != S_OK)
{
return;
}
dwEnum = 0;
// 보안 영역 열거자(Zone Enumerator)를 초기화한다.
pZoneMgr->CreateZoneEnumerator(&dwEnum, &dwZoneCount, 0);
for(DWORD i = 1;i < dwZoneCount;i++)
{
pZoneMgr->GetZoneAt(dwEnum, i, &dwZone);
pZoneMgr->GetZoneAttributes(dwZone, &zoneAttr);
// zoneAttr.szDisplayName에 보안 영역의 이름이 들어오는데 유니코드이다. 이를 변환한다.
WideCharToMultiByte(CP_ACP, 0, zoneAttr.szDisplayName, -1, szTemp1, 255, NULL, NULL);
// zoneAttr.dwTemplateCurrentLevel에는 보안 영역의 보안값 설정이 들어온다.
wsprintf(szTemp2, "%x", zoneAttr.dwTemplateCurrentLevel);
}
// 보안 영역 열거자(Zone Enumerator)를 제거한다.
if (dwEnum != 0)
pZoneMgr->DestroyZoneEnumerator(dwEnum);
pSecurityMgr->Release();
pZoneMgr->Release();
// COM 라이브러리를 메모리에서 내린다.
CoUninitialize();
}
14. ActiveX 컨트롤의 등록 방법
Regsvr32 같은 유틸리티를 이용하지 않고 프로그램 내에서 컨트롤을 레지스트리에 등록하려면 어떻게 해야합니까 ?
모든 AcitveX 컨트롤은 자신을 레지스트리에 등록하기위한 목적으로 DllRegisterServer라는 함수를 갖고 있습니다. ActiveX 컨트롤을 메모리로 로드한 다음에 이 함수를 불러주면 원하는 일을 수행할 수 있습니다. 반대로 ActiveX 컨트롤을 레지스트리에서 제거하기 위한 용도로 DllUnRegisterServer라는 함 수도 존재합니다.
// ==============================================================
// RegisterOCX 지정된 ActiveX 컨트롤을 레지스트리에 등록한다.
// --------------------------------------------------------------
// LPSTR pszString 등록하고자 하는 ActiveX 컨트롤의 절대 경로명
// ==============================================================
BOOL WINAPI RegisterOCX(LPSTR pszString)
{
int iReturn = 0;
HRESULT (STDAPICALLTYPE * lpDllEntryPoint)();
HINSTANCE hLib;
// OLE 라이브러리를 초기화한다.
if (FAILED(OleInitialize(NULL)))
{
MessageBox(GetFocus(), "OLE 초기화 실패", "에러", MB_OK);
return FALSE;
}
FreeLibrary(hLib);
OleUninitialize();
return TRUE;
}
15. 데이터 파일의 실행
탐색기에서 실행 파일이 아닌 데이터 파일을 더블클릭하면 그 데이터 파일과 연결된 실행 파일이 실행되면서 그 파일을 물고 올라갑니다. 이를 구현하는 방법을 알려주세요.
ShellExecuteEx라는 API를 사용하면 됩니다. 다음 함수는 인자로 데이터 파일 혹은 실행 파일의 경로를 받아서 실행해줍니다. 데이터 파일의 경우에는 연결된 실행 파일을 찾아서 그걸 실행해줍니다.
return ShellExecuteEx(&ExecInfo);
}
16. 파일의 존재 여부 테스트
어떤 파일이 실제로 존재하는 것인지 간단히 테스트해보는 방법은 무엇인가요 ?
CreateFile API를 사용하면 됩니다. 파일을 열때 플래그 중의 하나로 OPEN_EXISTING이라는 것이 있는데 이를 사용하면 됩니다. 다음 코드를 예로 보시기 바랍니다.
hFile = CreateFile("C:\\TEMP\\test.txt", GENERIC_READ, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
// 파일이 존재하는 경우
CloseHandle(hFile);
}
17. API를 이용한 파일 I/O
API를 이용한 파일 입출력에 대한 예제 코드가 없습니까 ?
프로그래밍을 하다보면 간혹 파일 I/O를 API를 이용해 수행해야 할 경우가 있습니다. 이 때 다음의 예제 코드를 복사해다가 사용하면 편리할 것입니다. MFC의 CFile을 이용한 파일 I/O에 대해 알고 싶으시면 요기를 클릭하세요.
파일 쓰기의 경우
HANDLE fp;
DWORD NumberOfBytesWritten;
char lpBuffer[1024];
// FileName이 지정한 파일의 이름이 있으면 그걸 열고 없으면 그 이름으로 하나 생성한다.
if ((fp=CreateFile((LPCTSTR)FileName, GENERIC_WRITE | GENERIC_READ, 0, NULL,
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE)
{
// 파일 열기 에러 발생
}
// 필요한 만큼 WriteFile의 호출을 반복한다. 파일 포인터의 이동시에는 SetFilePointer API를 이용한다.
WriteFile(fp, lpBuffer, 1024, &NumberOfBytesWritten, NULL);
if (NumberOfBytesWritten != 1024)
{
// 파일 쓰기 에러 발생
CloseHandle(fp);
}
// 작업이 다 끝났으면 파일을 닫는다.
CloseHandle(fp);
파일 읽기의 경우
HANDLE fp;
DWORD NumberOfBytesRead;
char lpBuffer[1024];
if ((fp=CreateFile((LPCTSTR)FileName, GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE)
{
// 파일 열기 에러 발생
}
// 필요한 만큼 ReadFile의 호출을 반복한다.
ReadFile(fp, lpBuffer, 1024, &NumberOfBytesRead, NULL);
if (NumberOfBytesRead != 1024)
{
// 파일 읽기 에러 발생
CloseHandle(fp);
}
// 작업이 다 끝났으면 파일을 닫는다.
CloseHandle(fp);
파일 포인터의 이동시에는 SetFilePointer라는 API를 사용하고 파일의 크기를 알고 싶을 때는 GetFileSize라는 API를 사용한다.
18. GetParent API의 리턴값
다이얼로그 박스에서 GetParent API를 호출했을 때 리턴되는 값이 이상합니다.
GetParent API의 리턴값은 보통의 윈도우에서는 윈도우 생성시 지정한 부모/자식 윈도우 간의 관계에 따라 달라집니다. 하지만 다이얼로그 박스의 경우에는 다릅니다. 다이얼로그 박스는 GetParent를 호출하면 무조건 그 것이 속한 응용프로그램의 메인 윈도우 핸들이 리턴됩니다.
19. 특정 프로그램을 실행하고 종료를 기다리기
특정 프로그램을 실행한 다음에 그 프로그램이 종료될 때 다른 일을 하고 싶습니다. 어떻게 해야합니까 ?
다음은 CreateProcess를 이용해서 특정 프로그램의 실행이 끝나기를 기다리는 코드입니다.
if (GetOpenFileName(&ofn)) // 사용자가 파일을 제대로 선택한 경우
{
// ....
}
21. 긴 파일 이름과 짧은 파일 이름간의 변환 방법
주어진 긴 파일 이름에 해당하는 짧은 파일 이름을 알려면 어떻게 해야하나요 ?
긴 파일 이름에서 짧은 파일 이름으로의 변환 : GetShortPathName API 사용
짧은 파일 이름에서 긴 파일 이름으로의 변환 : GetFullPathName API 사용
22. 시스템 이미지 리스트 얻어내기
탐색기 등에서 사용되는 시스템 이미지 리스트를 사용할 수 있는 방법이 있는지 알고 싶습니다.
물론 있습니다. SHGetFileInfo라는 API를 이용하면 됩니다. 이를 이용하면 16X16이나 32X32 크기의 이미지 리스트를 얻어낼 수 있습니다.
sysSmallList = (HIMAGELIST)SHGetFileInfo(TEXT("C:\\"), 0, &sfi, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
sysLargeList = (HIMAGELIST)SHGetFileInfo(TEXT("C:\\"), 0, &sfi, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_ICON);
23. 리스트뷰 컨트롤에서 스타일 동적 변경하기
리스트뷰 컨트롤에서 리스트(LVS_ICON) 스타일을 사용 중인데 이를 실행 중에 리포트(LVS_REPORT) 스타일로 변경하고 싶습니다. 어떻게 해야할까요 ?
윈도우의 스타일은 윈도우 엑스트라 바이트라는 영역에 저장됩니다. 이 곳의 데이터를 읽고 쓰는데 GetWindowLong, SetWindowLong 같은 API를 사용하는데 다음 코드를 예로 보시기 바랍니다.
리스트 스타일에서 리포트 스타일로
LONG lStyle = GetWindowLong(hListWnd, GWL_STYLE);
SetWindowLong(hListWnd, GWL_STYLE, (lStyle & ~LVS_LIST) | LVS_REPORT);
리포트 스타일에서 리스트 스타일로
LONG lStyle = GetWindowLong(hListWnd, GWL_STYLE);
SetWindowLong(hListWnd, GWL_STYLE, (lStyle & ~LVS_REPORT) | LVS_LIST);
24. 윈도우와 다이얼로그에서 클래스 포인터의 사용
제가 만든 C++ 클래스내에서 윈도우를 생성합니다. 생성한 윈도우의 윈도우 프로시저는 그 클래스의 정적 멤버 함수로 선언되어 있습니다. 정적 함수의 경우에는 데이터 멤버를 접근하지 못하기 때문에 접근하게 하려고 윈도우를 생성한 그 클래스의 객체에 대한 포인터를 전역 변수로 유지하여 사용하고 있습니다. 별로 깨끗한 방법도 아닌 것 같고 또 동시에 여러 개의 객체가 동작할 경우에는 에러가 날 수밖에 없는데 해결 방법이 없을까요 ?
이는 다이얼로그의 경우에도 마찬가지입니다. 윈도우 생성시 사용하는 CreateWindow나 CreateWindowEx 같은 함수를 보면 마지막 인자로 윈도우 생성 데이터라는 것을 지정하게 되어있습니다. 다이얼로그 생성시에는 DialogBox라는 API말고 DialogBoxParam이라는 API가 있어서 마지막 인자로 초기화 데이터를 넘겨줄 수 있도록 되어있는데 이것과 윈도우 엑스트라 바이트를 같이 사용하면 정적함수로 선언된 윈도우 프로시저나 다이얼로그박스 프로시저내에서 객체에 대한 포인터를 사용할 수 있습니다. 예를 들어 살펴보겠습니다.
윈도우의 경우
다음과 같이 CExplorerBar라는 클래스내에서 윈도우를 하나 생성합니다. CreateWindowEx 함수나 CreateWindow 함수의 마지막 인자로 this 포인터를 지정합니다.
switch (uMessage)
{
case WM_NCCREATE:
{
LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam;
pThis = (CExplorerBar*)(lpcs->lpCreateParams);
SetWindowLong(hWnd, GWL_USERDATA, (LONG)pThis);
}
break;
case WM_CREATE :
return pThis->OnCreate();
WM_NCCREATE 메시지에서 lParam 인자로 넘어오는 CExplorerBar 객체에 대한 포인터를 받아서 윈도우 엑스트라 바이트로 저장하고 있습니다. 윈도우 엑스트라 바이트는 윈도우 마다 할당되는 고유의 영역으로 사용자 정의 영역으로 GWL_USERDATA가 정의되어 있습니다. 여기에다 CExplorerBar 객체에 대한 포인터를 저장해두고 윈도우 프로시저에 진입할 때마다 이 값을 pThis라는 변수에 대입해 놓고 사용합니다. 참고로 WM_NCCREATE는 WM_CREATE 메시지보다 먼저 발생하는 메시지입니다.
다이얼로그의 경우
예를 들어 CKTree라는 클래스내의 한 멤버 함수에서 다이얼로그 박스를 띄운다고 가정하겠습니다.
BOOL CKTree::SelectFolder(short sTypes, long dwFolderID, short bCreationEnabled)
{
// ......
if (DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_FOLDER_SELECT), hWnd, (DLGPROC)SelectFolderDlg, (LPARAM)this))
DialogBoxParam 함수는 DialogBox 함수보다 인자가 하나 더 있는데 그것이 바로 다이얼로그 프로시저의 WM_INITDIALOG 메시지의 lParam 인자로 넘어갑니다. 여기에 CKTree 객체에 대한 포인터를 넘깁니다. 그리고나서 다이얼로그 박스 프로시저의 WM_INITDIALOG 메시지에서 이를 받아서
LRESULT CALLBACK SelectFolderDlg(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
{
// lParam으로 넘어온 값을 KPointer라는 윈도우 프로퍼티에 저장한다.
SetProp(hDlg, "KPointer", (HANDLE)lParam);
.......
case WM_NOTIFY :
{
// CKTree 객체에 대한 포인터가 필요하면 KPointer 윈도우 프로퍼티 값을 읽어들인다.
CKTree *pKTree = (CKMartTree *)GetProp(hDlg, "KPointer");
if (pKTree->m_bWait)
{
.....
여기서는 앞서 윈도우와는 달리 윈도우 프로퍼티라는 것을 이용해서 넘어온 포인터 정보를 저장하고 필요할 때 읽어옵니다. 여기서 앞서의 윈도우 엑스트라 바이트를 사용해도 무방합니다.
25. 특정 프린터로 출력하기
제 PC에는 두 대의 프린터가 붙어있습니다. 다이얼로그를 띄우지 않고 상황에 따라 다른 프린터로 출력하고 싶은데 동적으로 HDC를 생성하는 방법을 모르겠습니다.
CreateDC를 이용하면 됩니다. 시스템 디렉토리의 WIN.INI를 보면 [Devices]라는 섹션이 있는데 이 아래로 이 시스템에 설치되어 있는 모든 프린터 드라이버의 목록이 나옵니다. 예를 들어 다음과 같이 나옵니다.
[Devices]
HUNFAX=HUNFAX,FaxModem
삼성 SLB-6216H PCL5=SSMPCL5,\\영업팀\볼륨프린터
......
프린터별로 DeviceName=DriverName,OutputName와 같은 구조로 구성되어 있습니다. 이 값들을 CreateDC의 인자로 사용하면 됩니다. 예를 들어 위에서 두 번째 프린터의 DC를 생성하려면 다음과 같이 CreateDC를 호출합니다.
// hDC = CreateDC(DriverName, DeviceName, OutputName, NULL);
hDC = CreateDC ("SSMPCL5", "삼성 SLB-6216H PCL5", "\\\\영업팀\\볼륨프린터", NULL) ;
CreateDC의 마지막 인자로는 프린터의 설정값을 변경할 수 있습니다. 예를 들어 가로로 찍는다든지 2장을 찍는다든지 하는 설정을 변경하는데 사용됩니다. NULL을 주면 디폴트 값을 사용합니다. 다른 설정을 사용하고 싶다면 다음과 같이 합니다. CreateDC 호출 앞에서 DocumentProperties라는 함수를 호출하여 프린터의 기본 설정을 읽어온 다음에 이를 변경합니다. 다음 예는 출력 방향을 가로로 변경하는 예제입니다.
// 프린터의 핸들을 얻는다.
if (OpenPrinter( lpDeviceName, &hPrinter, NULL))
{
// OpenPrinter로 얻은 프린터의 초기 설정을 DocumentProperties API로 얻어온다.
// 먼저 마지막 인자를 0으로 해서 DocumentProperties를 호출하여 필요한 버퍼의 크기를 알아옵니다.
long lSize = DocumentProperties(GetFocus(), hPrinter, lpPrinterName, NULL, NULL, 0);
lpoutDevMode = (LPDEVMODE)malloc(lSize);
long lRet = DocumentProperties(GetFocus(), hPrinter, lpPrinterName, lpoutDevMode, NULL, DM_OUT_BUFFER);
if (lRet == IDOK)
{
// 프린터의 인쇄 방향 설정을 변경한다.
// 여기서 원하는 변환을 수행한다.
lpoutDevMode->dmOrientation = DMORIENT_LANDSCAPE;
}
hPrnDC = CreateDC (lpDriverName, lpDeviceName, lpOutputName, lpoutDevMode) ;
free(lpoutDevMode);
return hPrnDC;
}
26. 메뉴 관련 함수
메뉴 항목을 하나 추가하려고 합니다. InsertMenuItem API를 사용하는데 윈도우 3.1에서와 사용법이 다른 것 같습니다.
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
mii.fType = MFT_STRING;
mii.fState = MFS_DEFAULT | MFS_UNHILITE;
mii.wID = ID_WORKPLACE_REMOVE;
mii.dwTypeData = "바구니에서 제거";
InsertMenuItem(hSubMenu, GetMenuItemCount(hSubMenu), TRUE, &mii);
27. 코드 실행 중에 다른 윈도우 메시지 처리하기
하나의 함수 내에서 시간이 오래 걸리는 작업을 하고 있습니다. 이 때 작업 취소를 위한 별도의 다이얼로그를 하나 띄워 두었는데 이 쪽의 버튼이 눌리지 않습니다. 어떻게 해야할까요 ?
시간이 오래 걸리는 작업을 별도의 스레드로 만들어 처리하던지 아니면 시간이 오래 걸리는 작업을 수행하는 함수 안에서 다음 코드를 가끔 호출해주면 됩니다. 만일 루프를 돌고 있다면 루프내에서 한번씩 호출해주면 됩니다.
28. 메인 윈도우에서 캡션을 제거하고 싶습니다. 어떻게 해야 합니까 ?
윈도우를 생성할 때 WS_CAPTION이란 스타일을 지정하지 않아도 항상 윈도우의 캡션이 보입니다. 이를 제거하려면 어떻게 해야 하나요 ?
캡션을 제거하려는 윈도우의 WM_NCCREATE 메시지를 처리해야 합니다. 이 메시지는 WM_CREATE 메시지보다 앞서 발생하는 메시지입니다. NC는 Non-Client를 나타냅니다. GetWindowLong과 SetWindowLong API를 이용해서 WS_CAPTION 스타일을 제거합니다. 이 두 API는 앞서 리스트뷰 컨트롤에서 스타일 동적 변경하기에서 이미 사용해본 바 있습니다.
case WM_NCCREATE :
{
long lStyle;
lStyle = GetWindowLong(hWnd, GWL_STYLE);
lStyle = (lStyle & (~WS_CAPTION));
SetWindowLong (hWnd, GWL_STYLE, lStyle);
return TRUE;
}
29. 사각형 형태 이외의 모양을 갖는 윈도우를 띄우고 싶습니다.
윈도우는 기본 모양이 사각형인데 다른 형태의 모양을 갖는 윈도우를 띄우려면 어떻게 해야합니까 ?
원하는 모양을 Region이란 것으로 만들어야 합니다. 만든 다음에 이것을 원하는 시점에 SetWindowRgn라는 API를 이용해 윈도우에 설정해주면 됩니다. 예를 들어 타원 모양의 윈도우를 띄우고 싶다면 다음과 같이 해주면 됩니다.
HRGN g_hRgn;
g_hRgn = CreateEllipticRgn(0, 0, 700, 600);
SetWindowRgn(hWnd, g_hRgn, FALSE);
이렇게 했을 경우 윈도우의 위치 이동이 문제가 됩니다. 보통 캡션을 잡고 이동시켜야 하는데 캡션이 없으니까 문제가 됩니다. 이에 관한 것은 31. 윈도우의 이동 처리하기를 참고하기 바랍니다.
30. 시스템에 설치되어 있는 모든 프린터 드라이버 알아내기
현재 시스템에 설치되어 있는 모든 프린터 드라이버의 종류를 알아내고 싶습니다.
EnumPrinters라는 API를 사용하면 됩니다. 다음 코드는 현재 시스템에 설치되어 있는 모든 프린터 드라이버(로컬과 네트웍 프린터 포함)의 이름을 메시지박스로 보여주는 예제입니다.
// 버퍼의 크기를 알아낸다. cbRequired로 들어온다.
EnumPrinters(PRINTER_ENUM_CONNECTIONS | PRINTER_ENUM_LOCAL, NULL, 1, (unsigned char *)lpBuffer, 0, &cbRequired, &nEntries);
cbBuffer = cbRequired;
// 버퍼를 다시 버퍼를 잡는다.
lpBuffer = (PRINTER_INFO_1 *)malloc(cbBuffer);
// 프린터의 종류를 알아낸다.
bSuccess = EnumPrinters(PRINTER_ENUM_CONNECTIONS | PRINTER_ENUM_LOCAL, NULL, 1, (unsigned char *)lpBuffer, cbRequired, &cbRequired, &nEntries);
if (bSuccess == FALSE)
{
free(lpBuffer);
// 다른 이유로 에러가 난 경우
return;
}
// 알아낸 프린터를 하나씩 enumerate한다.
for (int i = 0;i < nEntries; i++)
{
::MessageBox(NULL, lpBuffer[i].pName, "프린터 이름", MB_OK);
}
free(lpBuffer);
31. 윈도우의 이동 처리하기
보통 윈도우는 캡션 영역을 잡고 위치 이동을 수행하게 되는데 윈도우의 특정 영역을 잡고 이동할 수 있게 하려면 어떻게 해야합니까 ?
WM_NCHITTEST라는 메시지를 잘(?) 처리하면 어느 영역이든 윈도우 이동을 처리할 수 있습니다. 마우스로 윈도우 위를 이동하면 WM_NCHITTEST, WM_SETCURSOR, WM_MOUSEMOVE 같은 메시지들이 발생합니다. WM_NCHITTEST는 현재 마우스가 윈도우의 어느 영역위에 있는지 알아내기 위해 사용됩니다. 이 메시지의 처리부에서 HTCAPTION이란 값을 리턴해주면 윈도우 운영체제는 지금 마우스 포인터가 윈도우의 캡션 부분에 와있다고 생각해서 여기서 드래그 작업이 시작될 경우에 윈도우의 위치를 이동시켜 버립니다. 다음 코드는 WM_NCHITTEST 메시지를 처리하여 현재 마우스 좌표가 정해진 영역에 있으면 HTCAPTION을 돌려주는 예제입니다.
case WM_NCHITTEST:
{
// 현재 마우스 위치를 바탕으로 pt 변수를 채운다.
POINT pt(LOWORD(lParam), HIWORD(lParam));
// 마우스 좌표를 윈도우의 좌측 상단 기준의 좌표로 변경한다.
ScreenToClient(hWnd, &pt);
// 지정된 사각형 안에 포함되는 점인지 검사한다.
// g_TitleRect는 RECT 타입의 변수로 지정된 사각형의 좌표가 들어있다.
if (PtInRect(&g_TitleRect, pt))
return HTCAPTION;
break;
}
32. 바탕 화면 위의 모든 윈도우를 최소화하거나 모든 최소화 실행 취소
바탕 화면 위의 모든 윈도우를 최소화하거나 모두 최소화 실행 취소를 프로그램으로 구현하는 방법을 알고 싶습니다.
태스크바의 빈 공간을 오른쪽 마우스 버튼으로 클릭해보면 팝업 메뉴가 뜨는데 거기에 보면 "모든 창을 최소화(M)"와 "모두 최소화 실행 취소(U)" 명령이 존재하는데 그것을 대신 선택해주는 형식으로 프로그램을 작성해주면 됩니다.
다음은 "모든 창을 최소화"해주는 루틴입니다. keybd_event API를 이용해서 사용자가 키입력한 것처럼 흉내내줍니다.
먼저 해당하는 VxD 드라이버의 이름과 위치와 호출하려는 작업의 작업 코드명을 알아야 합니다. 드라이버의 로드는 CreateFile API를 이용합니다. VxD 드라이버의 호출은 DeviceIoControl API를 이용합니다. 자세한 설명은 DeviceIoControl API의 레퍼런스를 참고하기 바랍니다.
DWORD byteReturned;
// 먼저 VxD 드라이버를 오픈한다.
hDevice = CreateFile("\\\\.\\NMOUSE.VXD", 0, 0, 0, CREATE_NEW, FILE_FLAG_DELETE_ON_CLOSE, 0);
if (DeviceIoControl(hDevice, W32_SETHWND, &hWnd, 4, NULL, 0, &byteReturned, NULL))
{
// Success !!!
}
Vxd 드라이버는 kernel 모드(이를 윈도우에서는 Ring 0라고 부릅니다)에서 동작하기 때문에 모든 하드웨어와 메모리를 바로 접근할 수 있습니다. VxD 드라이버를 작성하려면 DDK를 이용하거나 Numega의 DriverStudio나 KRFTech사의 WinDriver를 이용해야 합니다.
34. Thread 실행시 에러
CreateThread를 이용해 스레드를 만들어 생성하고 있습니다. 루틴에 이상은 없는 것 같은데 스레드가 많이 생성되어 시간이 좀 지나면 에러가 발생합니다. 이유가 무엇일까요 ?
정말로 스레드 코드에 별 이상이 없다면 CreateThread API 대신에 beginthread나 beginthreadex를 사용해보기 바랍니다. 자세한 사항은 마이크로소프트의 Knowledge base를 참고하시기 바랍니다.
35. 윈도우 운영체제 종료하기
프로그램에서 특정 상황이 되면 윈도우 운영체제를 종료하고 싶습니다.
ExitWindowsEx API를 사용하면 됩니다. 이 함수의 원형은 다음과 같습니다.
BOOL ExitWindowsEx(UINT uFlags, DWORD dwReserved);
uFlags로 종료방법을 지정할 수 있습니다. 다음과 같은 값이 가능합니다.
EWX_LOGOFF 현재 사용자를 로그오프한다.
EWX_POWEROFF 시스템을 종료하고 파워오프한다. 파워오프는 이를 지원하는 하드웨어에서만 가능하다.
EWX_REBOOT 시스템을 종료하고 시스템을 재시동한다.
EWX_SHUTDOWN 시스템을 종료한다.
EWX_FORCE WM_QUERYSESSION이나 WM_ENDQUERYSESSION을 보내지 않고 실행중인 모든 프로세스를 종료한다. 위의 네 가지 플래그들과 함께 사용할 수 있다.
36. 디폴트 웹 브라우저 알아내기
디폴트로 지정된 웹 브라우저를 실행하는 방법을 알고 싶습니다.
디폴트로 지정된 웹 브라우저는 레지스트리에 자신을 등록합니다. .htm (혹은 .html) 파일의 편집기로 링크도 되지만 http, ftp, gopher 등의 프로토콜 연결 프로그램으로 등록됩니다. 제 생각에 가장 좋은 것은 http 프로토콜의 연결 프로그램을 찾아보는 것으로 생각됩니다. 다음 레지스트리 항목에 보면 연결된 웹 브라우저의 절대 경로를 알 수 있습니다.
HKEY_CLASSES_ROOT\http\shell\open\command
이 항목의 값을 읽는 방법은 3. 레지스트리 읽기/쓰기를 참고하고 프로그램의 실행에 관한 부분은 19. 특정 프로그램을 실행하고 종료를 기다리기를 참고하거나 ShellExecute API 혹은 WinExec API를 사용하면 됩니다. 이 기능을 수행하는 함수는 다음과 같습니다.
App 클래스의 InitInstance 함수에서 AfxOleInit를 호출하는 부분을 CoInitializeEx(NULL, COINIT_MULTITHREADED)를 호출하는 것으로 변경하기 바랍니다. 그리고 App 클래스에 ExitInstance 함수를 추가하고 거기서 CoUninitialize를 호출하도록 하면 됩니다. MFC의 AfxOleInit는 기본적으로 STA(Single Threading Apartment) 모델을 사용합니다. Thread에서 자신이 생성하지 않는 COM 객체를 접근할 때는 MTA(Multiple Threading Apartment) 모델을 사용해야 합니다.
39. 최상위 윈도우의 종료 방법
현재 최상위 윈도우를 찾아서 종료하는 코드를 만들고 싶습니다.
일단 현재 사용자가 작업 중인 최상위 윈도우의 핸들은 GetForegroundWindow API로 얻어냅니다. 그런데 그 윈도우가 자식 윈도우일 수 있기 때문에 GetParent API를 반복적으로 사용해서 최상위 탑 레벨 윈도우의 핸들을 알아냅니다. 종료하는 방법은 먼저 DestroyWindow를 호출해서 시도해보고 실패하면 시스템 메뉴의 "닫기" 명령을 이용해 처리합니다. 사실 이 것도 실패할 수 있는데 무조건 종료시키고 싶다면 아래 코드에서 주석 처리해 놓은 GetWindowThreadProcessId/Terminate API 부분을 사용하면 됩니다.
40. 인터넷 익스플로러의 위치 경로 알아내기
인터넷 익스플로러가 설치된 절대 경로를 알고 싶습니다.
레지스트리의 HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\App Paths\IExplore.exe 의 기본 값으로 인터넷 익스플로러의 설치 경로가 들어옵니다. 다음 함수를 호출하면 설치 경로를 얻어 줍니다. 인자로 넘어가는 lpPath는 적어도 256바이트 이상의 크기를 갖는 문자 배열이어야 합니다.
BOOL GetIEPath(LPTSTR lpPath)
{
long lRet;
HKEY hKey;
lRet = RegOpenKey(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\IExplore.exe", &hKey);
if (lRet == ERROR_SUCCESS)
{
long cbData = 255;
마이크로프로세서는 주변장치를 통해서 외부와 정보를 교환할 수 있으며 일반적으로 정보를 외부와 교환하는 방법으로는 병렬통신과 직렬통신 2가지로 나눌 수가 있다.
일반적으로 컴퓨터내의 장치와 정보교환을 할 때는 통상적으로 고속의 통신속도를 필요로하여 한꺼번에 많은 정보를 처리할 수 있는 병렬통신 방식을 주로 쓴다.
이는 대량의 정보를 빠른시간에 한꺼번에 처리함으로써 컴퓨터의 성능을 향상 시킬 수가 있기 때문인데 이러한 방법의 대표적인 것이 마이크로프로세서 자체의 정보처리량을 증가시키는 것이며 이것은 데이터 비트수로써 나타난다.
(80286은 16비트의 외부 데이터 비트, 80386, 80486은 32비트의 외부 테이터 비트, 비록 내부에서는 32비트로 동작되지만 64비트의 외부 데이터 비트를 갖는 펜티엄 계열를 보아도 알 수 있다.) 그외 HDD, FDD, VIDEO 카드등이 대표적인 병렬통신 방식을 사용하는 장치라 하겠다. 하지만 모든 경우에 병렬통신 방식을 사용할 수는 없다.
그이유는 통신거리의 제한성, 구현상의 기술적인 어려움과 비용이 너무 비싸다는데있다. 또한 어플리케이션 자체가 고속의 통신속도를 필요로 하지않을 경우도 많다.
이러한 이유로 컴퓨터가 외부와의 통신을 할 때는 직렬통신 방식을 많이 사용한다.
직렬통신 방식이란 데이터비트를 1개의 비트단위로 외부로 송수신하는 방식으로써 구현하기가 쉽고, 멀리갈 수가 있고, 기존의 통신선로(전화선등)를 쉽게 활용할 수가 있어 비용의 절감이 크다는 장점이 있다. 직렬통신의 대표적인 것으로 모뎀, LAN, RS232 및 X.25등이 있다. 하지만 크게 직렬통신을 구분하면 비동기식 방식과 동기식 방식 2가지로 나누어진다. 많은 사람들이 비동기식 통신방식을 RS232로 알고있는데 실질적으로 RS232라는 것은 비동기식 통신콘트롤러에서 나오는 디지털신호를 외부와 인터페이스 시키는 전기적인 신호 방식의 하나일 뿐이다.
일반적으로 RS232를 비동기식 통신방식으로 인식하고 있는 것도 큰무리는 없다. 비동기식 통신방식을 지원하는 대표적인 콘트롤러는 NS사의 16C450과 16C550이며 그외 호환되는 콘트롤러가 다수의 회사에서 생산되지만 성능상의 차이는 없고 호환은 되지 않지만 비동기 통신의 기능을 갖는 콘트롤러는 수십가지의 종류가 있다.
비동기식 통신콘트롤러를 일반적으로 UART(Universal Asynchronous Receiver/ Transmitter)라 부른다. UART에서 나오는 신호는 보통 TTL신호레벨을 갖기 때문에 노이즈에 약하고 통신거리에 제약이 있다. 이러한 TTL신호를 입력받아 노이즈에 강하고 멀리갈 수 있게 해주는 인터페이스 IC를 LINE DRIVER/RECEIVER라 부르며 이중 대표적인 것이 RS422 및 RS485가 있다.
이들 인터페이스 방식의 특성은 아래 표에 나타나 있다.
Specification
RS232C
RS423
RS422
RS485
동작 모드
Single-Ended
Single-Ended
Differential
Differential
최대 Driver/Receiver 수
1 Driver
1 Receiver
1 Driver
10 Receivers
1 Driver
32 Receivers
32 Drivers
32 Receivers
최대 통달거리
약 15 m
약 1.2 km
약 1.2 km
약 1.2 km
최고 통신속도
20 Kb/s
100 Kb/s
10 Mb/s
10 Mb/s
지원 전송방식
Full Duplex
Full Duplex
Full Duplex
Half Duplex
최대 출력전압
±25V
±6V
-0.25V to +6V
-7V to +12V
최대 입력전압
±15V
±12V
-7V to +7V
-7V to +12V
위의 표에서 알 수 있듯이 RS-232과 RS-423(Single-Ended 통신방식) 통신방식은 RS422와 RS485에 비해서 통신속도가 늦고 통신거리가 짧은 단점이 있으나 동작모드에서 알 수 있듯이 하나의 신호전송에 하나의 전송선로가 필요하기 때문에 비용절감의 장점이 있다.(RS422인 경우 하나의 신호 전송에 2개의 전송선로가 필요함) 위의 인터페이스 방식중 RS232, RS422 및 RS485에 대해서 각자 설명하겠다. 현재의 RS422 또는 RS485칩의 경우 위의 표에 나와있는 Driver와 Receiver의 수보다도 훨씬 많이 지원하고 있으며 RS485인 경우 최대 256의 노드를 갖는 칩도 있다.
시리얼통신(Serial Communications)의 기본
Baud Rate (보오레이트)와 BPS(비트/초)
보오(Baud)라고 말하는 단위는 프랑스 전신 공사의 Jean Maurice Baudot씨의 이름에서 유래한다.그는19세기 후반에 5단위 부호를 고안한 인물이다. 보오(Baud)라고 말한 단위는 원래 변조율이나 1초간 통신선의 신호 변경 회수를 가리키는 단어로서 사용되고 있었다.이것은 BPS(bit per second)와 항상 똑같은 것은 아니다. 2개의 시리얼 디바이스를 접속한 경우에는 보오(Baud)와 BPS는 사실상 똑같다. 만약 통신 속도를 19,200BPS로 통신하고 있다면, 1초간에 19,200회 선을 통과한 신호가 변화한다고 말할 수 있다.그러나 모뎀의 등장에 의하여 그 의미가 달라졌다.
모뎀은 전화 회선을 이용하고 데이터를 송수신한다.보오레이트(Baud Rate)는 종래의 통신 회선으로는 최대 2400 보오(Baud)까지로 제한되고 있다.이것은 전화회사에 의하여 공급되는 전화선의 물리적인 한계치이다. 그러나 최근에는 종래의 통신회선이라도 9,600 혹은 그이상의 데이터 전송이 가능해지고 압축 기술등의 발달에 의하여 보다 고속의 데이터 통신이 가능해지고 있다.일반적으로 PC에서는 보오(Baud) 와 BPS는 같다. 그러나 각 신호에 복수의 2진 데이터를 중첩시키는 장치를 사용하는 고속 데이터 전송 분야에서는 BPS가 보오(Baud)속도보다 빠르다.
비트 단위 데이터 전송
시리얼통신에서는 1 바이트를 8개의 비트로 분리해서 한번에 1비트씩 통신선로로 전송한다. 수신측에서는 통신선로를 통해 수신한 비트들을 조립해서 1 바이트를 만들어내야 하는데 이때 1 바이트의 범위를 식별하기 위하여 사용하는 것이 스타트비트와 스톱비트이다. 일단 스타트 비트를 송신하면 송신측(계측 장치)에서는 계속해서 데이터비트를 송신한다.데이터 비트는 설정한 값에 따라 보통 5,6,7,8의 어느 쪽이다. 수신측(PC측) 과 송신측(계측 장치)은 이러한 데이터 비트수와 보오레이트의 값을 일치하게 설정할 필요가 있다.거의 대부분의 장비는 7 또는 8 데이터 비트를 사용해서 데이터를 전송한다.
7 데이터 비트라고 설정되어 있는 경우에는 127보다 큰 ASCII값을 보낼 수 없다. 똑같이 5 데이터 비트의 경우는 31이상의 ASCII 값을 취급할 수 없다.데이터를 송출하면 마지막으로 스톱 비트를 보낸다.스톱 비트의 값은 1 의 값 또는 기호이다.기호라면 이전 데이터 비트의 값이 1이라도 확실하게 스톱 비트로서 잡는 것이 가능하다.스톱비트의 데이터 길이는1,1.5,2비트의 어느 쪽이나 될 수 있다.
패리티 비트(Parity Bit)
스타트 비트와 스톱 비트에 의하여 데이터의 단락을 나타내지만,패리티 비트라고 불리는 것을 이용하여 데이터의 구조를 확인하는 경우가 있다.데이터의 송신 중에 데이터에 어떠한 누락이 생기고 있지 않을까 해서 그것을 체크하는 것이 패리티 비트이다.패리티에는 짝수 패리티(Even parity),홀수 패리티(Odd parity),마크 패리티(Mark parity),스페이스 패리티(Space parity),혹은 패리티 없음(None at all)을 선택할 수 있다. 짝수 또는 홀수 패리티를 이용하면 각 데이터 바이트 중의 1의갯수를 헤아리고 보내진 그 수가 짝수 또는 홀수가 되도록 패리티 비트를 송신한다.
예를 들어 짝수 패리티를 선택했다면 데이터 중에 1이 짝수개 있는 경우,패리티 비트는 0로 된다. 즉,바이너리 데이터 0110 0011에 대한 짝수 패리티는 0 이다.역으로 바이너리 데이터 1101 0110 의 경우,패리티 비트는1로 된다.홀수 패리티는 이 반대로 생각한다.데이터 중에 기수개의 1 이 있는 경우,그것은 0 이 된다.패리티 비트에 의한 에러 체크는 기본적인수법 이다.에러가 발생한 때에 에러의 존재를 알리는 것은 가능하지만 그것이 어느 데이터 중에 있는가 그 소재를 알리는 기능은 있지 않다.또,짝수개의 에러가 데이터 중에서 발생한 경우 패리티 비트로 에러를 검출하는 것이 불가능하다.마크 패리티나 스페이스 패리티는 실용적인 이점이 없기 때문에 현재 대부분 거의 이용되고 있지 않다.
케이블 길이
RS232C 규격으로는 케이블의 길이는 약150cm로 되어 있다.그러나 실제 고품질로 실드(shielded)된 케이블을 사용한다면 최장 3km정도까지 향상시키는 것이 가능하다.실드가 불충분한 케이블의 경우,외부 환경이 크게 영향을 준다.전기적인 노이즈가 발생하기 쉬운 환경에서는 짧은 케이블을 이용해도 노이즈에 영향을 받는다.표준적인 사용 환경에 있어서 24게이지 와이어의 실용적인 길이를 다음에 나타낸다. 이것 이상의 길이에 이용한 경우는 신호 증폭기나 옵티칼 아이솔레이터(Optical Isolators)를 사용한다.옵티칼 아이솔레이터는 LED나 포토 다이오드(Photo Diodes)를 내장하고 있고 시리얼 케이블의 각 와이어의 신호의 품질을 확보한다.전기적인 노이즈는 모든 와이어에 나타나고 영향을 미치지만 신호용 그라운드의 와이어도 영향을 받기 때문에 상대적인 전압 레벨은 확보하는 것이 가능하다
Baud Rate
실드 케이블
비 실드 케이블
110
1500m
300m
300
1200m
300m
1200
900m
150m
2400
600m
150m
4800
150m
75m
9600
75m
30m
RS232C
RS-232C는 「 Recommend Standard number 232 」의 약어이고,「 C 」는 표준 규격의 최신판을 나타내는 것이다.거의 대부분의 PC의 시리얼 포트는 RS-232C의 서브 세트(9핀)가 표준 장비되어 있다.풀 규격은 25-pin의 "D"형태 커넥터로,이 중 22핀을 통신에 사용한다. 그러나 보통의 PC 통신에서는 이들 대부분의 핀은 사용되지 않는다.대부분의 최신 PC에는 통상 수컷(male)의 9핀 "D" 타입 커넥터가 장비되고 있다.
DCE와 DTE 장치
DTE는 데이터 단말장치(Data Terminal Equipment)의 약어이고, DCE는 데이터 통신장치(Data Communications Equipment)의 약어이다. 이러한 약어를 이해한 것으로 데이터를 송신한 장치와 그 신호를 수신한 장치의 관계를 올바르게 이해할 수 있다.보통 PC는 DTE 장치이고 그 반면에 대부분의 다른 디바이스(예:시리얼 디바이스)는 보통 DCE 장치이다.
이하의 설명으로 의미를 잘 알지 못한 경우에는 DTE 장치를 그냥 PC라 하고 DCE 장치를 원격장치(Remote Device)라고 바꾸어 읽어도 괜찮다.
RS-232스탠더드에서는 DTE장치는 25핀의 수컷 커넥터를 사용하고, DCE장치는 25핀의 암컷 커넥터를 사용한다. DTE장치를 DCE장치에 접속하는 경우에는 스트레이트 케이블을 이용한다.역으로 2개의 같은 종류의 장치를 접속하는 경우는 널모뎀(Null Modem) 케이블,즉 크로스 케이블(송신라인과 수신라인을 서로 꼬인 케이블)을 사용한다.
동기(Synchronous) 통신과 비 동기(Asynchronous) 통신
시리얼 통신에는 동기통신과 비 동기 통신의 2 종류의 통신 방식이 있다.동기 통신의 경우,2개의 디바이스 사이에서 동기를 취하고 그 타이밍에 따라 데이터를 송수신한다.데이터의 교환이 없는 사이도 제어용의 신호가 흐르고 있으므로 상대와의 동기를 유지하는 것이 가능하다.실 데이터를 송신한 때는 그것을 수신하고 데이터가 없는 때에는 대기 상태를 나타내는 신호를 교환한다.이처럼 통신이 확립되면 실 데이터를 송수신한 것에 데이터의 시작과 종료를 나타내는 신호가 존재하지 않기 때문에 데이터 전송 속도는 빨라진다. PC의 시리얼 포트는 비 동기장치이다. 그러므로 비 동기 시리얼 통신만 지원한다.
비 동기란 "동기 통신 아님" 의 의미한다.그리고 송신과 수신 아이들(idle) 문자가 필요없다. 그러나 데이터의 처음과 끝에는 반드시 스타트 비트와 스톱 비트가 붙는다.스타트 비트는 데이터의 개시를 나타내고 스톱 비트는 데이터의 종료를 나타내는 것이다.따라서 이들 두 비트의 추가 때문에 비 동기 통신의 속도는 동기 통신에 비교하여 약간 늦어진다.그러나 프로세서는 대기 상태의 때에 여분의 아이들(idle) 문자를 처리할 필요가 없다.
비 동기 통신에 있어서 아이들 상태는 역시 마크(mark)라고 불리고 1의 값을 갖는다.이 값을 이용한 것으로 아이들 상태의 경우와 케이블이 벗어나고 있는 상태를 판별한 것이 가능하다.데이터를 송신하면 반드시 스타트 비트가 동시에 송신된다.즉,스타트 비트의 값은 0(스페이스 상태)로 수신측에 데이터가 송신되고 오는 것을 알린다.
RS232에 대한 설명
RS232C는 EIA(Electronic Industries Association)에 의해 규정되어 졌으며 그내용은 데이터단말기(DTE: Data Terminal Equipment)와 데이터통신기(DCE: Data Communication Equipment)사이의 인터페이스에 대한 전기적인 인수, 컨트롤 핸드쉐이킹, 전송속도, 신호 대기시간, 임피던스 인수등를 정의하였으나 전송되는 데이터의 포맷과 내용은 지정하지 않으며 DTE간의 인터페이스에 대한 내용도 포함하지 않는다.
같은 규격이 CCITT(Consultative Committee for International Telegraph and Telephony) 에서도 CCITT V.24에서 DTE와 DCE간의 상호 접속회로의 정의, 핀번호와 회로의 의미에 대해서 규정을 하고 있다.
여기서는 자세한 기술적인 내용의 기술은 피하고 필요한 내용만 간략하게 기술하겠다. RS232에서 일반적인 내용은 위에서 충분히 기술되어 있으며 기본적으로 알아야 할 내용은 코넥터의 사양, RS232 신호선과 케이블 연결 결선도이다. 이들의 내용은 아래와 같다.
코넥터 사양
신호선에 대한 설명
TXD - Transmit Data
비동기식 직렬통신 장치가 외부 장치로 정보를 보낼 때 직렬통신 데이터가 나오는 신호선이다.
RXD - Receive Data
외부 장치에서 들어오는 직렬통신 데이터를 입력받는 신호선이다
RTS - Ready To Send
컴퓨터와 같은 DTE장치가 모뎀 또는 프린터와 같은 DCE장치에게 데이터를 받을 준비가 됐음을 나타내는 신호선이다.
CTS - Clear To Send
모뎀 또는 프린터와 같은 DCE장치가 컴퓨터와 같은 DTE장치에게 데이터를 받을 준비가 됐음을 나타내는 신호선이다.
DTR - Data Terminal Ready
컴퓨터 또는 터미널이 모뎀에게 자신이 송수신 가능한 상태임을 알리는 신호선이며 일반적으로 컴퓨터등이 전원 인가후 통신 포트를 초기화한 후 이신호를 출력시킨다.
DSR - Data Set Ready
모뎀이 컴퓨터 또는 터미널에게 자신이 송수신 가능한 상태임을 알려주는 신호선이며 일반적으로 모뎀에 전원 인가후 모뎀이 자신의 상태를 파악한후 이상이 없을 때 이신호를 출력시킨다.
DCD - Data Carrier Detect
모뎀이 상대편 모뎀과 전화선등을 통해서 접속이 완료되었을 때 상대편 모뎀이 캐리어신호를 보내오며 이신호를 검출하였음을 컴퓨터 또는 터미널에 알려주는 신호선이다.
RI - Ring Indicator
상대편 모뎀이 통신을 하기위해서 먼저 전화를 걸어오면 전화 벨이 울리게 된다. 이때 이신호를 모뎀이 인식하여 컴퓨터 또는 터미널에 알려주는 신호선이며 일반적으로 컴퓨터가 이신호를 받게되면 전화벨 신호에 응답하는 프로그램을 인터럽터등을 통해서 호출하게 된다.
결선도
DTE to DCE 9 Wire Cables
DTE to DCE 8 Wire Cables
DTE to DTE 3 Wire Cables
DTE to DTE 7 Wire Cables
RS422에 대한 설명
RS422는 EIA에 의해서 전기적인 사양이 규정되어 있으나 물리적인 코넥터 및 핀에 대한 사양은 아직 규정되어 있지 않다. 앞으로 나오는 이들의 내용은 한 예로 규정하여 사용하는 사양이니 이에 대해서 오해가 없으면 한다. RS422에서는 Point To Point 모드와 Multi-Drop 모드 두가지가 있다. Point To Point 모드인 경우 RS232와 신호선당 2개의 라인이 필요한 것만 빼고 사용하는 방법에 있어서 별다른 필요가 없다. 하지만 Multi-Drop 모드인 경우는 사용법이 좀 복잡하다. Multi-Drop의 자세한 내용에 대해서는 다음 란에서 다루고 먼저 코넥터의 사양, RS422 신호선과 케이블 결선도에 대해서 먼저 설명하고자 한다.
코넥터 사양
신호선에 할당된 핀번호는 한 예로 다른제품과 틀릴 수 있음. 일반적으로 사용되는 신호선은 TXD+, TXD-, RXD+ 및 RXD- 이고 나머지 신호선은 거의 사용되지 않는다.
신호선에 대한 설명
신호선에 대한 설명은 RS232와 별차이가 없고 다만 물리적으로 하나의 신호선에 두 개의 라인이 필요한데 그들의 표현은 신호선명뒤에 + 와 - 로써 구분표기 한다. 즉, 예를 들면 RS232의 TXD 신호선이 RS422에서는 TXD+와 TXD-로 나누어 질 뿐이다.
Multi-Drop모드가 사용되는 시스템은 하나의 마스터에 여러개의 슬레이브가 연결되어 마스터가 어떤 슬레이브와 통신을 할것인지를 결정하고 해당 슬레이브를 호출하면 호출된 슬레이브가 응답을하는 체제로 구성되어진다. (앞 쪽의 Multi-Drop 모드 결선도 참조)
이때, 하나의 마스터에 보통 10개(Max 32)까지의 슬레이브가 연결될 수가 있고 이때 마스터는 Point To Point모드로 설정되어 있어도 상관이 없으나 슬레이브는 반드시 Multi-Drop 모드로 설정이 되어져 있어야 한다.
여기서 주의하여야 할내용은 모든 슬레이브의 TX신호라인을 정보를 출력시킬때만 공동 TXD라인에 접속 시켜야만 하며 그렇지않고 하나의 슬레이브가 계속 TX신호라인을 공동 TXD라인에 접속시키면 마스터에 의해서 호출된 다른 슬레이브가 정보를 출력시켜도 계속 접속된 슬레이브 때문에 공동 TXD라인에 전기적인 충돌이 발생되어 마스터로 정보가 전달되지 않는다. 즉 동시에 2개이상의 슬레이브가 공동 TXD라인에 접속하면 않되는 것을 반드시 지켜야만 한다.
TX신호선과 공동 TXD라인에 TX신호선을 접속 또는 단락시켜주는 개폐신호사이에는 S/W 또는 H/W에 의한 적절한 타이밍의 조절이 필요한데 일반적으로 S/W에 의한 방법을 많이 사용한다.
우선 TX신호선과 개폐신호사이의 관계를 알아보는 것이 중요한데 이들간에 필요한 타이밍 정보를 아래 그림을 통해서 알아보고자 한다.
먼저 슬레이브가 마스트로 데이터를 출력하기전(슬레이브측의 UART TXD 신호선) 먼저 개폐신호를 출력시켜야 한다.(슬레이브측의 RS422 개폐신호(Logic "1"이면 접속 Logic "0" 이면 단락)를 참조)
즉, TXD라인을 통해서 출력하는 첫 번째 데이터 "A"의 스타트비트가 출력되기전 최소한 RS422 드라이버칩이 개폐신호를 받고 접속되는데 걸리는 시간인 Driver Enable to Output High Delay Time(tZH)이나 Driver Enable to Output Low Delay Time(tZL)이전에 RS422 개폐 신호를 접속하는 상태로 출력시켜야만 한다.(Logic "1"상태)
여기서 tZH와 tZL의 수치는 칩제조회사마다 약간씩 틀리나 보통 수십에서 수백 nS사이의 값이다.
하지만 이수치값이 최소수치이기 때문에 정확하게 지킬필요는 없고 여유있게 주면된다. 즉, S/W에서 먼저 RS422 개폐신호를 접속상태로 출력시키고 난후 TXD라인에 데이터를 출력시키며 TXD라인에 마지막 데이터의 스톱비트까지 출력되고 난 것을 확인후 개폐 신호를 단락상태로 출력시키면 된다.(그림상에서 데이터 "B"의 스톱비트가 출력된후 RS422 개폐신호가 단락상태(Logic "0")로 전환되는 것을 보면 알수가 있다.)
위그림에서 알수있드시 RS422 개폐신호가 접속상태일 때 슬레이브측의 RS422칩의 출력단인 TXD+와 TXD-출력단에 신호가 출력되어(데이터 "A", "B") 마스터측의 UART RXD입력단에 신호가 입력됨을 알수가 있고(데이터 "A", "B") RS422 개폐신호가 단락 상태일 때 슬레이브측의 TXD+와 TXD-출력단이 플로팅(Hi-Z)상태가 되어 신호가 출력 되지 않아(데이더 "C") 마스터측의 UART RXD입력단에 아무신호가 입력되지 않음을 알수가 있다
TXD+와 TXD-신호는 공동 TXD라인에 접속시 서로 반대의 상태를 갖고 출력되고 단락시 동시에 플로팅 상태임을 그림을 통해 알 수 있다.
일반적으로 RS422 개폐신호는 RTS나 DTR신호중 하나를 사용하며 대부분 RTS신호를 사용한다.
사실 TXD신호선을 S/W에 의해서 접속 또는 단락하는 것 자체에 별문제는 없으나 프로그래머 입장에서는 까다롭고 귀찮은 일임에 틀림없다. 이러한 불편함을 해소하가 위해서 나온방법이 TXD신호선에서 데이터가 나올때만 H/W가 이를 감지하여 자동으로 접속 또는 단락 동작을 자동으로 하는 것이다. 이 방법은 프로그래머에게 편리함과 다른 S/W와의 호환성유지(Multi-Drop용의 S/W가 아닌 경우)에 유용하다.
RS485에 대한 설명
RS485는 EIA에 의해서 전기적인 사양이 규정되어 있으나 물리적인 코넥터 및 핀에 대한 사양은 아직 규정되어 있지 않다. 앞으로 나오는 이들의 내용은 한 예로 규정하여 사용하는 사양이니 이에 대해서 오해가 없으면 한다. RS485인 경우 RS232나 RS422처럼 Full Duplex가 아닌 Half Duplex 전송방식만 지원하기 때문에 RS422의 Multi-Drop 모드의 슬레이브처럼 RS485의 모든 마스터는 TXD신호를 멀티포인트 버스(RS485의 모든 마스터가 공유하는 신호라인을 그렇게 부른다.)에 접속 또는 단락시켜야만 할뿐만 아니라 RXD신호 역시 모드에 따라서는 접속, 단락의 제어를 하여야 한다. RS485에서는 Echo 모드와 Non Echo 모드 두가지가 있다. 이들에 대한 자세한 내용에 대해서는 다음 란에서 다루고 먼저 코넥터의 사양, RS485 신호선과 케이블 결선도에 대해서 먼저 설명하고자 한다.
코넥터 사양
신호선에 할당된 핀번호는 한 예로 적용되며 다른제품과 틀릴 수 있음.
신호선에 대한 설명
신호선에 대한 설명은 RS232와 별차이가 없고 다만 물리적으로 하나의 신호선에 두 개의 라인이 필요한데 그들의 표현은 신호선명뒤에 + 와 - 로써 구분표기 한다. 하지만 UART의 TXD, RXD신호선이 멀티포인트 버스에 의하여 공동으로 사용하게됨에 유의하여야 한다. 즉 하나의 마스터는 멀티포인트 버스를 출력이면 출력, 입력이면 입력으로 구분하여 사용할 수 밖에 없다.
결선도
RS485 Echo, Non Echo 모드에 대한 설명
멀티포인트 버스를 사용하는 시스템은 하나의 버스에 여러개의 마스터가 연결되어 사용한다. 이 때문에 하나의 마스터가 다른 마스터와 통신을 할 경우에는 반드시 출력 개폐를 하여야만 한다.
이것의 원리는 RS422의 Multi-Drop 모드와 동일하니 그쪽을 살펴보시기 바람. 하지만 동시에 여러개의 마스터가 출력을 하여 데이터가 충돌하는 현상이 발생하기 때문에 이러한 문제는 S/W에 의하여 해결되어야 한다. 이렇게 충돌 여부를 확인하는 방법 중 하나가 자기가 보낸 정보를 자기가 받아보아 충돌여부를 확인하는 것인데 이것을 RS485 Echo 모드라 부른다.
즉, 어떤 마스터가 멀티포인트 버스에 예를 들어 "ABC"라는 데이터를 보내면 이것이 자동적으로 되돌아 오므로(Echo) 이것을 읽어와 "ABC"여부를 확인하여 동일한 정보가 아니거나 들어온 데이터의 수가 틀리면 충돌한 것으로 보고 적절한 시간의 지연을 거쳐 다시 출력시켜 정확한 값이 되돌아 올 때 까지 되풀이하면 된다. 이때 마스터의 RXD신호선은 항상 멀티포인트 버스에 접속되어 있어 자신의 데이터 뿐만 아니라 다른 어느 마스터가 보내는 데이터를 받을 수가 있다.
이러한 데이터를 자신에게 필요한 정보 인지를 판단 하는 것은 S/W에 의해서 결정된다. 위의 내용을 요약하면 RS485 Echo 모드는 마스터의 RXD신호선은 항상 멀티포인터 버스 에 접속되어 있고 TXD신호선은 데이터를 출력할 때만 멀티포인터 버스에 접속시키야 하고 나머지는 반드시 단락 시켜야한다. 만약 단락시키지 않으면 RS422의 Multi-Drop모드와 같이 다른 마스터가 데이터를 보내도 충돌이 발생하여 절대로 올바른 송수신이 발생 할 수가 없다.
위의 RS485 Echo 모드에서 자기가 보낸 데이터가 자기자신에게 되돌아 오는 기능을 없앤 것이 RS485 Non Echo 모드이다. RS485 Non Echo 모드는 TXD신호선을 멀티포인트 버스에 접속시키면 그즉시 RXD신호선 이 멀티포인트 버스에서 단락되고, TXD신호선을 멀티포인트 버스에서 단락시키면 그즉시 RXD신호선이 멀티포인트 버스에 접속하게 된다.
멀티포인터 버스에 접속 및 단락할 때 필요한 타이밍관계는 RS422 Multi-Drop에 대한 설명에서와 같다. 일반적으로 RS485 개폐신호는 RTS나 DTR신호중 하나를 사용하며 대부분 RTS신호를 사용한다.
note) 기술문서 자료 발췌(인용) 편집함. 출처) my.dreamwiz.com/drshin/
이미 만들어져 있는 프로젝트를 UNICODE로 전환한다는 것은... 어려운 작업까지는 아니지만 상당한 노가다와 시간을 필요로 하는 일임에는 틀림 없다. 약간의 요령과 정형화된 절차가 있다면 조금이나마 시간과 노력을 절약할 수 있다.
다음은 MBCS로 쓰여진 코드를 UNICODE로 변환하는 순서를 설명한다.
(여기서 말하는 UNICODE란 Generic T-TYPE이다. W-Type 아님... ^^)
1. UNICODE 매크로 선언 Visual Studio (6.0) 실행 -> Project -> C++ 탭에서 Preprocessor definitions: 항목에 _MBCS를 삭제하고 _UNICODE와 UNICODE를 추가
2. UNICODE가 불가한 부분이 있는지 검토
코드 전체를 검토하여 UNICODE로 전환할 수 없는 부분이 있는지 체크한다. 예를 들면 유니코드가 아닌 외부모듈을 사용하는 부분이라던지... 통신 프로토콜 문제로 전환이 불가한 부분, GetProcAddress 와 같이 T-TYPE을 지원하지 않는 API를 호출하는 부분 등이 되겠다. 이런 부분은 억지로 유니코드화 하는 것보다 차라리 ANSI 코드를 내버려 두고 결과 스트링을 UNICODE로 한번 더 변환해서 쓰는게 여러 모로 낫다.
다음의 API를 사용한다.
int WideCharToMultiByte( UINTCodePage, // code page
DWORDdwFlags, // performance and mapping flags LPCWSTRlpWideCharStr, // wide-character string intcchWideChar, // number of chars in string. LPSTRlpMultiByteStr, // buffer for new string intcbMultiByte, // size of buffer LPCSTRlpDefaultChar, // default for unmappable chars LPBOOLlpUsedDefaultChar // set when default char used
);
int MultiByteToWideChar( UINTCodePage, // code page DWORDdwFlags, // character-type options LPCSTRlpMultiByteStr, // string to map intcbMultiByte, // number of bytes in string LPWSTRlpWideCharStr, // wide-character buffer intcchWideChar // size of buffer
);
제대로 Generic하게 Conversion하려면, UNICODE 전환 불가 부분에 대해 다음과 같이 해줘야 한다.
1) Visual Studio에서 ctrl + H 를 눌러 변환 창을 띄운다.
2) "Regular String"에 체크를 한다.
3) Find What 에는 Quoted String을 의미하는 "\:q" 를 입력한다.
(혹은 우측 [▶]를 클릭해 "Quoted String"을 선택하면 입력된다.)
4) Replace With: 에는 _T(\0) 를 입력한다. (\0 는 발견된 스트링을 의미한다.)
5) 찾아지는 Quoted String을 확인하면서 Replace 를 시작한다.
"Replace All"을 해버리고 나서 에러나는 부분을 수정해나가는 방법도 괜찮다.
(보통 #include 등에서 에러가 난다.)
6) 소스코드 내의 모든 파일에 대해 1~5를 반복한다. 이 때 위에서 확인된 변환불가 부분은 제외한다.
7) 에러없이 빌드가 되는지 확인한다.
※ #include "HeaderFile.h" 같은 부분이 #include _T("HeaderFile.h") 로 잘못 전환되지 않도록 조심. ^^;
4. Data Type과 문자열 함수를 T-Type 버젼으로 전환
Data Type과 문자열 함수를 T-Type 버젼으로 전환한다.
이때 API는 100% 대체가 가능한 API로 변환해야 하므로 주의하여 MSDN을 참고하여 진행해야 한다. 예를 들어 strncpy는 _tcsncpy 로 전환해야지 lstrcpyn 으로 전환해서는 안된다. (동작이 미묘하게 다르다. 자세한 내용은 여기 참조)
1) Data Type을 전환한다.
몇가지 예를 들면 다음과 같다.
다음의 항목에 대해서는 울트라에디트 등 텍스트 에디터의 Replace in Files 기능으로 한꺼번에 작업해도 된다. ("완전한 단어" 옵션에 체크하면 실수를 줄일 수 있다.)
다음의 타입들은 VisualStudio의 Replace기능으로 하나하나 확인하며 파일별로 전환해야 한다.
"CHAR" -> "TCHAR"
"char" -> "TCHAR"
※ 소켓 통신이나 파일 I/O 등에서 CHAR Array를 버퍼로 사용하는 경우는 TCHAR로 전환해선 안된다. (이럴 땐 CHAR Array보다 BYTE array를 쓰는 게 맞다.)
※ 혹시 "unsigned char" 와 같이 쓰여진 코드를 전환하면 이상하게 되므로.. 조심. ㅡ.ㅡ
2) 문자열 핸들링 함수들을 전환한다.
문자열 핸들링 함수들이 무지 많아서 다 적을 수는 없고... 대표적인 것 몇가지만 적으면 다음과 같다. 다음의 API들은 Replace in Files로 한꺼번에 작업해도 된다.
※ memset, memcpy 등은 문자열 함수가 아닌 메모리 함수이므로 건드리면 안된다.
※ lstrcpyn 계열의 함수들은 원래 T-Type을 지원하므로 건드릴 필요 없다.
※ 위의 함수들은 인자의 종류와 순서가 완전히 동일하므로 함수 이름만 Replace하면 유니코드로 전환이 된다. 만약 strcpy와 같이 버퍼체크를 안하는 함수를 버퍼체크 하는 버젼으로 변경하고 싶다면... 먼저 _tcscpy로 변환하여 유니코드로 컨버젼을 끝낸 다음에 _tcscpy_s (혹은 _tcsncpy) 로 다시한번 전환하는 것이 낫다.
3) 문자열 핸들링 함수의 버퍼 사이즈에 sizeof가 사용된 곳이 없는지 체크하고, 있다면 _countof로 변환한다.
문자열 함수에서 버퍼사이즈는 버퍼의 바이트수가 아닌 버퍼의 글자수를 지정해야 하며, memcpy 등 메모리 함수는 글자수가 아닌 바이트수를 지정해야 한다. MBCS일 때는 1글자가 1바이트이므로 sizeof가 문제가 안되지만, 유니코드일 때는 지켜주어야 한다.
VisualStudio 6.0의 경우 _countof를 지원하지 않으므로 다음과 같이 Define해서 사용하면 된다.
memcpy (lpBuffer, lpSource, sizeof(szBuffer)); // OK!
memset (lpBuffer, NULL, _countof(szBuffer)); // Wrong!! _countof가 아닌 sizeof 를 사용해야 한다~!!
5. 기능테스트를 진행하면서 오류나는 부분을 체크하고 수정
모.. 사람마다 생각이 틀리겠지만... 코드 전환을 꼼꼼히 해봐도 어차피 컨버젼하고 나면 결함투성이고 에러를 다 잡아야 한다. 내생각엔 차라리 코드 전환을 대충(?) 해놓고, 테스트를 꼼꼼하게 하는게 낫지 않을까 싶다. ㅎㅎ
유니코드는 16비트의 단일한 값으로 지구상의 모든 문자를 표현할 수 있는 문자 코드 체계이다. 유니코드의 등장 배경과 내부적인 구성 원리 등의 자세한 사항에 대해서는 다음에 따로 상세하게 다루되 여기서는 준비만 해 두자. 유니 코드를 지원하려면 문자형이나 문자열에 대해 C언어의 타입을 바로 쓰지 말고 유니코드 설정에 따라 변경되는 중간 타입을 사용한다. C언어에 익숙한 사람들은 앞으로 문자나 문자열을 표현할 때 다음 타읍들을 쓰도록하자
C 타입
유니코드 타입
char
TCHAR
char
LPSTR
const char*
LPCTSTR
TCHAR는 C의 기본 타입 중 하나인 char와 일단 같지만 유니코드로 컴파일할 때는 wchar_t타입이 된다. Wchar_t는 실제로는 unsigned short 로 정의 되어 있으며 부호없는 16비트 정수형이다. TCHAR타입의 실제 정의문은 다음과 같이 조건부 컴파일문으로 작성되어 있다.
#ifdef UNICODE
typedef wchar_t TCHAR;
#else
typedef char TCHAR;
#endif
char를 바로 쓴 소스는 유니코드로 바꿀 때 일일이 소스를 뜯어 고친 후 다시 컴파일해야 하지만 TCHAR라는 중간 타입을 사용한 소스는 프로젝트 설정에 따라 소스도 같이 바뀌는 효과가 있으므로 소스는 그대로 두고 컴파일만 다시 하면 된다. 문자열이 필요할 때도 char *를 쓰지 말고 가급적이면 LPSTR또는 TCHAR*를 쓰는 것이 현명하다.
C 표준 함수
유니코드 지원 함수
strlen
lstrlen
strcpy
lstrcpy
strcat
lstrcat
strcmp
lstrcmp
sprintf
wsprintf
Strlen은 char타입의 문자열 길이만 조사하지만 lstrlen은 TCHAR타입의 묹열에 대해서도 동작하므로 이식에 훨씬 더 유리하다. 문자열 상수도 타입이 있으므로 겹 따옴표안에 바로 문자열 상수를 쓰지 말고 TEXT 매크로로 둘러 싸는 것이 좋다.
TCHAR *str=”string”;//이렇게 쓰지 말고
TCHAR *str=TEXT(“string”);//TEXT 메크로 안에 문자열 상수를 쓴다.
TEXT 메크로는 유니코드 설정에 따라 문자열 상수의 타입을 변경한다. 유니코등로 컴파일할 때는 각 문자가 16비트의 유니코드 문자가 되며 그렇지 않을 때는 8비트의 안시 문자가 된다.
CString is based on the TCHAR data type. If the symbol _UNICODE is defined for your program, TCHAR is defined as type wchar_t, a 16-bit character type; otherwise, it is defined as char, the normal 8-bit character type. Under Unicode, then, CString objects are composed of 16-bit characters. Without Unicode, they are composed of 8-bit char type.