2020년 3월 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;
                }
            }
        }
    }


댓글 없음:

댓글 쓰기

HttpClient 에서 비동기 호출시 System.Threading.Tasks.Task 오류

client.PostAsync().Result; 하나 이상의 오류가 발생했습니다., 위치: System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification) 이 요청을...