파이썬 윈도우 메시지 펌프 처리 예제

2018. 12. 8. 10:19개발/파이썬

파이썬을 이용해서 윈도우 메시지펌프를 하는 간단한 예제 코드 입니다 

우선 전체 코드를 먼저 본 다음 일부 중요 함수들 설명 이어가겠습니다.


메시지 펌프 코드는 아래 사이트를 참고 했습니다.

Processing Windows Messages Using MsgWaitForMultipleObjects


import sys
 
import pythoncom
from PyQt5.QtWidgets import *
import win32event
 
 
StopEvent = win32event.CreateEvent(None, 0, 0, None)
################################################
# 테스트를 위한 메인 화면
class MyWindow(QMainWindow):
    def __init__(self):
        super().__init__()
 
 
        self.setWindowTitle("Test")
        self.setGeometry(300, 300, 300, 180)
 
        nH = 20
 
        btnStart = QPushButton('strat Pump', self)
        btnStart.move(20, nH)
        btnStart.clicked.connect(self.btnStartPump)
        nH += 50
 
        btnStop = QPushButton('stop Pump', self)
        btnStop.move(20, nH)
        btnStop.clicked.connect(self.btnStopPump)
        nH += 50
 
        btnExit = QPushButton('종료', self)
        btnExit.move(20, nH)
        btnExit.clicked.connect(self.btnExit_clicked)
        nH += 50
 
    def MessagePump(sef, timeout):
        waitables = [StopEvent]
        while 1:
            rc = win32event.MsgWaitForMultipleObjects(
                waitables,
                0,  # Wait for all = false, so it waits for anyone
                timeout, #(or win32event.INFINITE)
                win32event.QS_ALLEVENTS)  # Accepts all input
 
            if rc == win32event.WAIT_OBJECT_0:
                # Our first event listed, the StopEvent, was triggered, so we must exit
                print('stop event')
                break
 
            elif rc == win32event.WAIT_OBJECT_0 + len(waitables):
                # A windows message is waiting - take care of it. (Don't ask me
                # why a WAIT_OBJECT_MSG isn't defined < WAIT_OBJECT_0...!).
                # This message-serving MUST be done for COM, DDE, and other
                # Windowsy things to work properly!
                print('pump')
                if pythoncom.PumpWaitingMessages():
                    break  # we received a wm_quit message
            elif rc == win32event.WAIT_TIMEOUT:
                print('timeout')
                return
                pass
            else:
                print('exception')
                raise RuntimeError("unexpected win32wait return value")
 
    def btnStartPump(self):
        self.MessagePump(5000)
        return
 
    def btnStopPump(self):
        win32event.SetEvent(StopEvent)
        return
 
 
    def btnExit_clicked(self):
        exit()
        return
 
 
if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWindow = MyWindow()
    myWindow.show()
    app.exec_()


메시지 펌프를 하는 실제 코드는 아래 함수 입니다.  

  • MsgWaitForMultipleObjects 라는 윈도우 API 를 이용해서 윈도우 메시지 큐에 메시지가 올 때까지 기다립니다. 파라미터로 이벤트 리스트를 보내게 되는데 윈도우 메시지 말고도 지정한 이벤트 리스트 중 하나라도 시그널이 발생하면 대기를 멈추고 리턴합니다. 
  • if rc == win32event.WAIT_OBJECT_0:  같은 코드들은 이벤트 리스트 배열에 들어 있는 항목들 만큼 반복해서 어느 이벤트에서 시그널이 발생했는지 확인하는 용도 입니다.  이번 예제에서는 이벤트(StopEvent) 를 하나만 등록했고 그 이벤트가 발생(Signal) 할 경우 처리를 하면 됩니다. 
  • rc == win32event.WAIT_OBJECT_0 + len(waitables):  실제 윈도우 메시지가 들어온 경우 처리 해야 할 로직으로 여기서는 pythoncom.PumpWaitingMessages() 함수를 호출해서 윈도우 메시지를 가져와 실행하게 됩니다.
  • rc == win32event.WAIT_TIMEOUT : 무한대로 기다릴 수도 있지만 특정 시간이 경과 하면 MsgWaitForMultipleObjects  호출이 리턴되도록 할 수 있습니다

def MessagePump(sef, timeout):
    waitables = [StopEvent]
    while 1:
        rc = win32event.MsgWaitForMultipleObjects(
            waitables,
            0,  # Wait for all = false, so it waits for anyone
            timeout, #(or win32event.INFINITE)
            win32event.QS_ALLEVENTS)  # Accepts all input
 
        if rc == win32event.WAIT_OBJECT_0:
            # Our first event listed, the StopEvent, was triggered, so we must exit
            print('stop event')
            break
 
        elif rc == win32event.WAIT_OBJECT_0 + len(waitables):
            # A windows message is waiting - take care of it. (Don't ask me
            # why a WAIT_OBJECT_MSG isn't defined < WAIT_OBJECT_0...!).
            # This message-serving MUST be done for COM, DDE, and other
            # Windowsy things to work properly!
            print('pump')
            if pythoncom.PumpWaitingMessages():
                break  # we received a wm_quit message
        elif rc == win32event.WAIT_TIMEOUT:
            print('timeout')
            return
            pass
        else:
            print('exception')
            raise RuntimeError("unexpected win32wait return value")




메시지 펌프를 시작 시키고 이벤트를 발생 시켜 중단하는 코드는 아래에 있습니다.

  • win32event.CreateEvent 를 통해 새로운 이벤트를 생성합니다.
  • win32event.SetEvent(StopEvent)로 Signal 을 발생시킵니다.


# 이벤트 생성
StopEvent = win32event.CreateEvent(None, 0, 0, None)
 
# 중략...
    # 메시지 펌프 시작
def btnStartPump(self):
    self.MessagePump(5000)
    return
 
def btnStopPump(self):
    win32event.SetEvent(StopEvent)
    return
 




위 예제 코드를 실행 시켜 보면 start pump 버튼이 눌려진 상태에서 메시지펌핑이 시작 되고, 아직 함수를 빠져나오지 않았는데도 메시지 펌핑에 의해 버튼이 계속 눌려지는 것을 확인할 수 있습니다 

PYQT 가 이미 내부적으로 윈도우 메시지 펌핑을 하고 있기 때문에 이렇게 이중으로 메시지 펌핑을 하는 경우 약간의 문제들이 생길 수 있어 조심스럽게 코딩해야 합니다.