본문

P/Invoke로 다이얼로그 창 제어하기 #3. DLL 만들고 외부 프로세스와 연결하기

지난글(P/Invoke로 다이얼로그 창 제어하기 #2. SetWindowsHookEx() 소개)에 이어지는 글입니다. 특히 기존에 있었던 c#코드(P/Invoke로 다이얼로그 창 제어하기 #1. 현재 프로세스 제어)를 c++로 포팅하는데 게시물의 목적이 있습니다. 막상 변환하던 도중 다이얼로그 창을 제어할 다른 우회방법을 발견하여 막상 계획했던 장기 연재 프로젝트를 여기에서 중단해야 할것 같습니다. 아무튼 개략적인 설명은 다음과 같습니다. 이 프로젝트는 다이얼로그 창을 닫는데에 목적이 있다 하겠습니다. 만약 다이얼로그 창의 내용을 불러와서 그 내용에 맞게 처리하는 로직을 생각했었더라면 코드가 조금 더 복잡해 졌을것입니다.

DLL을 호출하는 프로그램(서버)와 DLL(드라이버)로 나누어 생각해 본다면, 서버에서는 드라이버를 다른 프로세스에 부착하는 역할을 수행하고, 드라이버 측에서는 부착된 프로세스에서 발생하는 메시지를 읽어서 해당 프로세스에 확인 버튼을 눌렀다는 메시지를 전송하는 역할을 하며, 그리고 후킹을 종료할 경우에는 서버에서 드라이버를 제거하는 구조로 되어있습니다. 참고로 후자의 경우(통신기능)일 경우에는 서버와 드라이버가 분리되어 있기 떄문에 드라이버 측에서는 서버의 위치와, 그리고 서버상에 등록되어있는 콜백함수를 이어주는 포인터등을 가지고 있어야 합니다(혹은 드라이버가 수집한 메시지를 서버에게만 전달하는 방법을 사용할 수 있겠습니다)

현재 서버는 c#으로 작성되어 있으므로 다음과 같이 설정해 줍니다. 소스는 여기(SetWindowsHookEx doesn't work with thread Id)에서 가져왔습니다 LIBRARY에는 드라이버명, PROC에는 프로시저명(DialogProc)을 기입하며, threadId는 GetWindowThreadProcessId()등을 사용하면 될것입니다. 게시물에는 답변대로 안될 이유가 없어서 그냥 가져왔습니다 (지금 게시물은 구현이 안된 상태에서 작성하는 것입니다)

[DllImport( "user32.dll", SetLastError = true )]
static extern IntPtr SetWindowsHookEx ( int hookType, UIntPtr lpfn, IntPtr hMod, uint dwThreadId );

[
DllImport( "kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true )]
public static extern UIntPtr GetProcAddress ( IntPtr hModule, string procName );

[
DllImport( "kernel32", SetLastError = true, CharSet = CharSet.Unicode )]
public static extern IntPtr LoadLibrary ( string libraryName );

const int WH_CBT = 5;

void SetHook (){
    IntPtr dll = LoadLibrary( LIBRARY );
    UIntPtr proc = GetProcAddress( dll, PROC );
    uint threadId = GetAppWindowThreadId();
    IntPtr hookAddress = SetWindowsHookEx( WH_CBT , proc, dll, threadId );
}


c++로 작성된 드라이버의 메시지 처리부는 다음과 같습니다. 1번 게시물과 비교하시며 보시면 좋을 것 같습니다. 이외 DLL제작에 있어 생략된 부분(DllMain 등)이 있으나 채우는데 어려움이 없으실거라 생각됩니다. 또한 CallNextHookEx()에서의 첫번째 인자는 무시되므로 그냥 NULL으로 처리하였습니다.

list<HWND> lstChildWindows;
void listChildWindows(HWND p){
    EnumChildWindows(p, EnumChildProc, 0);
}

BOOL CALLBACK EnumChildProc( HWND hwnd, LPARAM lParam ){
    lstChildWindows.push_back(hwnd);
    return TRUE;
}

LRESULT CALLBACK DialogProc(int nCode, WPARAM wParam, LPARAM lPARAM){
    CWPRETSTRUCT * cwpretStruct = (CWPRETSTRUCT*)lPARAM;

    if(nCode>=0){
        if ( ( cwpretStruct->message == WM_INITDIALOG )){
            HWND pOkButtonHwnd=NULL;
            int iLength=GetWindowTextLength(cwpretStruct->hwnd);
            TCHAR* sb= new TCHAR[iLength];
            GetWindowText(cwpretStruct->hwnd, sb, iLength);
            listChildWindows(cwpretStruct->hwnd);

            list<HWND>::iterator i;
            for(i=lstChildWindows.begin(); i != lstChildWindows.end(); ++i) {
                TCHAR* sbProbe= new TCHAR[100];
                if(GetClassName(*i, sbProbe, 100) !=0 && _tcslen(sbProbe) >0){
                    if(_tcscmp(sbProbe, _T("Button"))==0){
                        iLength=GetWindowTextLength(*i);
                        if(iLength>0){
                            TCHAR* sbText=new TCHAR[iLength];
                            GetWindowText(*i, sbText, iLength);
                            if(_tcscmp(sbText, _T("확인"))==0) pOkButtonHwnd=*i;
                            delete sbText;
                        }
                    }
                }
                delete sbProbe;
            }

            if(pOkButtonHwnd!=NULL){
                int ctrlId=GetDlgCtrlID(pOkButtonHwnd);
                SendMessage(cwpretStruct->hwnd, WM_COMMAND, (WPARAM)ctrlId, (LPARAM)pOkButtonHwnd);             
                return 1; 

           }
       }
    }

    return CallNextHookEx(NULL, nCode, pWParam, pLParam);
}


사실 다이얼로그 창을 제어하기 위한 까닭은 개인적으로 사용할 인터넷 Automation을 툴을 만드는 중, alert창등이 뜨면 이것에 막혀서 COM객체를 사용할 시 오류가 나게 되기 때문에 이를 억제하기 위함이었으며, 물론 windows.alert={}등으로 처리할 수도 있겠지만 이것을 적용하기 힘든 구조였기 때문에 좀 더 확실한 방법을 찾고자 후킹을 사용한 다이얼로그 제어를 생각하게 된것입니다. 그러다 떠오른것이 '다른 프로젝트를 검색해보는것은 어떨까?'였으며 만족스럽진 않지만 결과를 얻어 사용중입니다. 다음에는 자동화 툴 커스터마이징에 대해 알아보겠습니다.

댓글

Holic Spirit :: Tistory Edition

design by tokiidesu. powerd by kakao.