از پیغامها ( Messages ) برای برقراری ارتباط بین هر آنچه در ویندوز موجود است ، در پائین ترین سطح ممکن استفاده میشود . اگر میخواهید که یک پنجره یا یک کنترل ( که خود نوعی پنجره است ) در ویندوز ، کار خاصی انجام دهد ، باید پیغامی برای آن ارسال کنید. به همین شکل اگر پنجره دیگری بخواهد که شما ( بعنوان یک پنجره !) کاری را انجام دهید ، منظورش را بصورت یک پیغام ( Message ) برای شما میفرستد . اگر رویدادی اتفاق بیفتد (مثلا ً کاربر کلمه ای را تایپ کند ، موس حرکت کند و یا دکمه ( Button ) ای فشار داده شود ) ، سیستم برای پنجره ها پیغام را ارسال میکند . اگر شما هم یکی از آن پنجره ها باشید ، باید پیغام رسیده را هندل کنید و مطابق با آن عمل کنید .
هر پیغام در ویندوز حداکثر با دو پارامتر همراه است . wParam و lParam . در اصل wParam 16 بیتی است و lParam 32 بیتی است ولی در win32 هر دو 32 بیتی هستند . لزوما ً تمام پیغام ها از هر دوی این پارامتر ها استفاده نمی کنند . هر پیغام بصورت مجزا از این پیغام ها استفاده میکند . مثلا ً پیغام WM_CLOSE که برای بستن یک پنجره استفاده میشود ، از هیچیک از این پارامترها استفاده ای نمی کند . WM_COMMAND از هر دو استفاده میکند به این شکل :
wParam شامل دو مقدار است . کلمه باارزشتر ( HIWORD ) حاوی پیغام اخطار و کلمه کم ارزشتر ( Loword ) حاوی Id ی ( شناسه ی) کنترل یا منویی است که پیغام را ارسال کرده است .
lParam شامل HWND ( هندل پنجره ) کنترلی است که پیغام را ارسال کرده است و یا اینکه مقدار آن NULL است اگر پیغام از طرف هیچ کنترلی ارسال نشده باشد .
عبارت HIWORD و LOWORD ماکروهایی هستند که توسط خود ویندوز تعریف شده اند به این صورت که برای جدا کردن دو بایت با ارزش و دو بایت کم ارزش یک کلمه 32 بیتی استفاده میشوند .
دو بایت ( کلمه ) با ارزشتر = 0x
FFFF0000
دو بایت ( کلمه ) کم ارزشتر = 0x0000
FFFF
در win32 یک کلمه ( Word ) 16 بیتی است و DWord مقداری است 32 بیتی .
برای فرستادن یک پیغام از دو تابع PostMessage و SendMessage می توان استفاده کرد :
- PostMessage پیغام را داخل صف پیغام ها ( Message Queue ) قرار میدهد و بلافاصله برمیگردد . به این معنا که وقتی شما PostMessage را فراخوانی می کنید و این تابع اجرا می شود ، ممکن است که ارسال پیغام شما انجام شده باشد و یا هنوز در حال انجام شدن باشد.
- تفاوت SendMessage با قبلی هم در این است که که SendMessage تا وقتی که پیغام ارسال نشود برنمیگردد . در واقع این یکی پیغام را مستقیما ً به پنجره مورد نظر میفرستد .
اگر ما بخواهیم که پنجره ای را ببندیم ، از PostMessage می توان به این صورت استفاده کرد :
PostMessage(hwnd, WM_CLOSE, 0, 0);
این کد دقیقا ً مثل این عمل میکند که شما روی دکمه ضربدری که بالا سمت چپ هر پنجره قرار دارد کلیک کنید .
همانطور که قبلا ً هم گفته شد ، دو پارامتر wParam و lParam برای WM_CLOSE استفاده نمیشوند و مقدار آنها صفر است .
صف پیغامها ( Message Queue ) چیست ؟
فرض کنید که که ما مشغول پاسخگویی به پیغام WM_PAINT هستیم و در این هنگام کاربر کلمه ای را با استفاده از کیبورد تایپ میکند . به نظر شما چه اتفاقی می افتد؟
آیا باید در کار ترسیم صفحه وقفه ای وارد شود و به سراغ کیبورد برویم و یااینکه تایپ کاربر را نادیده بگیریم ؟ همانطور که ( احتمالا ً ) میدانید هر دوی اینها غلط است . راه حلی که در اینگونه موارد استفاده میشود استفاده از صف پیغام است به این شکل که وقتی پیغامی ارسال میشود ، به انتهای صف اضافه شده و به محض اینکه نوبت بررسی و اجرای آنها برسد از صف حذف میشوند . با این کار هیچ پیغامی بدون بررسی حذف نمی شود .
حلقه پیغام ( Message Loop ) چیست؟
while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
WNDPROC fWndProc = (WNDPROC)GetWindowLong(Msg.hwnd, GWL_WNDPROC);
fWndProc(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);
}
1- حلقه پیغام ، تابع GetMessages را فراخوانی میکند و به این ترتیب پیغامی از صف پیغامها خوانده میشود . اگر صف پیغام خالی باشد ، برنامه شما ( پروسس برنامه شما ) بلاک میشود .
2- وقتی رویدادی ( event ) اتفاق می افتد که باعث افزوده شدن پیغامی به صف پیغامها می- شود ( مثلا ً یک کلیک موس ) ، تابع GetMessages مقداری مثبت برمیگرداند که نشان دهنده وجود پیغامی برای بررسی است . در ادامه ساختار ( Struct ) Msg که ما به آن پاس داده ایم با مقادیر مناسب پر می شود . اگر مقدار برگردانده شده صفر باشد یعنی با پیغام WM_QUIT مواجه شده ایم و اگر مقدار منفی برگردانده شود یعنی خطایی اتفاق افتاده است .
3- پیغام دریافتی ( از طریق متغیر Msg ) را به تابع TranslateMessage پاس میدهیم تا یکسری پردازشهای اضافی روی پیغام انجام شود .
4- سپس تابع DispatchMessage را فراخوانی میکنیم . کاری که این تابع انجام میدهد به این ترتیب است : پیغام را میگیرد ، بررسی می کند که مربوط به کدام پنجره است و سپس بدنبال Window Procedure مربوط به آن پنجره می گردد . سپس آنرا فراخوانی میکند و هندل پنجره ، پیغام ، wParam و lParam را بعنوان پارامتر برای آن ارسال میکند .
5- در Window Procedure ( همانگونه که در قسمت قبلی عملا ً انجام دادید ) ، شما پیغام و پارامترهای مربوط به آن را بررسی می کنید و هرگونه که بخواهید میتوانید با آن برخورد کنید (!) اگر شما پیغامی را هندل نکنید ، تابع DefWindowProc برای آن فراخوانی می شود و عملیاتی که خود ویندوز بصورت پیش فرض در نظر گرفته است ، برای آن انجام میگیرد ( اغلب هم هیچ کاری انجام نمی شود !)
6- هنگامی که کار شما و Window Procedure و DispatchMessage تمام شد ، به ابتدای حلقه برمیگردیم .
درک این مطلب خیلی مهمه که Window Procedure خود به خود و بدون دلیل فراخوانی نمیشود بلکه توسط خود شما و بصورت غیر مستقیم توسط DispatchMessage فراخوانی میشود .
همانطور که میبینید برنامه شما اکثر زمان خود را صرف اجرای حلقه پیغام میکند ، جایی که شما پیغامهای خود را میفرستید و اجرا هم می شوند .
برای خروج از برنامه هم کافیست که تابع GetMessage مقدار False ( یا صفر ) برگرداند . در اینصورت از حلقه خارج شده و به تابع WinMain باز می گردیم . این دقیقا ً کاری است که PostMessage انجام می دهد ، یعنی با قرار دادن مقدار WM_QUIT در صف پیغامها باعث میشود که تابع GetMessage مقدار صفر برگرداند .
اصلی ترین هدف این قسمت ایجاد درک بهتر و کاملتر از پیغامها و نحوه کار آنها در ویندوز بوده است . امیدوارم که به این هدف رسیده باشه (!) .. البته در آینده باز هم خواهم نوشت ( انشاءالله )
پ.ن:فایل ضمیمه ی این بخش فقط شامل یک Pdf است . همین ..