شما اینجا هستید

خانه

Message Loop ( حلقه رویداد ) - قسمت اول

 Message Loop ( قسمت اول )

در زبان برنامه نویسی Message Loop یا Event Loop  یا حلقه رویداد یک ساختار برنامه نویسی است که توسط آن برنامه می تواند Message یا Event ( پیام یا رویداد) را دریافت و ارسال کند. در این بخش سعی دارم که درباره حلقه رویداد ها در یک برنامه ساده در ویندوز صحبت کنم.

وقتی صحبت از پیام یا رویداد می کنیم، منظور انتقال داده بین شبکه یا ... نیست بلکه انتقال پیام از سیستم عامل به یک برنامه در حال اجراست. برای مثال برنامه ای را که با آن صفحات وب را باز می کنید تصور کنید (مثلا Mozilla firefox ): به پنجره باز این برنامه به طور پیوسته پیام هایی از سیستم عامل ارسال می شود. برای مثال زمانی که شما روی پنجره این برنامه کلیک می کنید، سیستم عامل پس از دریافت رویداد کلیک و پس از تحلیل کلیک بر روی این برنامه، این رویداد را در قالب پیام به این برنامه ارسال می کند و موقعیت کلیک ماوس و نوع کلیک را به آن انتقال می دهد. در واقع هر تغییر در ورودی توسط سیستم عامل به برنامه انتقال می یابد.

به عنوان مثال دیگر زمانی که شما بر روی دکمه minimize در پنجره کلیک می کنید، سیستم عامل پیغام minimize را به برنامه ارسال می کند و در نتیجه پنجره مورد نظر می تواند خود را minimize نماید. البته توجه داشته باشید که برنامه ای که پیام را دریافت می کند می تواند پیام مورد نظر را درست انجام ندهد. مثلا زمانی که دکمه Close زده شد و پیام close به پنجره ارسال شد، پنجره خود را نبندد، در نتیجه دکمه close عملا کار نمی کند ولی پیام همواره توسط سیستم عامل به پنجره مورد نظر ارسال می شود.

یک مثال دیگر از این پیام ها که در بخش دیگری مفصل تر راجع به آن بحث خواهیم نمود، رویداد device change به معنی تغییر در سخت افزار است. برای مثال زمانی که یک flash memory به پورت USB متصل می شود، سیستم عامل پس از دریافت این رویداد می تواند این رویداد را به برنامه دلخواه ارسال نماید. فرض کنید که برنامه شما بخواهد پس از این که کاربر flash memory خود را به سیستم متصل کرد، اسکن ساده برای وجود autorun.inf را انجام دهد، در این صورت شما به راحتی می توانید با دریافت پیام device change به اتصال یک usb به دستگاه متصل شوید.

پس از این توضیحات که امیدوارم گویا بوده باشد به سراغ بحث شیرین کد نویسی می رویم. توجه داشته باشید که کد های کامل برنامه در پایین همین پست قابل دانلود است.

همه می دانند که برنامه C همواره از تابع main شروع می شود. اما برنامه هایی که به صورت window application هستند در سیستم عامل windows با تابع WinMain آغاز می شوند. این تابع بر خلاف تابع main که 2 آرگومان کنسولی دریافت می کند، 4 آرگومان دریافت می کند. فرم این تابع به این صورت است: 

int CALLBACK WinMain(
  HINSTANCE hInstance,
  HINSTANCE hPrevInstance,
  LPSTR lpCmdLine,
  int nCmdShow
); 


در تابع بالا hInstance یک handle به برنامه باز شده است.

hPrevInstance  یک هندل به برنامه قبلی است که همواره مقدار آن NULL است.

lpCmdLine آرگومانی است که برنامه ارسال شده است.
nCmdShow یک متغیر int  برای چگونگی نمایش برنامه است. حالت های مختلف آن را می توانید در لینک زیر ببینید.

لینک اطلاعات بیشتر درباره این تابع از سایت MSDN اینجا است.

توجه کنید که این آرگومان ها توسط سیستم عامل به برنامه پاس داده می شوند.

برای دوستانی که تازه با ادبیات windows آشنا می شوند باید عرض کنم که در کتابخانه ویندوز همه ی متغیر ها با نام گذاری مختص آن انجام شده است که به مرور زمان با این واژه ها آشنا می شوید. برای مثال LPSTR همان * char  است.

در header فایل های ویندوز دو نوع string داریم که کلیه توابع بر اساس این 2 نوع نوشته شده اند. رشته (string) هایی که از char ساخته می شوند و آن هایی که از WCHAR ساخته می شوند. char یک بایتی است و WCHAR دو بایتی است. در نتیجه برای ذخیره رشته هایی با حروف زبان های غیر از انگلیسی باید از این حروف استفاده نماید. مثلا زمانی که مسیر یک فایل را ذخیره می کنید. چون ممکن است فولدری با نام فارسی در مسیر باشد.

