이제 몇 개만 더 구현해 주면 끝이 납니다.

하나씩 구현해 보도록 하죠.
핵심적인 부분만 설명하도록 하겠습니다. 자세한건 패키지 전체 소스를 받아서 보시기 바랍니다.


1. Keyboard Event 받기
 
 - 후킹을 통해서 입력된 키값을 얻어와야 합니다. 그리고나서 기획 의도대로 'Win+C', 'Win+V' 가 눌러진 것을 인식해야 하죠.
후킹 프로시져를 보면 다음과 같습니다.


// Keyboard Hook Proc
LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	// 0 은 무시
	if(nCode < 0) { return 1; }
	int return_code = 1;

	// 키값 가져옴
	DWORD vKey = ((tagKBDLLHOOKSTRUCT *)lParam)->vkCode;

	// ESC 는 허용
	if(wParam == WM_KEYDOWN && vKey != VK_ESCAPE && g_IsCaptureMode == TRUE) { return -1; }

	switch(wParam) {
		case WM_KEYDOWN:
			// 윈도우 키가 함께 눌러진 경우에만
			if(GetAsyncKeyState(VK_LWIN) != NULL) {
				if(vKey == 0x43 && g_IsCaptureMode == FALSE && m_bIsSaveMode == FALSE) {	// Win + C
					g_IsCaptureMode = TRUE;
					PostMessage(m_hMainWnd, WM_WIN_C, wParam, lParam);
					return_code = -1;
				} else if(vKey == 0x56 && g_IsCaptureMode == FALSE && m_bIsSaveMode == FALSE) {	// Win + V
					PostMessage(m_hMainWnd, WM_WIN_V, wParam, lParam);
					return_code = -1;
				}
			}
			break;
	}
	return return_code;
}




일단 'Win' 키가 함께 눌러졌는지 확인하기 위해서 GetAsyncKeyState(VK_LWIN) 를 사용했습니다. 이는 특정 키값이 눌러진 상태인지 아닌지를 알려 줍니다.
즉, 'C' 가 눌러졌을때, Win 키도 눌러진 상태인지 확인하면 되겠지요.

그리고 원하는 이벤트(Win+C / Win+V)가 발생했을 때, 메인 윈도우로 PostMessage 를 해 줍니다.
물론 메세지는 사용자 등록 메세지입니다.(WM_WIN_C / WM_WIN_V)

이런 방식을 쓸 수 밖에 없는 이유는 후킹 프로시저는 global 함수라서 Class 에 접근할 수 없기 때문입니다.
사용자 메세지를 수신받아 처리하기 위해서 핸들러를 등록해 주었습니다.



BEGIN_MESSAGE_MAP(CEasyClipperDlg, CDialog)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	//}}AFX_MSG_MAP
	ON_MESSAGE(WM_TRAY_ICON, OnTrayNotify)
	ON_MESSAGE(WM_WIN_C, OnWinCPressed)
	ON_MESSAGE(WM_WIN_V, OnWinVPressed)
	ON_MESSAGE(WM_CTRL_V,OnCtrlCPressed)
	ON_WM_DESTROY()
	ON_WM_WINDOWPOSCHANGING()
END_MESSAGE_MAP()




각 핸들러는 아래와 같습니다.


LRESULT CEasyClipperDlg::OnWinCPressed(WPARAM wParam, LPARAM lParam)
{
	// Mode Check
	if(g_IsCaptureMode == FALSE || m_bIsSaveMode == TRUE) { return 1; }

	// Canvas Display
	CanvasDlg canvas;
	canvas.DoModal();

	// Clip Area
	RECT area = canvas.GetClipRect();
	if((area.bottom | area.left | area.right | area.top) != 0) {
		ScreenCapture(area);
		SaveToClipboard();
		m_ImageCaptured = TRUE;
	} else {
		m_ImageCaptured = FALSE;
	}

	g_IsCaptureMode = FALSE;
	return 1;
}




