win32/코드팁

windows 프로그램, 특히 Visual C++에 관련된 유용한 코드 팁을 담는 곳.

내용

  1. Link: Knowledge Base
  2. 다이알로그에 스킨 입히기
  3. 투명 윈도우 만들기
  4. 내 IP 얻는 방법
  5. 트레이 아이콘
  6. 패스 분리하는 방법
  7. FTP 관련
  8. Downloaded Program Files 폴더의 OCX 삭제방법
  9. 디버깅 용도의 실행시간 출력 코드
  10. Trace Tip
  11. 모니터 끄기
  12. 프로세스간 데이터 교환
  13. Log 함수
  14. Ctrl+Esc, Alt+Tab, and Alt+Esc를 막는 법
  15. About box에 URL 링크 걸기
  16. 특정 파일 오픈시 바로 프로그램 불러오기
  17. 드래그 앤 드롭으로 파일 오픈
  18. app가 트레이에만 띄우도록 하려면
  19. single thread 어플리케이션에 취소 버튼 구현
  20. PC시간 셋팅하기

1 Link: Knowledge Base

[WWW]Knowledge base : MFC 및 Visual C++ 프로그래밍에 대한 방대한 tip과 know-how를 담고 있다.

2 다이알로그에 스킨 입히기

void CDlgPictureDlg::OnPaint()  
{ 
    if (IsIconic()) 
    { 
        CPaintDC dc(this); // device context for painting 
 
        SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); 
 
        // Center icon in client rectangle 
        int cxIcon = GetSystemMetrics(SM_CXICON); 
        int cyIcon = GetSystemMetrics(SM_CYICON); 
        CRect rect; 
        GetClientRect(&rect); 
        int x = (rect.Width() - cxIcon + 1) / 2; 
        int y = (rect.Height() - cyIcon + 1) / 2; 
 
        // Draw the icon 
        dc.DrawIcon(x, y, m_hIcon); 
    } 
    else 
    {     
        CWindowDC dc(this); 
        CDC memDC; 
        memDC.CreateCompatibleDC(&dc); 
        CBitmap bitmap; 
        BITMAP bm; 
        bitmap.LoadBitmap(IDB_BITMAP1);      <<-- 비트맵 이미지를 만들어야한다. 
        bitmap.GetBitmap(&bm); 
        CBitmap* pOld = memDC.SelectObject(&bitmap); 
        dc.BitBlt(5, 30, bm.bmWidth, bm.bmHeight, &memDC, 0, 0, SRCCOPY); 
        dc.SelectObject(pOld); 
        memDC.DeleteDC(); 
        CDialog::OnPaint(); 
    } 
  
} 

3 투명 윈도우 만들기

BOOL CWin1Dlg::OnInitDialog() 
{ 
    CDialog::OnInitDialog(); 
  
    SLWA pSetLayeredWindowAttributes = NULL;     // 함수포인터 선언, 초기화. 
    HINSTANCE hmodUSER32 = LoadLibrary("USER32.DLL");  // 인스턴스 얻음. 
    pSetLayeredWindowAttributes=(SLWA)GetProcAddress(hmodUSER32,"SetLayeredWindowAttributes");   //함수포인터 얻음. 
    HWND hwnd = this->m_hWnd;                              //다이얼로그의 핸들 얻음. 
 
    SetWindowLong(hwnd, GWL_EXSTYLE,GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED); 
    pSetLayeredWindowAttributes(hwnd, 0, (255 * 70) / 100, LWA_ALPHA); 
 
    return TRUE;  // return TRUE  unless you set the focus to a control 
} 
 
// 해당 다이알로그의 상단에 추가 
#define WS_EX_LAYERED           0x00080000 
#define LWA_COLORKEY            0x00000001 
#define LWA_ALPHA               0x00000002 
#define ULW_COLORKEY            0x00000001 
#define ULW_ALPHA               0x00000002 
#define ULW_OPAQUE              0x00000004 
  
typedef BOOL(WINAPI *SLWA)(HWND, COLORREF, BYTE, DWORD); 

4 내 IP 얻는 방법

CString GetMyIPAddress() 
{ 
    char        chName[255]; 
    CString        sAddress; 
    PHOSTENT    pHostEntry; 
    IN_ADDR        inAddr; 
 
    if( gethostname( chName, 255 ) != 0 ) return ""; 
    else 
    { 
        if( ( pHostEntry = gethostbyname( chName ) ) == NULL ) return ""; 
        else 
        { 
            memcpy( &inAddr, pHostEntry->h_addr, 4 ); 
            sAddress.Format( "%s", inet_ntoa( inAddr ) ); 
            return sAddress; 
        } 
    } 
} 

5 트레이 아이콘

void CWin1Dlg::OnButton1()  
{ 
            //일단 트레이아이콘 생성 
 
    NOTIFYICONDATA nid;     
                     
    nid.cbSize = sizeof(nid);  
    nid.hWnd = m_hWnd;         
    nid.uID = IDR_MAINFRAME;   
    nid.uFlags = NIF_MESSAGE|NIF_ICON|NIF_TIP; 
    nid.uCallbackMessage = WM_ICON_NOTIFY; 
    //이 부분이 중요 - 트레이 아이콘에서 이벤트 발생시 발생되는 메세지 정의하는 부분..  
    nid.hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);  
    strcpy(nid.szTip,"Test"); 
    Shell_NotifyIcon(NIM_ADD,&nid);  
    SendMessage(WM_SETICON,(WPARAM)TRUE,(LPARAM)nid.hIcon); 
 
    //일단 아이콘 생성하기 전에 다이얼로그를 숨겨야 겠죠..^^ 
    ShowWindow(SW_HIDE); 
} 
 
