반응형

앞서서는 API의 종류와 관련한 상수값을 보았습니다.


이번에는 API를 응용한 함수를 만들어보겠습니다.




[목표]

 - 후킹을 시작하고

 - 입력 키를 확인한다.

 - 조합키 입력을 확인한다.

 - 내가 원하는 키를 다른 프로그램에 보낸다.



1. 후킹 시작 / 후킹 해제 선언하기


선언한 User32.dll API 를 그대로 써도 되겠지만, 본 API를 그대로 쓰기 보단 

래핑하여 사용하는 것이 제 취향입니다.

public static void SetHook()

{

    IntPtr hInstance = LoadLibrary("User32");

    hhook = SetWindowsHookEx(WH_KEYBOARD_LL, _proc, hInstance, 0);

}


public static void UnHook()

{

    UnhookWindowsHookEx(hhook);

}


UnHook은 WPF 에서 프로그램이 종료될 때 호출하면 좋겠죠?


private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)

{

    UnHook();

}



2. 내가 후킹할 키가 맞는지 확인

public static IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam)

{

    // 후킹할 내용만 정의

    if (code <= 0 && wParam != (IntPtr) WM_KEYDOWN)

    {

// 원래 입력되어야 할 프로그램에 키를 넘긴다면 아래와 같이 보냅니다.

        return CallNextHookEx(hhook, code, (int)wParam, lParam);

    }


    int vkCode = Marshal.ReadInt32(lParam);

    VKeys curKey = (VKeys)vkCode;

/*

여기에 원하는 동작을 할 소스코드를 적으면 되겠죠?

*/ 


    // 원래 입력되어야 할 프로그램에 키를 넘기기 싫다면 (IntPtr)1; 을 보냅니다.

    return (IntPtr)1;

}



3. 조합키를 확인


GetAsyncKeyState는 같이 눌린 키가 있는지 확인하는 API입니다.

Return 값에 따라서, 누른적이 있는지, 지금도 누르고 있는지를 확인할 수 있습니다.

Return 값은 어설프게나마 Enum으로 만들어 봤습니다.


/**

 *  0x0000 이전에 누른 적이 없고 호출 시점에도 눌려있지 않은 상태

 *  0x0001 이전에 누른 적이 있고 호출 시점에는 눌려있지 않은 상태

 *  0x8000 이전에 누른 적이 없고 호출 시점에는 눌려있는 상태

 *  0x8001 이전에 누른 적이 있고 호출 시점에도 눌려있는 상태

 */

enum RETURN_GetAsyncKeyState : ushort

{

    NN = 0x0000, // 0

    YN = 0x0001, // 1

    NY = 0x8000, // 32768

    YY = 0x8001  // 32769

}


private static bool IsKeyPress(VKeys key)

{

    RETURN_GetAsyncKeyState rtnVal = (RETURN_GetAsyncKeyState)GetAsyncKeyState((int)key);


    return RETURN_GetAsyncKeyState.YN.Equals(rtnVal) || RETURN_GetAsyncKeyState.YY.Equals(rtnVal);

}





// 위에 예제에 더해서 체크해봅시다.

public static IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam)

{

    // 후킹할 내용만 정의

    if (code <= 0 && wParam != (IntPtr) WM_KEYDOWN)

    {

// 원래 입력되어야 할 프로그램에 키를 넘긴다면 아래와 같이 보냅니다.

        return CallNextHookEx(hhook, code, (int)wParam, lParam);

    }


    int vkCode = Marshal.ReadInt32(lParam);

    VKeys curKey = (VKeys)vkCode;


    // Left Ctrl + S 인지 확인해봅시다.

    if(VKeys.S.Equal(curKey) && IsKeyPress(VKeys.VK_LCONTROL)) 

    {

        // Left Ctrl + S 일 때 처리할 내용 쓰기

    }


    // 원래 입력되어야 할 프로그램에 키를 넘기기 싫다면 (IntPtr)1; 을 보냅니다.

    return (IntPtr)1;

}





3. 프로그램에 내가 원하는 값을 보내기

함수명만 봐도 이해가 될거 같아서 함수설명은 패스하겠습니다.


public static void SetFocus(IntPtr handle)

{

    SetForegroundWindow(handle);

}


public static void SendLeftButtonDown(IntPtr handle, int x, int y)

{

    PostMessage(handle, WM_LBUTTONDOWN, 0, new IntPtr(y * 0x10000 + x));

}


public static void SendLeftButtonUp(IntPtr handle, int x, int y)

{

    PostMessage(handle, WM_LBUTTONUP, 0, new IntPtr(y * 0x10000 + x));

}


public static void SendLeftButtondblclick(IntPtr handle, int x, int y)