LRESULT CEasyClipperDlg::OnWinVPressed(WPARAM wParam, LPARAM lParam)
{
	// Capture 된 Image 가 없을 경우
	if(m_ImageCaptured == FALSE || m_bIsSaveMode == TRUE) { return 1; }
	m_bIsSaveMode = TRUE;

	// Dialog 를 최상위로 올림
	if(::GetForegroundWindow() != this->m_hWnd){ 
		HWND h_active_wnd = ::GetForegroundWindow(); 
		if(h_active_wnd != NULL){ 
			DWORD thread_id = GetWindowThreadProcessId(h_active_wnd, NULL); 
			DWORD current_thread_id = GetCurrentThreadId(); 
			if(current_thread_id != thread_id){ 
				if(AttachThreadInput(current_thread_id, thread_id, TRUE)){ 
					BringWindowToTop(); 
					AttachThreadInput(current_thread_id, thread_id, FALSE); 
				} 
			} 
		} 
	}
	
	// File Dialog
	CFileDialog dlg(FALSE, NULL, NULL, OFN_EXPLORER | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,  _T("JPEG (*.jpg)|*.jpg|PNG (*.png)|*.png|Bitmap (*.bmp)|*.bmp||"), this, 0, 0);
	if(dlg.DoModal() == IDOK) {
		CString fileName = dlg.GetPathName();
		switch(dlg.m_pOFN->nFilterIndex) {
			case 1:	fileName.Append(_T(".jpg")); SaveImage(fileName, m_jpegClsid); break;
			case 2: fileName.Append(_T(".png")); SaveImage(fileName, m_pngClsid); break;
			case 3: fileName.Append(_T(".bmp")); SaveImage(fileName, m_bmpClsid); break;
		}
	}

	m_bIsSaveMode = FALSE;
	return 1;
}




:: 후킹이 어느 순간부터 동작하지 않는 문제

 후킹을 하다보면, 어느 순간부터 후킹이 되지 않는 경우가 발생합니다.
즉 자동으로 후킹이 제거되는 경우입니다. 원인은 MSDN 에서 찾아 볼 수 있습니다.

http://msdn.microsoft.com/en-us/library/windows/desktop/ms644985(v=vs.85).aspx

The hook procedure should process a message in less time than the data entry specified in the LowLevelHooksTimeout value in the following registry key:

HKEY_CURRENT_USER\Control Panel\Desktop

The value is in milliseconds. If the hook procedure times out, the system passes the message to the next hook. However, on Windows 7 and later, the hook is silently removed without being called. There is no way for the application to know whether the hook is removed.

 즉, 윈도우 7 이후부터는 후킹 프로시저에서 너무 오랜 시간을 빼앗는다고 생각되면, 자동적으로 훅을 remove 시켜버립니다.
그렇기 때문에 프로시저는 최대한 간단한 작업만 해야 합니다.

이런 이유로 제가 프로시저에서 SendMessage() 가 아닌 PostMessage() 를 사용한 것입니다.
실제로 개발할때 SendMessage() 로 사용했더니, 어느 순간부터 훅이 자동으로 제거 되었습니다. =_=; 

 




2. Screen Capture 하기

윈도우 화면을 캡처하기 위해서는 아래와 같이 하시면 됩니다.


void CEasyClipperDlg::ScreenCapture(RECT area)
{
	// 기존 Bitmap 리소스 해제
	if(m_hCaptureBitmap != NULL) {
		DeleteObject(m_hCaptureBitmap);
		m_hCaptureBitmap = NULL;
	}

	// Capture
	int nClipWidth = area.right - area.left;
	int nClipHeight = area.bottom - area.top;

	HWND hDesktopWnd = ::GetDesktopWindow();
	HDC hDesktopDC = ::GetDC(hDesktopWnd);
    HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
    m_hCaptureBitmap =CreateCompatibleBitmap(hDesktopDC, 
                            nClipWidth, nClipHeight);
	::SelectObject(hCaptureDC,m_hCaptureBitmap); 
	::BitBlt(hCaptureDC,0,0,nClipWidth,nClipHeight,
		hDesktopDC,area.left,area.top,SRCCOPY|CAPTUREBLT);

	// 리소스 해제
	::ReleaseDC(hDesktopWnd,hDesktopDC);
    DeleteDC(hCaptureDC);
}


 