void CWin1Dlg::OnButton2()  
{ 
    //다음 트레이아이콘 삭제 
    NOTIFYICONDATA nid; 
 
    nid.cbSize = sizeof(nid); 
    nid.hWnd = m_hWnd; 
    nid.uID = IDR_MAINFRAME; 
    Shell_NotifyIcon(NIM_DELETE,&nid); 
 
    //글구 이젠 다이얼로그를 나타나게.. 
    ShowWindow(SW_SHOW); 
} 
 
LRESULT CWin1Dlg::OnTrayNotification(WPARAM wParam, LPARAM lParam) 
{ 
    // 왼쪽 마우스 버튼이 더블 클릭되었을 경우 
   if (LOWORD(lParam) == WM_LBUTTONDBLCLK)  
   { 
                // 프로그램 정보 다이얼로그 박스 출력 
                SendMessage(WM_COMMAND, IDC_BUTTON2); 
   } 
   return 1; 
} 

6 패스 분리하는 방법

void _splitpath( const char *path, char *drive, char *dir, char *fname, char *ext ); 

를 쓰면 됩니다. 다음은 MSDN에 나와있는 예제입니다

#include  
#include  
 
void main( void ) 
{ 
   char path_buffer[_MAX_PATH]; 
   char drive[_MAX_DRIVE]; 
   char dir[_MAX_DIR]; 
   char fname[_MAX_FNAME]; 
   char ext[_MAX_EXT]; 
 
   _makepath( path_buffer, "c", "\\sample\\crt\\", "makepath", "c" ); 
  
   printf( "Path created with _makepath: %s\n\n", path_buffer ); 
  
   _splitpath( path_buffer, drive, dir, fname, ext ); 
  
   printf( "Path extracted with _splitpath:\n" ); 
   printf( "  Drive: %s\n", drive ); 
   printf( "  Dir: %s\n", dir ); 
   printf( "  Filename: %s\n", fname ); 
   printf( "  Ext: %s\n", ext ); 
} 

7 FTP 관련

#include"CFTPTrans.h" 
 
CFTPTrans::CFTPTrans() 
{ 
} 
 
CFTPTrans::~CFTPTrans() 
{ 
    DisConnect(); 
} 
 
BOOL CFTPTrans::Connect(char *serverName, int port, char *username, char *password) 
{ 
    hInternet = InternetOpen("File Transfer for FTP", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0); 
    if(hInternet == NULL) 
    { 
        errCode = GetLastError(); 
        return FALSE; 
    } 
  
    hFtpSession = InternetConnect(hInternet, serverName, port, username, password, INTERNET_SERVICE_FTP, INTERNET_FLAG_PASSIVE, 0); 
    if (NULL == hFtpSession) 
    { 
        InternetCloseHandle(hInternet); 
        errCode = GetLastError(); 
        return FALSE; 
    } 
    return TRUE; 
} 
 
CFTPTrans::DisConnect() 
{ 
    InternetCloseHandle(hFtpSession);  
    InternetCloseHandle(hInternet); 
} 
  
BOOL CFTPTrans::ChangeDirectroy(char *dirName) 
{ 
    if(!FtpSetCurrentDirectory(hFtpSession, dirName)) 
    { 
        errCode = GetLastError(); 
        return FALSE; 
    } 
    return TRUE; 
} 
  
BOOL CFTPTrans::PutFile(char *source, char *destination) 
{ 
    if(!FtpPutFile(hFtpSession, source, destination, FTP_TRANSFER_TYPE_BINARY | INTERNET_FLAG_RELOAD, 0)) 
    { 
        errCode = GetLastError(); 
        return FALSE; 
    } 
    return TRUE; 
} 

8 Downloaded Program Files 폴더의 OCX 삭제방법

typedef HRESULT (WINAPI *REMOVECONTROLBYNAME) 
( 
             LPCTSTR lpszFile, 
             LPCTSTR lpszCLSID, 
             LPCTSTR lpszTypeLibID, 
             BOOL bForceRemove, 
             DWORD dwIsDistUnit 
); 
 
int _tmain(int argc, _TCHAR* argv[]) 
{ 
    HMODULE                   hMod; 
    REMOVECONTROLBYNAME       pfn =  NULL; 
 
    hMod = LoadLibrary("OCCACHE.DLL"); 
    if (hMod == NULL) 
    {   // Error loading module -- fail as securely as possible 
        return 0; 
    } 
 
    HRESULT hr; 
    pfn = (REMOVECONTROLBYNAME)GetProcAddress(hMod, "RemoveControlByName"); 
    if (pfn)  
    { 
        hr = (*pfn)(_T("C:\\WINDOWS\\Downloaded Program Files\\teechart5.ocx"), 
            _T("{B6C10532-FB89-11D4-93C9-006008A7EED4}"), 0, TRUE, TRUE); 
    } 
    FreeLibrary(hMod); 
 
    return 0; 
} 

위에서 빨간 글자는 레지스트리에서 아래의 위치와 같습니다.
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Code Store Database\Distribution Units 위 값의 하위키를 보면 숫자로된 키가 그것입니다.
파란색 글자는 빨간색 숫자로된 키 밑에 '\Contains\Files' 로 들어가서 오른쪽 값을 참고하면됩니다.

9 디버깅 용도의 실행시간 출력 코드

디버깅시 TRACE로 시간정보를 출력하고 싶을때 사용한다.

#include <sys/timeb.h> 
 
void printCurrentTime() 
{ 
    time_t ltime; 
    struct _timeb tstruct; 
    struct tm *gmt; 
 
    time(<ime); 
    gmt = localtime(<ime); 
    _ftime( &tstruct ); 
    TRACE2("called - %u(ms) %s", tstruct.millitm, asctime(gmt)); 
} 

