c# WndProc으로 윈도우즈 메시지 제어하기
WndProc 메소드
WNDPROC은 win32 api에 들어 있는 콜백 함수이며 .네트의 Control.WndProc 메써드로 wrap되어 있다. 다른 환경에서 이용되는 메써드 등을 자신의 환경에서 이용할 수 있게 가공하는 걸 wrap한다고 한다. ‘포장한다’는 뜻이다. 콜백 함수란 이벤트 실행 함수를 말한다. callback은 ‘답신’을 뜻한다. 내가 아무개에게 전화를 했는데 그가 받으면 용건을 말하고 끊으면 된다. 그러나 전화를 했는데 그가 없고 다른 사람이 받으면 그 사람에게 아무개로 하여금 들어오면 전화 좀 하게 해 달라고 부탁할 수 있다. 내가 한 전화를 끊고 시간이 흐른 뒤 아무개가 내게 전화를 하면 이게 call back이고 그 전화 통화가 callback이다. 내가 a라는 동작을 실행하려고 a 메써드를 실행하면 이건 일반적인 메써드이고 어떤 일이 발생한 때 b 메써드를 실행하게끔 하면 이건 콜백 메써드다. 가만 있을 땐 실행되지 않고 있다가 윈도우즈가 메시지를 보낸 때 작동하는 메써드가 WndProc이다. windows process를 줄인 이름이지만 윈도우즈 프로세스와 직접적인 관계는 없다. 이용 방법은 간단하지만 먼저 오버라이드에 대해서 아는 게 좋다.
WndProc 오버라이드하기
어떠한 객체를 상속하면 부모 객체의 메써드를 그대로 이용할 수 있지만 경우에 따라서는 그러지 않아야 할 때도 있다. 부모 객체에 있는 것과는 달리 그것에 우선해서 작동하게 해야 할 때 override라는 모디파이어를 붙인다. ‘뭉갠다’는 뜻이다. 윈도우즈가 보내는 모든 메시지들은 이 WndProc을 거치는데 WndProc이 구현된 코드를 보면 수많은 메시지들을 각각 어떻게 처리해야 하는지에 대한 긴 내용이 들어 있다. 우리가 해야 할 건 이게 아니라 원하는 거만 몇 개 골라서 처리하는 거다. 상속은 하되 다르게 이용해야 한다.
override wndproc이라고만 치고 탭 키를 누르면 기본적인 코드가 자동으로 만들어진다. WndProc을 오버라이드한 때에는 반드시 base.WndProc을 해 줘야 하는데 우리가 원하는 메시지가 아닌 메시지는 자신의 역할을 마저 끝내야 하기 때문이다.
int MessageID;
protected override void WndProc(ref Message m)
{
if (m.Msg == 12345) // 원하는 메시지 id
{
MessageID = m.Msg;
}
else
{
base.WndProc(ref m);
}
}
private void label1_Click(object sender, EventArgs e)
{
label1.Text = MessageID.ToString();
}
멀티뜨레드 이용시 주의할 점
메시지를 아규먼트로 뜨레드에 실어서 보낼 때에는 주의해야 한다. Message는 스트럭트 즉 밸류 타입이라서 원칙적으로는 문제될 게 없지만 WndProc에서는 ref 키워드가 붙어서 레퍼런스 타입으로 처리되는 데에 유의한다. 예를 들어 증권회사 api로 많은 주식들의 데이터를 수신하다 보면 1초에 수백 개의 패킷이 들어올 수도 있다. 이것들을 메인뜨레드에서 전부 처리면 병목 현상이 생기므로 수신하는 즉시 새로운 뜨레드에서 처리하게 보내야 한다. 이때 메시지 자체를 보내면 안 된다. 메시지는 WndProc이 끝나면 생명을 다하는데 늘 그런 건 아니지만 레퍼런스 타입으로 처리되면서 그 다음 메시지가 그 자리를 덮어쓰게 될 수도 있다. 이러면 새로 만들어진 뜨레드에서 메시지를 처리하는 동안 그 내용이 바뀌어 버리게 된다. 이런 문제를 피하려면 메시지 자체를 아규먼트로 쓰면 안 되고 인스턴스를 만들어서 고유하게 한 다음에 이거를 아규먼트로 써야 한다.
MessageBox 이용시 주의할 점
WndProc은 끊임없이 작동하는데 그 안에서 MessageBox를 실행하면 이후의 작업들이 멈춘다. MessageBox를 클릭하여 없애면 기다리던 작업들이 실행이 되기는 하는데 오작동한다. 이게 버그인지 이렇게 쓰면 원래 안 되는 건지는 레퍼런스에서 관련 내용을 찾을 수 없어서 모르겠다.
wpf의 경우
WndProc은 System.Windows.Forms에 있는 거라 wpf로는 이용할 수 없다. wpf에서 윈도우즈 메시지를 제어하려면 조금 더 복잡해진다.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource;
hwndSource.AddHook(WindowsMessageHook);
}
private IntPtr WindowsMessageHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
TextBlock1.Text = msg.ToString();
return IntPtr.Zero;
}