2020/03/12

C# Win32 API 이용해서 GUI 핸들 확인하기


키움증권 API 이용해서 직접 개발한 닷넷 기반 주식 자동 매매 GUI 프로그램의, 동작 모니터링과 자동 제어가 필요해서 위해서 참조했던 코드.

실행된 GUI 프로그램에서,  특정 핸들 개체(예를 들면 버튼)가 존재하는지 확인 후, 해당 개체에 바인딩된 Property 값을 가져오거나 클릭/글자입력 같은 Action 을 주기 위해서 활용.

Process _trader = Process.GetProcessesByName("Trader").Single();
IntPtr _handle = _trader.MainWindowHandle;
IntPtr _apiConnet1 = Win32WindowApi.FindWindowEx(_handle, IntPtr.Zero, "WindowsForms10.STATIC.app.0.34f5582_r32_ad1", "OpenAPI");

GUI 컨트롤이 계층 구조인 경우 단계적으로 찾아 가면서 접근하거나, 해당 프로세스 핸들에 속한 모든 하위 핸들을 리스트로 뽑거나 검색하여 해당 컨트롤에 대해서 제어

foreach (IntPtr _child in Win32WindowApi.GetChildWindows(pHandle))
{
    HandleRef _ref = new HandleRef(this, _child);
    int capacity = Win32WindowApi.GetWindowTextLength(_ref) * 2;

    if (capacity > 0)
    {
        StringBuilder stringBuilder = new StringBuilder(capacity);
        Win32WindowApi.GetWindowText(_ref, stringBuilder, capacity);
        Console.WriteLine(">" + stringBuilder);
    }
}

특정 컨트롤(핸들)를 찾기 위해서는, MS Spy++ 같은 유틸을 이용해서 핸들 검색시 고유한 값으로 사용할만한 부분을 미리 찾아 놓으면 됨



    class Win32WindowApi
    {
        public const int WM_COMMAND = 0x0111;
        public const int WM_LBUTTONDOWN = 0x0201;
        public const int WM_LBUTTONUP = 0x0202;
        public const int WM_LBUTTONDBLCLK = 0x0203;
        public const int WM_RBUTTONDBLCLK = 0x0206;
        public const uint WM_SETTEXT = 0x000C;  // 글자 설정
        public const int WM_CHAR = 0x0102;    // 타이핑
        public const int WM_SETFOCUS = 0x0007;
        public const int BM_CLICK = 0x00F5;
        public const int BN_CLICKED = 0;
        public const int MK_LBUTTON = 1;
 
        // Public Const WS_MINIMIZE = &H20000000
        // Public Const WS_MAXIMIZE = &H1000000
 
        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
        }
 
        public delegate bool Win32Callback(IntPtr hwnd, IntPtr lParam);
 
        [DllImport("User32.dll")]
        public static extern Int32 FindWindow(String lpClassName, String lpWindowName);
 
        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, string windowTitle);
 
        [DllImport("user32.Dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool EnumChildWindows(IntPtr parentHandle, Win32Callback callback, IntPtr lParam);
 
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr GetClassName(IntPtr hWnd, System.Text.StringBuilder lpClassName, int nMaxCount);
 
        [DllImport("user32.dll")]
        public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);
 
        [DllImport("user32.dll")]
        public static extern bool PostMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);
 
        [DllImport("user32.dll")]
        public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
 
        [DllImport("user32.dll")]
        public static extern bool SetForegroundWindow(IntPtr hWnd);
 
        [DllImport("user32.dll")]
        public static extern IntPtr GetWindowDC(IntPtr hWnd);
 
        [DllImport("user32.dll")]
        public static extern IntPtr GetWindowRect(IntPtr hWnd, ref RECT rect);
 
        private static bool EnumWindow(IntPtr handle, IntPtr pointer)
        {
            GCHandle gch = GCHandle.FromIntPtr(pointer);
            List<IntPtr> list = gch.Target as List<IntPtr>;
            if (list == null)
                throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
            list.Add(handle);
            return true;
        }
 
        public static List<IntPtr> GetChildWindows(IntPtr parent)
        {
            List<IntPtr> result = new List<IntPtr>();
            GCHandle listHandle = GCHandle.Alloc(result);
            try
            {
                Win32Callback childProc = new Win32Callback(EnumWindow);
                EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
            }
            finally
            {
                if (listHandle.IsAllocated)
                    listHandle.Free();
            }
            return result;
        }
 
        public static string GetWinClass(IntPtr hwnd)
        {
            if (hwnd == IntPtr.Zero)
                return null;
            StringBuilder classname = new StringBuilder(100);
            IntPtr result = GetClassName(hwnd, classname, classname.Capacity);
            if (result != IntPtr.Zero)
                return classname.ToString();
            return null;
        }
 
        public static IEnumerable<IntPtr> EnumAllWindows(IntPtr hwnd, string childClassName)
        {
            List<IntPtr> children = GetChildWindows(hwnd);
            if (children == null)
            {
                yield break;
            }
 
            foreach (IntPtr child in children)
            {
                string _clsName = GetWinClass(child);
                //Console.WriteLine("find. {0:X}", _clsName);
 
                if (_clsName == childClassName)
                {
                    yield return child;
                }
                foreach (var childchild in EnumAllWindows(child, childClassName))
                {
                    //yield return childchild;
                }
            }
        }
    }


댓글 3개:

Unknown :

여기저기 알아보다가 여기까지 왔씁니다^^
다른프로그램에서 키움의 0600 차트에 주식종목코드를 자동으로 입력하고 싶은데 도저히 안되네요.
키입력이 왜 안될까요.
StringBuilder title = new StringBuilder(6+1);
SendMessage(edit, WM_GETTEXT, new IntPtr(6+1), title); 는 잘 되고
SendMessage(edit, WM_SETTEXT, IntPtr.Zero, new StringBuilder("04")); 는 안되네요.
원인이라도 좀 알려주세요 ㅠㅠ

Unknown :

참고로 똑같이 메모장에서 하면 잘됩니다.

Mango :

PostMessage / WM_CHAR 로 텍스트 타이핑 후 엔터키를 보내는걸로 한번 해보세요.
프로그램 설정에 따라 텍스트박스 컨트롤에 SETTEXT 했을때 표시가 안되는걸수도 있습니다.
저는 로그인컨트롤 + API + 윈폼으로만 해본거고, 영웅문 자체를 그렇게 해보지 않았습니다.

댓글 쓰기

가장 많이 본 글