예)

while(1) 
{ 
    Sleep(10); 
    printCurrentTime(); 
} 
 
<실행결과> 
... 
called - 92(ms) Tue Oct 01 17:44:28 2002 
called - 102(ms) Tue Oct 01 17:44:28 2002 
... 

10 Trace Tip

출처: http://www.codeproject.com/debug/tracetips.asp
디버그 창에서 trace 메시지를 더블클릭하면 에디터 커서를 해당 라인으로 위치시킨다.

TRACE(_T("%s(%i) : Please double click on me!\n"), __FILE__,__LINE__); 

11 모니터 끄기

PostMessage( GetDesktopWindow(),WM_SYSCOMMAND, SC_MONITORPOWER, 2 );  

12 프로세스간 데이터 교환

보통 서로 다른 프로세스끼리 데이터를 주고 받을 때는 shared memory나 clipboard를 이용하는데 간단한 데이터를 주고 받을 때는 사용하기에 부담스러운 면이 있다. 이때 간편하게 이용할 수 있는 프로세스간 데이터 교환 방법을 이용한다.

1. GlobalAddAtom()을 이용한 데이터 전송
예)

서버측: 
 
ATOM atom = GlobalAddAtom("This is test!");  
 
HWND hWnd = ::FindWindow(NULL, "AtomClinet");  
 
::PostMessage(hWnd, WM_MYMSG, 0, atom); // atom값을 전송  
 
클라언트측: 
void CATOMClientDlg::OnGetMsg(WPARAM wParam, LPARAM lParam)  
{  
    char vc_b[256];  
 
    ATOM atom = (ATOM)lParam;  
 
    GlobalGetAtomName(atom, vc_b, 256);  // vc_b를 사용  
}  

2. WM_COPYDATA를 이용한 데이터 교환

// source window에서..  
void CSourceWnd::Write(const void* lpBuf, UINT nCount)  
{  
       CWnd *pTraceWnd = CWnd::FindWindow(TRACEWND_CLASSNAME, NULL);  
 
       if (pTraceWnd) {  
              COPYDATASTRUCT cds;  
 
              cds.dwData = ID_COPYDATA_TRACEMSG /* Flag 용도 */;  
              cds.cbData = nCount;  
              cds.lpData = (void*)lpBuf;  
 
              pTraceWnd->SendMessage(WM_COPYDATA,  
                     (WPARAM)AfxGetApp()->m_pMainWnd->GetSafeHwnd(),  
                     (LPARAM)&cds);  
       } else {  
              AfxMessageBox("윈도우를 찾을 수 없습니다.");  
       }  
}  
 
//////////////////////////////////////////////////////  
// target windows에서..  
 
// 메세지 핸들링 설치  
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)  
       //{{AFX_MSG_MAP(CMainFrame)  
       ON_MESSAGE(WM_COPYDATA, OnTraceMsg)  
       .  
       .  
       .  
       //}}AFX_MSG_MAP  
 
END_MESSAGE_MAP()  
 
LRESULT CMainFrame::OnTraceMsg(WPARAM wParam, LPARAM lParam)  
{  
       COPYDATASTRUCT* pcds = (COPYDATASTRUCT*)lParam;  
 
       // pcds 를 사용한다..  
 
       return 0;  
}  

3. Shared section을 이용한 global variable 공유

#pragma data_seg("Shared") 
volatile LONG g_lSharedData = 0;   // initialized data 
#pragma data_seg() 

또는,

__declspec(allocate("Shared")) int g_nSharedData1 = 0;  // initialized data 
__declspec(allocate("Shared")) int g_nSharedData2;      // uninitialized data 

이때 dll의 프로세스간 데이타 공유라면 dll의 def파일에 데이타 공유 섹션을 지정해 줘야 한다.

SECTIONS 
        .Shared Read Write Shared 

이렇게 하면 g_lSharedData은 이 dll을 로딩한 모든 프로세스에서 공유해서 사용할 수 있다

4. File Map을 이용한 메모리 공유

// 
// 파일을 매핑하는 함수 
 
LPVOID InitFileMapping( HANDLE *phFileMap, char *pszShareName ) 
{      
     LPVOID lpMapView; 
 
     // 파일맵 핸들을 생성한다 
     *phFileMap = CreateFileMapping( (HANDLE)0xFFFFFFFF, NULL, PAGE_READWRITE, 
                              0, sizeof(DWORD), pszShareName ); 
     if( INVALID_HANDLE_VALUE == *phFileMap ) 
          return NULL; 
 
     // 파일을 매핑한다 
     lpMapView = MapViewOfFile( *phFileMap, FILE_MAP_ALL_ACCESS, 0, 0, 0 ); 
     if( NULL == lpMapView ) 
          return NULL; 
 
     // 매핑한 데이터의 선두번지를 리턴 
     return lpMapView; 
} 
 
.... 
 
// 
// 파일맵 데이터를 쓰는 부분(1번 프로세스) 
pdwShare = (DWORD*)InitFileMapping( &hFileMap, dszFileMapName ); 
 
DWORD dwCount = 0; 
 
while( !( GetKeyState( VK_ESCAPE ) & 0x8000 ) ) 
{ 
     *pdwShare = ++dwCount; 
 
     printf( "Write - [%3d] %3d\n", dwCount, *pdwShare ); 
 
     Sleep( 500 ); 
} 
 
... 
 
// 
// 파일맵 데이터를 읽는 부분(2번 프로세스) 
DWORD dwCount = 0; 
 
while( !( GetKeyState( VK_ESCAPE ) & 0x8000 ) ) 
{ 
     printf( "Read - [%3d] %3d\n", ++dwCount, *pdwShare ); 
 
     Sleep( 500 ); 
} 
 