3. HBITMAP 을 Image(jpg, png, bmp)로 저장

캡처한 이미지를 저장하려면 아래처럼 해 줍니다.
참고로 코드가 복잡해 지는 것을 막기 위해서 GDI+ 를 사용하였습니다.
GDI+ 사용을 하기 위한 초기화, 리소스 해제는 전체코드를 참고하시기 바랍니다.


BOOL CEasyClipperDlg::SaveImage(LPCWSTR filePath, CLSID clsid)
{
	// 이미지가 없는 경우.
	if(m_hCaptureBitmap == NULL) { return FALSE; }

	// Image 저장
	HPALETTE hpal; //팔레트 설정
	hpal = (HPALETTE)GetStockObject(DEFAULT_PALETTE);
	Gdiplus::Bitmap image(m_hCaptureBitmap, hpal);
	image.Save(filePath, &clsid, NULL);

	return TRUE;
}





4. Clipboard 에 캡처한 이미지 저장

캡처한 이미지를 Clipboard 에 등록하는 코드입니다.


BOOL CEasyClipperDlg::SaveToClipboard(void)
{
	if (OpenClipboard()) {
		EmptyClipboard();
		CBitmap * bit = CBitmap::FromHandle(m_hCaptureBitmap);
		SetClipboardData(CF_BITMAP, bit->m_hObject);
		CloseClipboard();
		return TRUE;
	}
	return FALSE;
}




5. 보안프로그램 우회 
 

 5.1 Code Injection 의 경우(윈도우 버젼에 따라 binary 코드는 다를 수 있음)


void CEasyClipperDlg::RestoreBitBlt(void)
{
	FARPROC org = GetProcAddress(GetModuleHandle(L"GDI32.dll"), "BitBlt");
	if(org != NULL) {
		DWORD dwOldProcted;
		if(VirtualProtect((LPVOID)org, 120, PAGE_EXECUTE_READWRITE, &dwOldProcted) == TRUE) {
			
			WriteProcessMemory(GetCurrentProcess(), org, "\x8B\xFF\x55\x8B\xEC\x51\x8B\x45\x28\x8B\xC8\xC1\xE1\x02\x33\xC8\xF7\xC1\x00\x00\xCC\x00\x0F\x84\x4A\x1E\x01\x00\x53\x8B\x5D\x08\x56\x33\xF6\x57\xBF\x00\x00\x01\x00\x39\x35\xC4\x9C\xE6\x75\x0F\x85\x09\x03\x02\x00\x8B\xC3\x25\x00\x00\x7F\x00\x3B\xC7\x0F\x85\xEC\x03\x02\x00\x56\x6A\xFF\xFF\x75\x28\x64\xA1\x18\x00\x00\x00\xFF\x75\x24\x89\xB0\xD0\x06\x00\x00\xFF\x75\x20\xFF\x75\x1C\xFF\x75\x18\xFF\x75\x14\xFF\x75\x10\xFF\x75\x0C\x53\xE8\x0C\x00\x00\x00\x5F\x5E\x5B\xC9\xC2\x24\x00", 120, NULL);
			VirtualProtect((LPVOID)org, 120, dwOldProcted, &dwOldProcted);
		}
	}
}




전체 프로젝트 코드는 첨부파일을 참고하시기 바랍니다. :)
EasyClipper_src.zip




'개발 프로그램 > EasyClipper' 카테고리의 다른 글

Easy Clipper ver 2.0  (3) 2015.05.27
EasyClipper - 화면 캡처프로그램  (1) 2011.12.15
EasyClipper 개발 - Canvas  (0) 2011.12.14
EasyClipper 개발 - Hooking  (0) 2011.12.13
EasyClipper 기획  (0) 2011.12.13