안녕하세요? 맨날맑음 입니다.

윈도우에서 [Ctrl + Alt + Del]키를 누르면 아래와 같이 Windows 작업관리자를 볼 수 있습니다. 응용 프로그램 탭을 보면 현재 실행 중인 프로그램의 목록을 확인 할 수 있는데요. 이와같이 프로그램을 만들다 보면 특정한 프로그램을 선택하는 창을 제공해야 할 때가 있습니다.
이런 기능을 만들기 위해서는 Win32 API의 도움을 받아야합니다.(.net class library에서 지원해 주는지는 잘 모르겠습니다. 아시는분은 꼭 알려주시길..) 잡설이지만 .NET의 Windows Form 개발시 자꾸만 Win32 API를 사용하게 되는군요'' 분명 그에 매칭하는 .NET Class가 있을꺼라는 생각이 드는데요(일부 크리티컬한 기능 제외);

Win32 API의 EnumWindows 함수는 현재 실행중인 프로세서들의 목록을 얻어올 수 있는 기능을 제공합니다.
http://msdn.microsoft.com/en-us/library/ms633497(VS.85).aspx 이 링크에서 보다 자세한 MSDN 도움말을 확인 할 수있습니다.

C#에서 사용시에는 다음과 같이 선언 해 주어야 하는데, 한가지 고민되는 사항은 첫번째 인자가 callback 함수의 '함수포인터'를 넣어야 되는 것입니다. 두번째 인자의 의미는 사용자 임의의 데이터를 넘길 때 사용합니다.
[DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern bool EnumWindows(EnumWindowsCallback callback, int extraData);

그런데 C#에는 함수포인터가 없기 때문에 이를 대체하기 위한 수단으로 'Delegate'를 사용해야 합니다.

그래서 아래과 같이 EnumWindows의 Callback 함수와 형식이 일치하는 Delegate를 선언해 줍니다.
public delegate bool EnumWindowCallback(int hwnd, int lParam);
배웠으니 시험도 해볼겸 간단한 예제를 만들어 보겠습니다. Windows Form 프로젝트를 생성합니다.
Form위에 프로그램의 목록과 아이콘을 띄워줄 ListView를 가져다 놓습니다.

각자 입맛에 맛게 속성을 조정해 줍니다. 저는 Form크기에 맞게 딱 붙도록 Dock속성을 Fill로 변경해 보았습니다.
코드(form.cs)로 가서 아래의 코드를 추가합니다.
using System;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace Sample
{
    public partial class Form1 : Form
    {
        public delegate bool EnumWindowCallback(int hwnd, int lParam);
        
        [DllImport("user32.dll")]
        public static extern int EnumWindows(EnumWindowCallback callback, int y);

        [DllImport("user32.dll")]
        public static extern int GetParent(int hWnd);

        [DllImport("user32.dll")]
        public static extern int GetWindowText(int hWnd, StringBuilder text, int count);

        [DllImport("user32.dll")]
        public static extern long GetWindowLong(int hWnd, int nIndex);

        [DllImport("user32.dll")]
        public static extern IntPtr GetClassLong(IntPtr hwnd, int nIndex);

        const int GCL_HICON = -14; //GetWindowLong을 호출할 때 쓸 인자
        const int GCL_HMODULE = -16;
        ImageList imgList;//ListView의 Image로 쓸 리스트
               

        public Form1()
        {
            InitializeComponent();
            imgList = new ImageList();
            imgList.ImageSize = new Size(16, 16);
            listView1.SmallImageList = imgList;
            listView1.View = View.List;
            //CallBack 델리게이트 생성
            EnumWindowCallback callback = new EnumWindowCallback(EnumWindowsProc);
            EnumWindows(callback, 0);

        }
       
        public bool EnumWindowsProc(int hWnd, int lParam)
        {            
            //윈도우 핸들로 그 윈도우의 스타일을 얻어옴
            UInt32 style = (UInt32)GetWindowLong(hWnd, GCL_HMODULE);
            //해당 윈도우의 캡션이 존재하는지 확인
            if ((style & 0x10000000L) == 0x10000000L && (style & 0x00C00000L) == 0x00C00000L)
            {
                //부모가 바탕화면인지 확인
                if (GetParent(hWnd) == 0)
                {
                    StringBuilder Buf = new StringBuilder(256);
                    //응용프로그램의 이름을 얻어온다
                    if (GetWindowText(hWnd, Buf, 256) > 0)
                    {
                        try
                        {
                            //HICON 아이콘 핸들을 얻어온다
                            IntPtr hIcon = GetClassLong((IntPtr)hWnd, GCL_HICON);
                            //아이콘 핸들로 Icon 객체를 만든다
                            Icon icon = Icon.FromHandle(hIcon);
                            imgList.Images.Add(icon);                            
                        }
                        catch (Exception)
                        {
                            //예외의 경우는 자기 자신의 윈도우인 경우이다.
                            imgList.Images.Add(this.Icon);
                        }
                        listView1.Items.Add(new ListViewItem(Buf.ToString(), listView1.Items.Count));
                    }
                }
            }
            return true;
        }    
    }
}

소스가 좀 많아 보이긴 하는데, 복잡하진 않습니다. Win32API들을 사용하기 위해 EnumWindows, GetParent, GetWindowText, GetWindowLong, GetClassLong을 선언해 줍니다.
ListView의 Image를 넣기 위한 ImageList도 추가합니다.

생성자에서는 ImageListListView의 초기화를 하고, Delegete를 생성하여, EnumWindows를 호출해 줍니다.
그럼 OS는 현재 실행중인 프로세서를 EnumWindowsProc으로 전달해 주게 됩니다. 단 EnumWindowsProc으로 전달되는 것은 프로세스 목록(전체)이 아니라 한번에 하나씩 들어오게 됩니다.

EnumWindowsProc에서는 첫번째 매개변수로 들어온 윈도우 핸들을 이용하여 GetWindowLong을 호출해 윈도우의 스타일을 얻어오고, 해당윈도우의 캡션이 존재하는지 확인합니다.

그 다음으로 부모가 바탕화면인지 확인하여, 이것이 최상위의 윈도우인지 확인하게 됩니다. GetWindowText함수를 이용하여, 윈도우(응용프로그램)의 이름을 얻어온후, GetClassLong을 사용하여 해당 윈도우의 HICON 아이콘핸들을 얻어오게 됩니다(.net에서 사용하기 위해 IntPtr 사용), 이렇게 얻어온 아이콘핸들을 이용하여 .net의 Icon 클래스의 static메소드인 FromHandle을 이용하여 Icon 객체를 생성합니다.

예외처리 구문은 자기 자신의 윈도우일 경우 Icon을 못얻어오는 경우가 생겨서, try를 사용하였습니다.
EnumWindowsProc의 return은 true로 해주셔야, 계속하여 호출하게 됩니다. 상황에따라 원하는 윈도우를 찾은다음에 false를 return하면 되겠습니다. 실행시켜 보니 잘되는군요!
사실, API의 EnumWindows를 사용하지 않고 .net의 Process 클래스를 사용해도 현재 사용중인 프로세스의 목록을 얻어, 바탕화면이 부모인지 확인하고 MainWindowTitle을 확인하여 이와같은 기능을 만들 수 도 있습니다.(하지만 탐색기 같은 일부창의 경우 윈도우의 이름을 얻어올 수 없는 문제가 있습니다.) 아래는 Process를 이용하는 간단한 예제입니다.
Process[] pro = Process.GetProcesses();            
for (int i = 0; i < pro.Length; i++)
{
     if (pro[i].MainWindowHandle != IntPtr.Zero)
     {
          if (pro[i].MainWindowTitle == "")
              continue;
       //To do..
     }
}
Posted by 맨날맑음
,