... 
 
// 파일매핑 해제 
UnmapViewOfFile( pdwShare ); 
CloseHandle( hFileMap ); 

13 Log 함수

윈도우 프로그래밍을 하다보면 가끔 dll 소스코드를 디버깅해야할 때가 있는데 이때 MSDEV의 디버거를 이용할수도 없기 때문에 디버깅하기가 까다롭다. 때론 printf대용인 MessageBox를 사용하기도 했는데 아무래도 썩 좋은 방법은 아니다. 이때 로그 함수를 만들어서 로그 파일을 생성하는 방법도 유용한 대안이다.
아래는 Nasser Remy Rowhani라는 외국 친구가 쓴 코드인데 써보니 쓸만한 코드라 생각되서 소개한다. 굳이 디버깅 용도뿐만 아니라 API를 후킹하는 응용에서도 유용하게 쓸 수 있다.

#define LogFile "d:\\Logs\\LOG.txt"                   // log파일이 생성되는 위치 
#define Append(text) AppendLog(text, strlen(text))    // log를 기록하는 함수 
 
HANDLE   hLogFile=0;                // log파일의 핸들 
BOOL     IsLogging=false;           // log파일이 제대로 open되었는지 체크하는 플래그 
 
HANDLE OpenLog(char *Filename);     
BOOL CloseLog(HANDLE h=hLogFile);    
DWORD AppendLog(char *str, DWORD uSize, HANDLE h=hLogFile); 
 
HANDLE OpenLog(char *Filename) 
{ 
        HANDLE hLogFile; 
 
        hLogFile = CreateFile( Filename, GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_ALWAYS,0,0); 
        if(hLogFile!=INVALID_HANDLE_VALUE) 
                IsLogging = true;//SetFilePointer(hLogFile, 0,0, FILE_END);//*/ 
         
        return hLogFile; 
} 
 
BOOL CloseLog(HANDLE h) 
{ 
        IsLogging = false; 
        return CloseHandle(h); 
} 
 
DWORD AppendLog(char *str, DWORD uSize, HANDLE h) 
{ 
        DWORD written; 
        if(!IsLogging) return 0; 
 
        SetFilePointer( h, 0, 0, FILE_END ); 
        WriteFile(h, str, uSize, &written, 0); 
 
        return written; 
} 

용례:

hLogFile = OpenLog( LogFile );   // 처음엔 log파일을 open한다. 
   ... 
sprintf(buf, "Error Code: %d", dwErr); 
Append(buf); 
   ... 
CloseLog();                     // 프로그램에서 exit할 때 log를 close 

14 Ctrl+Esc, Alt+Tab, and Alt+Esc를 막는 법

출처: http://msdn.microsoft.com/msdnmag/issues/0700/win32

/************************************************************************ 
Module:  DisableLowLevelKeys.cpp 
Notices: Written 2000 Jeffrey Richter 
**************************************************************************/ 
 
#define _WIN32_WINNT 0x0400 
#include <Windows.h> 
 
LRESULT CALLBACK LowLevelKeyboardProc(int nCode,  
   WPARAM wParam, LPARAM lParam) { 
 
   BOOL fEatKeystroke = FALSE; 
 
   if (nCode == HC_ACTION) { 
      switch (wParam) { 
      case WM_KEYDOWN:  case WM_SYSKEYDOWN: 
      case WM_KEYUP:    case WM_SYSKEYUP:  
         PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT) lParam; 
         fEatKeystroke =  
            ((p->vkCode == VK_TAB) && ((p->flags & LLKHF_ALTDOWN) != 0)) || 
            ((p->vkCode == VK_ESCAPE) &&  
            ((p->flags & LLKHF_ALTDOWN) != 0)) || 
            ((p->vkCode == VK_ESCAPE) && ((GetKeyState(VK_CONTROL) &  
             0x8000) != 0)); 
         break; 
      } 
   } 
   return(fEatKeystroke ? 1 : CallNextHookEx(NULL, nCode, wParam,  
          lParam)); 
} 
 