{

    PostMessage(handle, WM_LBUTTONDBLCLK, 0, new IntPtr(y * 0x10000 + x));

}


public static void SendRightButtonDown(IntPtr handle, int x, int y)

{

    PostMessage(handle, WM_RBUTTONDOWN, 0, new IntPtr(y * 0x10000 + x));

}


public static void SendRightButtonUp(IntPtr handle, int x, int y)

{

    PostMessage(handle, WM_RBUTTONUP, 0, new IntPtr(y * 0x10000 + x));

}


public static void SendRightButtondblclick(IntPtr handle, int x, int y)

{

    PostMessage(handle, WM_RBUTTONDBLCLK, 0, new IntPtr(y * 0x10000 + x));

}


public static void SendMouseMove(IntPtr handle, int x, int y)

{

    PostMessage(handle, WM_MOUSEMOVE, 0, new IntPtr(y * 0x10000 + x));

}


public static void SendKeyDown(IntPtr handle, int key)

{

    PostMessage(handle, WM_KEYDOWN, key, IntPtr.Zero);

}


public static void SendKeyUp(IntPtr handle, int key)

{

    PostMessage(handle, WM_KEYUP, key, new IntPtr(1));

}


public static void SendChar(IntPtr handle, char c)

{

    SendMessage(handle, WM_CHAR, c, IntPtr.Zero);

}


public static void SendString(IntPtr handle, string s)

{

    foreach (char c in s) SendChar(handle, c);

}



다만, 보내는 순서는 아래와 같이 하셔야 합니다.

...

// 보낼 프로그램의 Process 를 가져옵니다.

Process sendTargetProcess = 보낼 프로그램 가져오는 로직 정의;


// 보낼 프로그램을 활성화시킵니다.

SetFocus(sendTargetProcess.MainWindowHandle); 


// 키를 보냅니다.

SendKeyDown(sendTargetProcess.MainWindowHandle, (int) VKey.PageUp); 


User32의 FindWindow를 통해 프로세스의 Handle을 찾아도 되지만, 

Process 를 통해 Handle을 얻어도 됩니다.


화면에 리스트 형태로 MainWindowTitle이 있는 프로세스를 찾아(작업줄에 표시되는 프로그램) 리스트뷰에 뿌리고, 뿌려진 아이템을 클릭한 뒤 보내면 편하겠죠?



아래는 리스트뷰를 선언하고, 리스트 뷰에 실행중인 프로세스를 아이템소스로 하여 선택을 유도하는 예제입니다.


- UI 선언

<Button Content="보내기" HorizontalAlignment="Left" Margin="263,104,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click" Height="22"/>

<ComboBox x:Name="cbxTest" HorizontalAlignment="Left" Margin="138,104,0,0" VerticalAlignment="Top" Width="120"/>

<ListView x:Name="livwPrcsList" Margin="0,131,394,53" BorderBrush="{x:Null}">

    <ListView.View>

        <GridView>

            <GridViewColumn Header="프로세스명" DisplayMemberBinding="{Binding MainWindowTitle}"/>

        </GridView>

    </ListView.View>

</ListView>

<Button x:Name="btnGetProcess" Content="가져오기" HorizontalAlignment="Left" Margin="10,104,0,0" VerticalAlignment="Top" Width="76" Click="BtnGetProcess_Click"/>



- 소스코드(cs)에서 데이터 설정과 핸들러 정의

// MainWindow 생성자를 통해 콤보박스 초기화

public MainWindow() 

{

    // TEST

    cbxTest.ItemsSource = Enum.GetValues(typeof(VKeys)).Cast<VKeys>();

}


// 선택한 프로세스에 선택한 키 보내기 버튼

private void Button_Click(object sender, RoutedEventArgs e)

{

    if (livwPrcsList.SelectedIndex < 0 || cbxTest.SelectedIndex < 0) { MessageBox.Show("없어"); return; }


    Process p = livwPrcsList.SelectedItem as Process;

    VKeys a = (VKeys) cbxTest.SelectedItem;


    HookingUtils.SetFocus(p.MainWindowHandle);

    HookingUtils.SendKeyDown(p.MainWindowHandle, (int) a);

}


// 실행중인 프로세스 에서 작업줄에 표시되는 프로그램만 가져오기

private void BtnGetProcess_Click(object sender, RoutedEventArgs e)

{

    Process[] prcs = ProcessUtils.GetProcessNames();

    List<Process> prcsList = new List<Process>();

    foreach (Process p in prcs)

    {

        if (p.MainWindowTitle.Equals(""))

        {

            continue;

        }

        prcsList.Add(p);

    }


    livwPrcsList.ItemsSource = prcsList;

}






원하시는 분이 있으면 WPF로 간단하게 만들어 올려드리겠습니다.

반응형

WRITTEN BY
데르벨준

,