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

윈도우에서 [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 맨날맑음

댓글을 달아 주세요

  1. 학생1 2010.04.29 20:48  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. 좋은 자료를 올려주셔서 감사합니다.

    다름이 아니라. 제가 서버에서 클라이언트 상에 실행중인 프로그램을 감시 하기 위한 프로그램을 실습 수업 프로젝트에서 작성 하려고 합니다. 물론 일부 분이긴 하지만. 제가 하려는 프로젝트에서 중요한 부분이라서요. (조교느님들 PC에서 학생 PC를 감시할 수 있게요.) 위의 소스를 사용해 봤는데, 작업 표시줄에 있는 목록만 가져오게 되는데, 수업시간에 불필요한 네이트온같은 경우에는 트래이 아이콘으로 되어 있어서 못보던데 이 경우에 트래이 아이콘 목록을 가져오는 방법도 있나요??
    네이트온 대화창이나 쪽지는 리스트에 나오지 않아서 이를 찾기 위한 방법을 MSDN에서 찾아 보는 중이지만 아직 검색에 성공하지는 못해서 질문을 올림니다. _ 읽어 주셔서 감사합니다. (__)