int WINAPI WinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int) { 
 
   // Install the low-level keyboard & mouse hooks 
   HHOOK hhkLowLevelKybd  = SetWindowsHookEx(WH_KEYBOARD_LL,  
      LowLevelKeyboardProc, hinstExe, 0); 
 
   // Keep this app running until we're told to stop 
   MessageBox(NULL,  
      TEXT("Alt+Esc, Ctrl+Esc, and Alt+Tab are now disabled.\n") 
      TEXT("Click \"Ok\" to terminate this application and re-enable 
            these keys."), 
      TEXT("Disable Low-Level Keys"), MB_OK); 
   UnhookWindowsHookEx(hhkLowLevelKybd); 
 
   return(0); 
} 

15 About box에 URL 링크 걸기

About 대화상자에 웹으로 가는 링크를 넣는 방법. 이것은 액티브X 컨트롤을 이용하는 것이 가장 손쉬운 방법이다. IE에서 제공하는 오브젝트중 '마이크로소프트 웹브라우저'라는 것이 있는데 다음과 같이 하면 프로젝트에 포함된다.

  1. Project 선택
  2. Add to Project 선택
  3. 컴포넌트와 컨트롤 선택
  4. Registered ActiveX Controls 폴더를 선택
  5. Microsoft Web 브라우저라는 오브젝트 선택
  6. 컴포넌트 클래스 대화 상자에서 확인
  7. 대화상자 편집 모드에서 컨트롤 툴바에 등록된 웹브라우저 컨트롤을 대화상자에 드래그해 화면 디자인을 한다.
  8. Class Wizard를 사용해서 Member Variable 탭에서 IDC_WEBxxx에 대한 데이터 멤버를 지정한다. 이때 Add Member Variable 대화상자의 category 콤보를 컨트롤로 지정
  9. 이제는 OnInitDialog등 원하는 지점에서 m_Webxxx로 지정된 멤버 객체 데이터를 사용해 다음과 같이 작성한다.
     m_WebBrowser.Navigate("http://dasomnetwork.com/~leedw", NULL, NULL, NULL, NULL); 
     

위와 같이 해주면 대화상자가 기동 시에 동우의 홈페이지가 대화 상자에 나타날 것이다. 참고로 원격 사이트가 아닌 로컬에 html 문서를 미리 작성해서 가지고 있다가 URL 부분에 "file:///c:/My Documents/DHTML/ftv20/ftexample.html"라고 해주면 html이 나타난다.

16 특정 파일 오픈시 바로 프로그램 불러오기

ACDSee라는 그래픽 뷰어 프로그램같이 해당 그림 파일을 더블클릭하면 프로그램이 실행되면서 그 데이타를 불러오는 것과 같은 기능을 볼 수 있다. 이런 임의 파일의 싨행은 WinExec, ShellExecute API를 사용하면 구현할 수 있다. 여기서 원하는 것은 실행 파일 및 문서 파일들이므로 ShellExecute를 사용해야 한다.

HINSTANCE ShellExecute( 
    HWND hwnd,  
    LPCTSTR lpOperation, 
    LPCTSTR lpFile,  
    LPCTSTR lpParameters,  
    LPCTSTR lpDirectory, 
    INT nShowCmd 
);       

자세한 것은 MSDN 참조

17 드래그 앤 드롭으로 파일 오픈

윈도우 탐색기나 바탕 화면에서 드래그 앤 드롭으로 파일을 불러서 처리하고자 할 경우. 응용 프로그램에서의 드래그 앤 드롭의 처리는 InitInstance() 처리 부분에서 DragAcceptFiles라는 멤버 함수 또는 API를 수행하면 된다. 그러면 윈도우는 드래그 앤 드롭되는 것이 존재할 때 WM_DROP_FILES라는 메시지를 받는다.(이 메시지는 OnDropFiles(..)라는 멤버 함수가 처리한다.) 이 메시지에서 적당히 처리해주면 된다. 이것은 OLE와는 다른 것이니 주의한다.

18 app가 트레이에만 띄우도록 하려면

winamp같은 어플리케이션이 실행될 때 윈도우가 뜨더라도 상태바에는 뜨지 않고 트레이에만 생기는 모습을 보았을 것이다. 이것을 구현하려면 다음의 코드를 사용한다

void HideApplication(HWND hwnd) 
{ 
   ShowWindow(hwnd, SW_HIDE); 
   ShowOwnedPopups(hwnd, FALSE); 
    
   SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0, 
                 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); 
} 

CApp 클래스에 위와 같은 이름을 가진 멤버 함수가 존재하는데 그 함수를 사용해도 된다. 실제 응용 프로그램을 태스크 바에서 사라지도록 하는 것은 SetWindowPos API에 있다. 또한 Shell_NotifyIcon이란 API가 있는데 이것을 이용해봐도 될 것이다.

WINSHELLAPI BOOL WINAPI Shell_NotifyIcon( 
    DWORD dwMessage,  
    PNOTIFYICONDATA pnid 
);       

주의해야할 것은 응용 프로그램은 메인 프레임을 가져야 하나 메인 프레임을 활성화시키지는 않도록 한다. 활성화만 안시키면 <Alt-Tab>에서 나타나지 않을 것이다. 대신 별도의 윈도우를 WS_POS으로 만들어 이를 트레이에 등록해야 한다. 즉, 트레이에 등록된 팝업 스타일의 윈도우가 모든 메시지를 가로채도록 하기 위함이다.

19 single thread 어플리케이션에 취소 버튼 구현

출처: http://www.fhcf.net/misc/id_ws/database/essays/fboyjoe/cancel/cancel.html

어플리케이션을 개발하다가 간혹 processor-intensive한 루틴을 작성해야할 때가 있다. 단일 쓰레드로 이를 구현했을 때는 하나의 루틴이 프로세서 자원을 다 잡아 먹기 때문에 윈도우즈 메시지를 처리할 수 없는 문제에 부딪힌다. 그래서 이때는 processor 자원을 많이 요구하는 연산 부분은 thread로 구현해서 돌려 놓고 원래의 thread는 윈도우 메시지를 처리하는 방법으로 구현을 한다. 그런데 별도의 쓰레드를 만들지 않고 단일 쓰레드에서 processor 자원 요구량이 많은 모듈과 윈도우즈 메시지를 동시에 처리할 수 있는 기법이 있어 소개한다.

결론을 미리 말하자면, processor-intensive한 연산 안에 message pump를 두어 메시지를 처리하도록 하며 유저가 언제든 취소를 원할 때 연산을 취소할 수 있도록 플래그를 검사하도록 하는 것이다. 플래그는 "취소"버튼을 눌렀을 경우에 셋팅되도록 한다. 핵심 부분을 코드로 살펴보면 다음과 같다.