در ویندوز LPSTR  همان * char  و LPWSTR همان *   WCHAR است.

 

پس از ایجاد تابع ورودی به برنامه (user-provided entry point)، زمان آن رسیده که یک حلقه پیام (message loop) ایجاد کنیم.  واضح است که در صورتی که برنامه دارای حلقه پیام نباشد پس از انجام عملیات خود به پایان رسیده و بسته می شود. ولی حلقه پیام تا زمانی که پیام close دریافت نکرده منتظر پیام جدید خواهد بود و برنامه بسته نخواهد شد.

 

تابع دریافت پیام در windows ، تابع GetMessage است. که به این صورت در تابع winmain قرار می دهیم:

    MSG msg;
    BOOL bRet;

    // Start the message loop.

    while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
    {
        if (bRet == -1)
        {
            // handle the error and possibly exit
        }
        else
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

در کد بالا یک حلقه قرار داده شده است که که تا زمانی که GetMessage مقدار غیر صفر برگرداند ( یعنی پیام quit برای برنامه ارسال نشده باشد) در حلقه می ماند. داخل حلقه معمولا دو تابع TranslateMessage و DispatchMessage را صدا می زنند.

تابع TranslateMessage ، کلید های مجازی را در قالب کاراکتر ها دوباره به  همین thread پست می کند. تابع DispatchMessage ، پیام دریافت شده را به تابع پنجره ( که در زیر بیشتر توضیح داده خواهد شد) ارسال می نماید.

نکته ای که در بالا به آن اشاره شد این است که تابع GetMessage فقط پیام را دریافت می کند و تابع DispatchMessage آن را به تابع پنجره (Window Procedure) ارسال می کند. در واقع در ویندوز همه پیام ها برای پنجره ها ارسال می شوند. در نتیجه اگر شما بخواهید پیام های  سیستم را دریافت کنید باید حتما یک پنجره یا window ایجاد نمایید. پنجره ی ویندوزی توسط تابع CreateWindowEx قابل ایجاد است که در زیر به آن اشاره می کنم:

HWND CreateWindowEx(
  DWORD dwExStyle, 
  LPCTSTR lpClassName, 
  LPCTSTR lpWindowName, 
  DWORD dwStyle, 
  int x, 
  int y, 
  int nWidth, 
  int nHeight, 
  HWND hWndParent, 
  HMENU hMenu, 
  HINSTANCE hInstance, 
  LPVOID lpParam 
);



تابع CreateWindowEx که نوع کامل تابع CreateWindow است، یک پنجره ویندوزی می سازد و یک هندل از این پنجره را به عنوان خروجی بر می گرداند. HWND یکی دیگر از هندل های ویندوز است که باید به خاطر بسپارید. این هندل برای کار کردن با پنجره ها مورد استفاده قرار می گیرد.

آرگومان های مختلف این تابع برای کنترل پنجره مورد نظر است. جزییات آن را می توانید از لینک بالا ببینید. به طور خلاصه:

 

dwExStyle :  استایل یک پنجره را مشخص می کند. مقادیر قابل استفاده:  

 

مقدار توضیح
WS_EX_ACCEPTFILES پنجره قابلیت drag & drop را دارد.
WS_EX_CAPTIONOKBTN افزودن دکمه OK به نوار عنوان
WS_EX_CLIENTEDGE پنجره با لبه فرو رفته
WS_EX_CONTEXTMENU افزودن دکمه help در نوار عنوان
WS_EX_TOPMOST مشخص می کند که آیا پنجره باید بالای همه ی پنجره های دیگر باشد یا نه.
....  

 

DWORD در واق ع unsigned long است که در کتابخانه ویندوز بازتعریف (typedef) شده است.

 

lpClassName: یک رشته مختوم به null است که در واقع نام کلاس این پنجره می باشد. توجه کنید که نام این پنجره باید قبلا توسط تابع RegisterClass رجیستر شده ب اشد.

 

lpWindowName  : رشته مختوم به null است که نام پنجره است. این رشته در عنوان پنجره نشان داده  می شود.

 

dwStyle: نوع دیگری از استایل پنجره است که توسط جدول زیر قابل تنظیم است. توجه داشته باشید که چند استایل همزمان هم قابل تنظیم است. ( با عملگر منطقی or )

 

مقدار توضیح
WS_BORDER پنجره با لبه نازک
WS_CAPTION پنجره دارای نوار عنوان
WS_CHILD ایجاد یک زیر پنجره
WS_HSCROLL پنجره با اسکرول افقی
WS_MAXIMIZEBOX داشتن دکمه maximize
WS_MINIMIZEBOX داشتن دکمه minimize
WS_POPUP پنجره با استایل pop-up
WS_VISIBLE ایجاد پنجره ای که قابل دید است
WS_VSCROLL پنجره با اسکرول عمودی
..... .....

 

x , y :  موقعیت اولیه پنجره است که نسبت به گوشه بالا و چپ صفحه نمایش است.

nWidth و nHeight :  عرض و ارتفاع پنجره مورد نظر است. 

 

hWndParent:  اگر پنجره ای که ایجاد می شود زیر مجموعه پنجره دیگر است، هندل پنجره پدر در غیر این صورت مقدار NULL.

 

hMenu: ه ندل منوی دلخواه یا NULL. 

lpParam : اطلاعات اضافی برای ارسال به پنجره دلخواه، می تواند NULL باشد.

خروجی تابع در صورت ایجاد پنجره یک هندل به پنجره خواهد بود در غیر این صورت مقدار NULL را برمی گرداند.

جزییات بیشتر را حتما در سایت MSDN مطالعه بفرمایید.

پس از ایجاد یک پنجره می توانید آن را نشان دهید یا مخفی کنید. تابع  ShowWindow  می تواند پنجره را نشان دهد.

و آخرین تابعی که در این جا توضیح می دهم، تابع RegisterClass است:

 

ATOM WINAPI RegisterClass(
  __in  const WNDCLASS *lpWndClass
);



این تابع یک کلاس پ نجره را برای استفاده های بعدی مثل CreateWindow رجیستر می کند.
lpWndClass یک پوینتر به ساختار WNDCLASS است:

 

typedef struct tagWNDCLASS {
  UINT      style;
  WNDPROC   lpfnWndProc;
  int       cbClsExtra;
  int       cbWndExtra;
  HINSTANCE hInstance;
  HICON     hIcon;
  HCURSOR   hCursor;
  HBRUSH    hbrBackground;
  LPCTSTR   lpszMenuName;
  LPCTSTR   lpszClassName;
} WNDCLASS, *PWNDCLASS;

3 خاصیت مهم از این  ساختار که باید حتما مقدار دهی شوند :

hInstance : هندل به نمونه ای که حاوی این پنجره است.
lpszClassName : نام کلاس پنجره
lpfnWndProc : پوینتر به تابع پنجره. WNDPROC  در واقع پوینتر به تابع است با ساختار :

typedef LRESULT (CALLBACK* WNDPROC) (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

 همان طور که پیشتر بیان شد، این تابع همان تابعی است که پیام های سیستم پس از دریافت به آن منتقل می شود. در واقع پس از صدا کردن تابع DispatchMessage ، تابع WNDPROC
توسط ویندوز صدا زده می شود.

پارامتر های پاس شده به این تابع:

hwnd : هندل به پنجره پیام داده شده

uMsg : پیام ارسال شده

wParam : پارامتر اول پیام

lParam : پارامتر دوم پیام ( این پارامتر ها بسته به نوع پیام می تواند حاوی اطلاعات مختلفی باشند.)

و در این جا هم به پایان بحث می رسیم. امیدوارم توضیحات قابل درک بوده باشند. در صورت داشتن هر گونه ابهام، اشکال یا سوالی می تونید کامنت بذارید.

موفق باشید.

دانلود کدهای بالا

افزودن نظر جدید

درباره من

سلام. به وب سایت شخصی من خوش آمدید.

محمد ذات خواهی هستم متولد سال 66 اهل رشت. سال 89 از دانشگاه شریف با مدرک لیسانس مهندسی شیمی و سال 91 از دانشگاه علم و صنعت ایران با مدرک ارشد کنترل و شبیه سازی فرایند فارغ التحصیل شدم، و در حال حاضر به عنوان مهندس نرم افزار در شرکت گسترش فرایند شریف هستم. از اونجایی که از دبیرستان به الگوریتم و برنامه نویسی علاقه داشتم از همون موقع تو این حیطه کار می کنم. بیشترین توانایی من در طراحی نرم افزار های مهندسی شیمی، طراحی وب و کلا کد نویسی هست. خوش حال میشم بتونم به دیگران کمک کنم خصوصا در حوزه آموزش. سعی می کنم تو این وب سایت مطالب و تجربه هایی که فکر می کنم به درد علاقه مندان به حوزه نرم افزار و برنامه نویسی بخوره قرار بدم. امیدوارم شما هم با نظرات و انتقادات و بحث های علمی تون در ارتقای محتوی علمی این وب سایت من رو یاری کنید.

 

Mohammad Zatkhahi Image

 

.

?>