void StartProcessing() 
{ 
   hCancelDialog = CreateDialog (inst, MAKEINTRESOURCE(IDD_DIALOG2),    
                               hwnd, CancelDialog); 
   ShowWindow (hCancelDialog, TRUE);          // 취소 버튼이 있는 대화 상자 출력 
   cancelled = 0;       // 취소 버튼이 눌렸는지에 대한 플래그 
    
   for ( ...) {       // 여기부터가 processor-intensive한 연산 부분이다. 
        .... 
    
        if(!MessagePump()) { // 연산 수행중에도 메시지를 처리할 수 있도록 한다. 
             return;       // WM_QUIT 메시지를 받으면 루틴을 빠져나간다. 
        }  
 
        if(cancelled) { 
            // 취소 플래그가 셋팅되면 연산을 중지한다. 
    
         .... 
    
   } 
   DestroyWindow(hCancelDialog); 
   EnableWindow(hwnd, TRUE); 
    
   .... 
 
BOOL MessagePump()        // 메시지 펌프 
{ 
   MSG msg; 
    
   while(PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {  // 메시지 큐에 메시지가 있는지 체크 
      if (!GetMessage(&msg, NULL, 0, 0)) {    // 메시지를 가져온다. 
         PostQuitMessage(0); 
         return FALSE; 
      } 
      TranslateMessage(&msg); 
      DispatchMessage(&msg); 
   } 
   return TRUE; 
} 

예제 프로젝트 : CancelTestApp.zip

20 PC시간 셋팅하기

void CSetDateDlg::OnOK()  
{ 
        // TODO: Add extra validation here 
 
        CTime time( 2005, 1, 10, 22, 17, 43 );  
        SYSTEMTIME sysTime; 
 
        time.GetAsSystemTime(sysTime); 
 
        if (getSystemType() == VER_PLATFORM_WIN32_NT) 
        { // NT 계열이면 시간을 바꿀 수 있는 특권을 가져와야 한다. 
                HANDLE hToken; 
                TOKEN_PRIVILEGES tp; 
                LUID luid; 
 
                OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken); 
                LookupPrivilegeValue(NULL, "SeSystemtimePrivilege", &luid); 
 
                tp.PrivilegeCount = 1; 
                tp.Privileges[0].Luid = luid; 
                tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 
                AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL); 
        } 
 
        SetLocalTime(&sysTime); 
         
        CDialog::OnOK(); 
} 
 
void CSetDateDlg::getSystemType() 
{ 
        OSVERSIONINFO osv; 
 
        osv.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); 
        GetVersionEx(&osv); 
 
        return osv.dwPlatformId; 
} 



출처: http://www.dasomnetwork.com/~leedw/mywiki/moin.cgi/win32_2f_c4_da_b5_e5_c6_c1?action=highlight&value=VisualC%2B%2B%C6%C1%C1%F6%B5%B5

신고
Posted by 나비 나비:D
태그 API
 글이 상당히 좋은 글임에도 불구하고 보시는 분마다 스트레스(?)를 받는것은 그냥... 좀 어려운내용이 길~게 있으니깐 그럴것입니다.
해서리.. 제가 초간단으로 요점을 간추려서 제 스타일로 다시 설명을 해보겠습니다...
쩝....(이런건 또 첨해보넹.. --;)



API후킹이란?...


1. API는 DLL파일안에 들어있습니다.
  API함수를 사용한다는것은 윈도우가 제공하는 DLL안에 들어있는 함수를 사용하는겁니다.
  그러므로 API후킹을 한다는것은 다른 프로그램이 DLL의 함수를 사용하는것을 내가 가로채는것을 말합니다.


API 후킹의 목적...


2. 가로채서? 그 다음은 그 함수의 기능을 사용하지 못하게 할수도 있고 어떻게 사용하는지 감시만 할 수도 있고 전혀 다른 내용으로 바뀌게끔 할 수도 있습니다. 그러므로 이것을 이용해서 할 수 있는 일을 두가지 정도로만 야그해보면...

3. 다른 프로그램을 디버깅하거나 리버스엔지니어링들을 위해서 사용할 수 있습니다. API함수만 알아가지고 뭘 알수 있겠냐라고 생각할 수도 있겠지만.. 사실 우리가 사용하는 모든 델파이 문법과 라이브러리는 결국 API함수를 사용하기위한 과정에 불과합니다. 윈도우는 디바이스에 내용을 출력하고 키보드 마우스로 입력받고, 다른 장비와 통신하고하는 모든 과정을 API, 메세지, ActiveX 이 세가지로 다 합니다.

4. API후킹을.. 디버깅하는데만 사용하지는 않고 어떤 특별한 기능을 구현할때도 사용합니다. 가장 좋은 예로 노클릭 영한사전이 있습니다. 글씨에 커서를 가져가면 번역을 보여주는.. 그런것을 할때도 API후킹이 필요합니다. 그때는 Text에 관련된 함수들을 후킹하겠지요..

5. DLL의 구조를 좀 알아야합니다. 여기서 DLL의 구조를 안다는것을 PE포맷을 알아야 한다는것과 유사한데 그렇다고 핵사에디터로 DLL을 열어서 바이너리를 들여다 보라는 야그가 아니라 적어도 DLL의 도스헤더, NT헤더(PE헤더) 그리고 임포트테이블 정도까지는 아..그런게 있구나 하는 정도는 알아야한다는겁니다. 물론 그것들을 위한 스트럭쳐(레코드)가 있으므로 그것을 우선 확보해서 사용방법을 알면 되겠습니다.


두 가지 기술...


6. 남의 역역으로 쳐들어가기.. API후킹을 할려면 다른 프로그램의 메모리 영역으로 내가 만든 코드를 침투시켜야합니다. 왜냐하면 NT의 버쳘메모리메니져가 기본적으로 남의 영역을 침범하지 못하게 만들어 놨기 때문입니다. 물론 그것은 9x계열도 마찬가집니다. 그래서.... API 후킹을 하는데 있어서 남의 영역으로 쳐들어 가는 방법은 상당히 중요한 부분입니다.

7. 함수 가로채기.. 일단 쳐들어가면 그 다음에는 함수를 가로채야합니다. 목적이 그것이니깐여... 함수를 가로채는데도 여러가지 방법이 있습니다.


남의 영역으로 쳐들어가기


8. 메세지훅을 이용하는 방법... 메세지 후킹을 시스템 전역으로 설치하면 다른 프로그램이 내가 만든 DLL을 로드하게 됩니다.
  그것을 이용해서 남의 프로그램의 영역에 침투시키는데 이 방법이 가장 일반적입니다.
  그래서 메세지 후킹도 알아야 하는데 잘 할 필요는 없습니다. 그냥 남이 만든거 배껴서 고쳐서 쓸수 있는 정도면 되겠습니다.
  본문에서는 메세지훅을 윈도우즈후크라고 표현했습니다.

  메세지훅을 이용했을때의 장점은 모든 윈도우에서 동일한 방법으로 지원한다.
  미래의 윈도우즈에서도 계속 지원이 유지될것이다.
  훅의 시작과 해제를 자유롭게 제어할수있다..라고 본문에서 설명하고 있는데..
  양병규가 좀더 덧붙이자면 메세지훅은 윈도우가 지원하는 표준적인 방법이라는것이 가장 큰 매력인것같고요...
  본문는 미래에도 이 방법은 바뀌지 않을것같다라고 했는데 제 생각은 좀 다릅니다.
  그렇게 생각하는 가장 큰 이유는 이 방법이 문서화 되어있는 표준적인 개발 방법이기때문에 호환성을 위해서라도 없앨수 없다라고 라고 생각하는데..
  그것은 맞는 말이지만.... 메세지 훅을 통해서 로드된 DLL이 프로그램의 모든 버쳘메모리 영역에 침투할 수 있다..라는 사실은 MSDN어디에도 명시되어있지 않습니다.
  그것이 그렇게 되어있는 이유는 현재의 윈도우 버쳘메모리 메니져가 그렇게 만들어져 있기때문이고 앞으로 MS가 그것이 문제가 심각하다라고 판단하면 언제든지 변경을 할 수 있는 내용입니다.

  메세지훅을 이용하는 방법의 단점은... 시스템의 성능을 확연하게 저하시킵니다. 메세지의 체계에 하나가 더 끼어들기때문에 어쩔수 없이 그렇게 됩니다.
  하지만... 제 생각은 조금 다릅니다. 시스템 성능이 저하는 되지만 확연하게..는 아닙니다... 현저하게 저하되는 경우는 훅프로시져에서 많은 일을 할수록 시스템성능이 더 많이 저하됩니다.
  그런데 API훅의 침투용으로 사용하는 훅프로시져는 대부분 아무일도 안합니다.
  그리고 메세지 훅도 훅 나름입니다. WB_GETMESSAGE훅의 경우 메세지가 발생하는 대부분의 윈도우에 걸립니다. 그러므로 한 프로그램에 윈도가 100개면 그만큼 훅프로시도 많이 동작하게 됩니다.
  WB_CBT훅의 경우는 시스템메뉴메세지가 동작할때 발생하므로 윈도가100개라도 그중에 가장 parent에 있는 윈도만 그 훅을 받아들이고 발생도 자주 안합니다.
  고로...메세지훅의 종류와 사용법에 따라 많이 달라집니다. 암튼.. 그러므로 메세지훅을 잘 이용하면 성능저하가 피부로 느끼지 못하게 할수도 있습니다.
  본문에서 "어떤 특별한 상황(버그 발생)에서는 복구하기 위해 시스템을 리부팅해야만 한다"고 했는데...
  메세지훅이 걸린 상태에서 에러가 발생하는등 훅을 건 프로그램이 정상기능을 수행하지 못하게 되버리면 훅을 해제할 수도 없고 훅용DLL이 사용중이니 재 컴파일 할수 없으므로 재부팅을 해야한다는 말입니다.
  제가 생각하는 메세지훅의 또 다른 단점은 윈도우가 없는 어플리케이션에는 침투할 수 없다는 단점이 있습니다.
  좌우지간 메세지훅을 이용하는 방법은 까다로운면도 많고 장점도 많고... 단점도 있고.... 머 그렇습니다.

9. USER32.DLL의 초기화 부분을 이용하는 방법 USER32.DLL은.. 초기화 부분이 실행될때 레지스트리에 등록된 USER32초기화 DLL목록을 읽어와서 거기에 등록되어진 DLL을 로드하게 되어있습니다.
  흠... 그렇게 만든 이유는 아마도 USER32는 윈도우에서 사용하는 대부분의 객체가 구현되어져 있기때문에 그것과 관련이 있지 않나 싶습니다.
  본문에서 소개하고 있는 레지스트리 경로에 DLL파일을 추가하면됩니다.
  이 방법의 장점은 비교적 손쉽다라는 장점이 있습니다.. 더 이상 다른 장점은 없습니다. ^^;
  단점은 USER32.DLL을 안쓰는 프로그램에는 효과가 없고 NT계열만 되고.. 설치후 재부팅을 해야하고...훅의 시작와 해제를 맘대로 제어할 수 없습니다.
  고로 이방법은 일반 어플리케이션에 적용하기에는 좀 그렇고 거의 디버깅용으로 밖에 쓸 수 없습니다.

10. CreateRemoteThread를 이용하는 방법을 본문의 저자가 젤루 선호한다고 했는데....
   CreateRemoteThread함수를 이용할때 콜백함수를 지정하게 되는데 그 콜백함수의 구조가 LoadLibrary함수의 구조와 동일합니다.
   그래서 콜백함수로 LoadLibrary를 지정해서 로드하게 만든다... 머 그런겁니다.
   이 방법의 단점은 시스템 전역으로 사용하기에는 무리가 있고 주로 디버깅용으로 많이 사용합니다. 글구 NT계열에서만 됩니다.

11. 본문에서 그 외 소개하는 방법은 익스플로러나 오프스등의 플러그인 기능을 이용한다는 방법인데...
   요즘 이걸 이용한 바이러스들이 아주 지랄입니다. 이 넘이 하나 깔리면 V3로 치료를 해도 또 살아나고 그럽니다.
   그래서 바탕화면까지 몽땅 죽여놓은 상태에서 백신을 돌려야 치료가 되곤합니다.
   사실 저도 예전에 민성기님과 이런 저런 야그를 하면서 폴더카피훅을 이용해서 쳐들어가곤 했는데.... 암튼 이방법은 아주 허접합니다. ^^;;
   단점은 걸리면 무쟈게 욕을 먹고 요즘엔 이런것만 잡고 다니는 프로그램도 많습니다.


함수 가로채기...


12. 본문에서는 커널레벨훅과 서브클래싱을 소개하고 있는데 그것은 API후킹이 아닙니다... --;

13. DLL바꿔치기... 본문에서는 Proxy DLL (Trojan DLL)(대리자 DLL)라고 소개하고 있는데...
   API함수를 가지고 있는 DLL을 똑같이 새로 만드는 방법입니다.
   그 DLL에는 원래 DLL이 가지고 있는 API함수를 똑같은 구조로 만들어서 그 함수들은 원래의 DLL함수의 똑같은 함수를 호출합니다.
   아~~주 단순무식한 방법인데 수 년전에 내가 하영재씨와 민성기씨에게 이 방법을 소개했드만 낄낄대고 웃더라... ^^;
   하지만 기술적으로 아주 쉬운방법이긴합니다 ^^;; 물론 실용성은 없슴다 --;

14. Code overwriting(코드 덮어쓰기)
   이 방법은... 메모리상에있는 함수들을 싸그리 뒤져서 CALL명령($E9)을 찾아다가 그 바로 뒤 네바이트(포인터값)을 봐서 그 값이 API함수의 어드레스면 그것을 내가 만든 함수의 어드레스로 바꿔치고 내가 만든 함수에다가 하고싶은 일을 다하고 다시 원래 API함수를 호출하는 .. 머 그런 방법인데...
   잘 만들면 괜찮은 방법인데 이 방법을 써서 제대로 돌아가는 프로그램을 못봤슴다. 1분안에 에러가 안나면 기적이다.. ^^;;

15. Spying by a debugger(디버거를 이용한 스파이)
   이 방법은 아무리 생각해봐도...... API후킹이라고 말하기에는 좀 그렇슴다....
   본문을 쓴 원작자는...  API후킹을 주로 크랙이나 리버스엔지니어링으로 많이 사용하는 경향이 있는것같슴다... 쩝....

16. 임포트테이블 바꾸기
   이 부분은 상당히 길게 설명하고 있는데... 많이 보편화 되어있습니다.... 실제로 피씨방에서 감시용 프로그램등에 많이 사용되고 있지여..
   EXE에는 DLL함수를 연결하는 함수 임포트테이블이란걸 가지고 있습니다. (DLL에도 있따)
   실행이 되면 윈도우가 DLL에 있는 함수의 주소를 그 테이블에 써줍니다. 그래서 그 주소로 점프하게 되는데...
   그 주소를 바꿔치기하는 방법입니다.
   장점은 9x, NT모두 사용할수 있고 비교적 쉽습니다.
   제가 덧붙이면.. 단점이 하나있는데 UPX, ASPack등으로 압축된 실행파일은 임포트테이블을 제거하고 LoadLibrary로 대치하는등 임포트테이블을 망가뜨려버리기때문에 이 방법이 안통합니다.


그 외....


17. 나머지 설명들은 API후킹을 이용하는 프로그램의 구성등에 대해 설명하고 있슴다....
   그러므로 뒷부분은 일단 API후킹을 성공한 후부터 보면될것 같습니다.


그리고 본문에서 소개하는 방법외에도 몇가지를 더 소개하면....


18. 심플코드오버라이팅이라는 방법이 있는데 제가 젤루 선호하는 ^^ 방법입니다.
   원래의 API함수 자체를 수정하는 방법인데 내가 만든 함수로 점프하게하는 코드를 써넣습니다.

19. 익스포트테이블을 수정하는 방법도 있는데 임포트테이블을 수정하는 방법하고 유사합니다.
   다른 점은 임포트테이블을 수정하는 방법은 EXE에 있는 테이블을 수정하는것이고 이 방법은 API함수가 있는 DLL에서 익스포트테이블을 고치는 방법인데(물론 모두 메모리에서 벌어지는 일입니다. 실제 파일을 수정하는게 아닙니다.)
   EXE의 경우 UPX등으로 압축할 수 있지만 API함수가 있는 DLL을 UPX로 압축해서 쓰는 사람은 없으므로 위에서 말한 단점을 보완할수 있다는 장점이 있습니다.


제가 생각하기에는...


20. API후킹을 이용해서 프로젝트를 할 때는 반드시 그것이 통하는 범위를 제한해야합니다.
   예컨데 노클릭영한 사전을 만들면서 한글로된 모든 텍스트를 다 후킹할수 있따...라고 호언장담하지말고..
   캡쳐방지를 만들면서 모든 캡쳐프로그램을 다 막을수 있습니다....라고 장담하지 말라는 야급니다.


쩝...

- 끝 -

헥헥~ @@;
신고
Posted by 나비 나비:D

BLOG main image
by 나비:D

공지사항

카테고리

분류 전체보기 (278)
Programming? (0)
----------------------------- (0)
나비의삽질 (5)
Application (177)
SQL (51)
Web (27)
etc. (14)
Omnia (0)
---------------------------.. (0)

글 보관함

달력

«   2017/10   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        
Total : 882,505
Today : 1 Yesterday : 218