โครงสร้างสถาปัตยกรรมการทำงานของวินโดวส์โปรแกรม
ก่อนอื่นต้องขออภัยไม่ได้อัปบทความแอสแซมบลีมาซะนาน เนื่องจากภาระกิจหลัก (ทำงานเลี้ยงชีพ)กำลังยุ่งเลย และตัวผมเองก็มีโปรเจ็คต่างๆ เต็มหัวไปหมด...
บทความต่อไปนี่เป็นส่วนที่จำเป็นอย่างยิ่งสำหรับการเขียนโปรแกรมบนระบบปฏิบัติการวินโดว์ ก่อนที่เราจะก้าวไปสู่ส่วนอื่นๆ ต่อไป เราควรมาคำความเข้าใจหลักการต่าง ๆ เกี่ยวกับหน้าต่างโปรแกรม(Window) และ ไดอะล็อกบ็อก(Dialogbox)กันก่อน ในบทความนี้อาจจะใช้คำภาษาอังกฤษค่อยข้างเยอะ และคงไม่ขอแปลเป็นไทยจะดีกว่าเพราะจะทำให้เกิดความเข้าใจที่ผิดพลาดได้ แต่อันไหนพอจะแปลได้ก็จะแปลแล้วกัน หากมีส่วนไหนอ่านแล้วไม่เข้าใจก็ comment ไว้ได้ครับ จะเป็นพระคุณอย่างยิ่ง
ชนิดขอหน้าต่าง (Types of Windows)
การเขียนโปรแกรมบนวินโดว์เป็นการเขียนเชิงวัตถุ (object-oriented) object (อ่านว่า ออปเจ็ค ต่อไปขอเขียนเป็นภาษาอังกฤษนะสั้นดี) หลักที่สำคัญคือตัวหน้าต่างโปรแกรม (window) นั่นเอง (window ไม่มี “s” ต่อท้ายจะหมายถึงหน้าต่างของโปรแกรมนะครับ อย่าสับสนกับ Windows ล่ะ) ซึ่งก็มีด้วยกันอยู่หลายประเภท เช่น Main Window ซึ่งประกอบด้วย Title bar, Menu, Toolbar และ Scrollbar และ ประเภทอื่นๆ ได้แก่ ไดอะล็อกบ็อก(DialogBox) ซึ่งอาจมีหรือไม่มี toolbar และ scrollbar และหน้าต่างโปรแกรม (window) อีกประเภทหนึ่งคือ MessageBox , Push buttons, Radio buttons, Check boxes, List boxes, Scroll bars, Progress bars และ Text-entry fields ซึ่งก็เป็นส่วนหนึ่งของ Main windows และเป็น windows object ประเภทหนึ่งด้วยเหมือนกัน เราจะเรียนวัตถุ หรือ Object พวกนี้ว่า “Child Window Controls” เราจะมาพูดถึงรายละเอียดลึกๆ ของ Dialog boxes และ Child window Controls ต่อไป
หน้าต่างโปรแกรม (Window) มาตรฐานจะมีพื้นที่ที่สำคัญอยู่ 2 พื้นที่ด้วยกันได้แก่ Client Area ซึ่งเป็นส่วนพื้นที่หลักของ Window ที่ใช้สำหรับแสดงผลลัพธ์ (output)ต่างๆ ตัวอย่างเช่น ข้อความ หรือรูปภาพกราฟิก พื้นที่อีกส่วนหนึ่งคือ Non-Client area ได้แก่ Title bar, Menu bar, Window menu, Minimize และ Maximize buttons, Sizing border, และ Scroll bars -- System manages ส่วนใหญ่เป็นการจัดการกับ Non-client area และ Application manages เป็นส่วนที่จัดการเกี่ยวกับการแสดงผลและพฤติกรรมของ Client area:

Window Messages
หน้าต่างโปรแกรม(Window) ได้รับการป้อนข้อมูลจากผู้ใช้ เช่น การใช้งาน Keyboard หรือ Mouse ในรูปแบบของ “window messages” จากระบบปฏิบัติการ (OS) นอกจากนั้นหน้าต่างโปรแกรม(window) ยังใช้ message เพื่อติดต่อสื่อสารกับ หน้าต่างโปรแกรม(window) อื่นๆ อีกด้วย ยกตัวอย่างเช่น เมื่อผู้ใช้ แดรกเม้าส์ลากขอบหน้าต่างของโปรแกรม เพื่อปรับขนาดของหน้าต่าง ระบบปฏิบัติการ (OS) จะจัดการกับ code ต่าง ๆ สำหรับปรับเปลี่ยนขนาดของหน้าต่าง และโปรแกรมจะตอบสนองโดยการปรับเปลี่ยนการแสดงผลของหน้าต่างใหม่ โปรแกรมจะรู้ได้อย่างไรว่าเมื่อไหร่ที่หน้าต่างโปรแกรม(window) มีการปรับเปลี่ยนขนาน? คำตอบก็คือ ระบบปฏิบัติการจะส่ง message คำสั่งการเปลี่ยนแปลงดังกล่าวไปยังโปรแกรมนั่นเอง อีกตัวอย่างหนึ่งคือ Push-button window จะรู้ได้อย่างไรว่ามันได้ถูก “click” แล้ว? คำตอบก็จะเป็นลักษณะเกี่ยวกันนั่นเอง ในโปรแกรมบนวินโดว์ส่วนใหญ่ โค้ดโปรแกรมส่วนใหญ่ถูกเตรียมไว้เพื่อใช้ในการจัดการกับ message ซึ่ง Message จะประกอบไปด้วย MessageID โดยจะขึ้นต้นด้วยตัวอักษรที่แสดงประเภทของ message (ตัวอย่างเช่น WM_) และ Parameters 2 ตัว (lParam และ wParam) ซึ่งใช้เก็บข้อมูลเพิ่มเติม
The Window Procedure
ระบบปฏิบัติการ(OS) ส่ง message ต่างๆ ไปยังโปรแกรมโดยเรียกใช้งานฟังก์ชั่นพิเศษ (“Window procedure” หรือ WndProc) ภายในตัวโปรแกรม และ ส่งผ่าน message ในรูปแบบของ parameter ฟังก์ชั่นถูกเขียนขึ้นโดยโปรแกรมเมอร์ ซึ่งเรียกใช้งานโดยระบบปฏิบัติการ วิธีนี้เราเรียกว่า “callback procedures” ทุกๆหน้าต่างที่โปรแกรมสร้างขึ้นมาต้องมีส่วนประกอบของ window procedure ซึ่งใช้ในการประมวลผล message จากระบบปฏิบัติการ และหน้าต่างหรือwindow อื่นๆ การดำเนินการ ทั้งหมดเกิดขึ้นใน window procedure งานเกือบจะทุกอย่างที่โปรแกรมบนวินโดว์ทำคือการตอบสนองต่อ message ของ window procedure --window procedure เป็นตัวกำหนดว่าจะแสดงผลอย่างไรใน Client area ของมัน และจะตอบสนองอย่างไรกับ input ที่มาจากผู้ใช้งาน เมื่อ windows procedure ได้รับ message ที่มันไม่ต้องการในการประมวลผลในตัวมัน กรณีนี้ เราต้องส่งผ่าน message ไปบนตัวจัดการd efault message “DefWindowProc” เพื่อประมวลผลต่อไป
The Window Class
หน้าต่างของโปรแกรม (window) จะถูกสร้างขึ้นโดยอาศัย “window class” เสมอ -- window class จะเป็นตัวกำหนด window procedure ที่เป็นตัวประมวลผล message ของตัวหน้าต่างโปรแกรม (window) การใช้ window class จะยอมให้มีการสร้างหลายๆ หน้าต่าง (window) ภายใต้ window class และ window procedure เดียวกัน ตัวอย่างเช่น Button window ทุกตัว ซึ่งประกอบด้วย Push buttons, Check boxes, และ Radio buttons ถูกสร้างขึ้นโดยใช้ window class เดียวกัน window class นี้เกี่ยวพันกับ window procedure ที่อยู่ใน System DLL ที่ใช้ประมวลผล message สำหรับ Button window ทุกตัว ก่อนที่คุณจะสร้างแอพพลิเคชั่นบนวินโดวส์ (application window) คุณต้องทำการ register window class ก่อน โดยการเรียกใช้ฟังก์ชั่น RegisterClass window class ถูกใช้กำหนดคุณสมบัติทั่วไปของ windows ด้วย ซึ่งประกอบด้วย icon และ สีของพื้นหลัง เป็นต้น
เมื่อคุณสร้างหน้าต่างโปรแกรม (window) โดยการเรียกใช้ฟังก์ชั่น CreateWindow คุณสามารถระบุรายละเอียดต่างๆ เพิ่มเติมเกี่ยวกับการแสดงผลของหน้าต่าง (window) ยกตัวอย่างเช่น เรารู้ว่า Push buttons ทุกตัวทำงานเหมือนกันเนื่องจากทุกตัวใช้ WndProc ตัวเดียวกัน แต่ Push button ทุกตัวหน้าตาไม่เหมือนกัน ซึ่งอาจจะมีขนาดที่แตกต่างกัน ตำแหน่งบนหน้าจอต่างกัน และมีข้อความที่แสดงต่างกัน เป็นต้น คุณสมบัติต่าง ๆ เป็นส่วนหนึ่งของ window definition มากกว่าที่จะเป็น window class
Child window controls ที่กล่าวมาด้านบน มี 6 class ที่กำหนดไว้ล่วงหน้าได้แก่ BUTTON, COMBOBOX, EDIT, LISTBOX, SCROLLBAR, และ STATIC. คอนโทรลที่ใช้งานรวมกัน (common controls) เหล่านี้ถูก register โดยการเรียกใช้ฟังก์ชั่น InitCommonControls
The Message Queue and Message Loop
ขณะที่โปรแกรมบนวินโดวส์ทำงาน ระบบปฏิบัติการ (OS) จะสร้าง “message queue” สำหรับโปรแกรมนั้น เจ้า message queue นี้จะเก็บ messages สำหรับทุกๆ หน้าต่างที่โปรแกรมสร้างมา แอพพลิเคชั่นบนวินโดวส์จะประกอบด้วยโค้ดที่เรียกว่า “message loop” ซึ่งจะรับ message จากคิว โดยการเรียกใช้ฟังก์ชั่น GetMessage และ ส่งต่อมันไปยัง อีก window Procedure ที่เหมาะสม โดยเรียกใช้ฟังก์ชั่น DispatchMessage Messages อื่นๆ ส่งตรงไปยัง window procedure โดยที่ไม่ใส่ลงใน queue
เราจะเรียก messages ที่อยู่ในคิวนั้นว่า “posted” ซึ่งเป็นการส่ง message ไปยัง message queue และ เรียก messages ที่ไม่ได้ส่งไปยังคิว นั่นว่า “Sent” ซึ่งเป็นการส่ง message ไปยัง window procedure -- message ที่ถูกเก็บอยู่ในคิว (queue) โดยทั่วไปจะเป็นผลลัพธ์ที่มาจากการป้อนมาจากผู้ใช้งาน ในรูปแบบคีย์บอร์ด ตัวอักษรที่ถูกพิมพ์ การเลือน mouse การ คลิก mouse ตลอดจนการ repaint message เพื่อ refresh หน้าจอแสดงผล (WM_PAINT) และ quit message (WM_QUIT) messages ที่ไม่อยู่ใน queue คืออื่นๆ ที่เหลือทุกอย่าง และบ่อยครั้งที่มาจากผลของการเรียกใช้ windows functions สำหรับตัวอย่าง คือ เมื่อ WinMain เรียกใช้ฟังก์ชั่น CreateWindow Windows สร้างหน้าต่างโปรแกรม(window) และใน process จะส่ง WM_CREATE message ไปยัง windows procedure เป็นต้น
Dialog boxes and Child Window Controls
Dialog Boxes ไม่มีอะไรแตกต่างไปจาก หน้าต่างโปรแกรม(window)ธรรมดาทั่วไป ซึ่งถูกออกแบบมาเพื่อให้ทำงานกับ child windows controls มันสามารถใช้งานเป็นตัวป้อนข้อมูลให้กับหน้าต่างหลัก(main window) หรือทำตัวเองเป็นหน้าต่างหลักก็ได้ ขนาดหรือเลย์เอ้าท์ของ dialog box และ controls ต่างๆ ของมันจะถูกกำหนดไว้ใน “dialog box template” resource script ซึ่งสามารถเขียนขึ้นเองได้ แต่จะดีกว่าและสะดวกกว่าถ้าเราใช้ “Visual Resource Editor” ใน WinAsm
เมื่อเรียกใช้งานเท็มเพลตที่เป็น Dialog box วินโดวส์จะทำการสร้างหน้าต่างป๊อบอัพของ Dialog box และ child windows controls และจัดเตรียม window procedure เพื่อจัดการกับ dialog box messages ซึ่งรวมถึงการป้อนข้อมูลผ่านทางเม้าท์และคีย์บอร์ด เมื่อ child window controls ประมวลผล เม้าส์และคีย์บอร์ดเมสเสจ ของตัวมันเอง แล้วจะแจ้งไปยังหน้าต่างแม่ (parent window) เมื่อมันมีการเปลี่ยนสถานะ ซึ่งเป็นการลดภาระของโปรแกรมเมอร์ลงเป็นอย่างมาก โค้ดภายในวินโดวส์(windows)ที่ดำเนินการทั้งหมดนี้ บางครั้งจะถูกอ้างอิงเป็น “dialog box manager”
แมสเสจต่าง ๆ ที่ถูกจัดการโดย Dialog box manager จะถูกส่งผ่านไปยัง callback procedure ภายในโปรแกรม ซึ่งเราจะเรียกว่า “Dialog box procedure” หรือ “Dialog procedure” เจ้าตัว dialog procedure นี้ก็เหมือนกันกับ window procedure นั่นคือวินโดวส์จะส่งแมสเสจไปยังโพรซีเจอร์(procedure) เมื่อมันมีข้อมูลที่ต้องการจะส่งให้ หรือเมื่อต้องการข้อมูลที่จะต้องนำออกมา dialog box procedure ส่วนใหญ่จะดำเนินการกับ WM_INITDIALOG message และ WM_COMMAND message ที่ส่งโดยตัว controls แต่จะดำเนินการกับแมสเสจอื่นๆเป็นส่วนน้อย
ซึ่งไม่เหมือนกันกับ window procedure -- dialog box procedure จะไม่เรียกใช้งานฟังก์ชั่น DefWindowProc แต่มันจะคืนค่า TRUE ถ้ามันดำเนินการกับแมสเสจ หรือ FALSE ถ้ามันไม่ได้ดำเนินการ อีกในหนึ่ง ถ้า dialog procedure ไม่ได้ดำเนินการกับแมสเสจ มันจะคืนค่ามาเป็น FALSE ไปยังวินโดวส์(Windows)โดยตรงเพื่อดำเนินการกับแมสเสจภายใน ยกเว้นแต่ WM_INITDIALOG message dialog box procedure ต้องคืนค่าเป็น TRUE ไปยังวินโดวส์(windows) เพื่อดำเนินการกับแมสเสจ WM_INITDIALOG ต่อไป
เราสามารถสื่อสารกับ child windows ต่าง ๆ บน dialog box (ผ่านทาง control ID ซึ่งเป็นตัวเลขที่ไม่ซ้ำกัน) โดยใช้ฟังก์ชั่น SendDlgItemMessage ใน Win32.hlp จะมีรายการ messages ที่สามารถใช้งานได้ แต่วินโดวส์ (windows) ได้จัดเตรียม API functions ต่างๆ ที่ใช้ในการรับค่าและกำหนดค่าได้อย่างรวดเร็วไว้ให้ใช้งานแล้ว เช่น GetDlgItemText, CheckDlgButton เป็นต้น ฟังก์ชั่นต่างๆ เหล่านี้ได้ถูกจัดเตรียมเพื่ออำนวยความสะดวกสำหรับโปรแกรมเมอร์ และทำให้ซอร์สโค้ดอ่านง่ายมากขึ้น
Dialog Box หลักแบ่งออกได้เป็น 2 ประเภท (The 2 Main Types of Dialog Box)
modal - ตัวอย่างเช่น “About” dialog box จากเมนู help ผู้ใช้งานไม่สามารถสลับไปมาระหว่างไดอะล็อกบ็อก(dialog box) และหน้าต่างโปรแกรม (window) อื่นในโปรแกรมของเราได้ ผู้ใช้ต้องตอบสนองกับไดอะล็อกบ็อก (dialog box) แรกก่อน อย่างไรก็ตามผู้ใช้สามารถสลับไปยังโปรแกรมอื่นได้ ซึ่งจะถูกสร้างโดยเรียกใช้ “DialogBoxParam” ซึ่งจะคืนค่าหลังจากเลิกใช้งาน (destroyed) แล้วเท่านั้น ถ้าเราใช้รู้แบบ DS_SYSMODAL ใน dialog box template มันจะสร้าง system modal dialog ขึ้นมา ซึ่งผู้ใช้งานต้องจบการทำงานในไดอะล็อก (dialog) นี้ก่อนที่จะสามารถทำงานใดๆ ต่อได้ในวินโดวส์
modeless – ตัวอย่างเช่น ไดอะล็อกบ็อก (Dialog) “Find” ในโปรแกรม Word Processor ซึ่งยอมให้ผู้ใช้สามารถสลับไปมาระหว่างไดอะล็อกบ็อก (dialog box) และหน้าต่างโปรแกรมที่ถูกสร้างขึ้นมา เช่นเดียวกันกับโปรแกรมอื่นๆ ซึ่งจะถูกสร้างโดยการเรียกใช้งาน CreateDialogPraram โดยจะคืนค่าวินโดว์แฮนเดิล (window handle) ของไดอะล็อกบ็อก (dialog box) มาโดยทันที
ทั้งสองแบบนี้ มีส่วนแตกต่างที่สำคัญอีกหลายอย่าง ได้แก่ :
1. modeless dialog boxes โดยปกติจะประกอบไปด้วย Caption bar และ System menu เพื่อยอมให้ผู้ใช้งานเคลื่อนย้ายไดอะล็อกบ็อก(dialog box)ไปยังพื้นที่อื่นของหน้าจอแสดงผล โดยสามารถใช้ได้ทั้งเม้าท์และคีย์บอร์ด ซึ้งเราไม่ต้องการส่วนนี้ใน modal dialog box เพราะว่าผู้ใช้งาน ไม่สามารถดำเนินการต่าง ๆภายใต้หน้าต่างนี้ได้
2. ถ้าเราไม่ได้ใช้งานรูปแบบ WS_VISIBLE หรือ เรียกใช้งาน ShowWindows modeless ไดอะล็อกบ็อกจะไม่แสดงผลที่หน้าจอไม่ว่ากรณีใดๆ
3. แมสเสจที่ส่งไปยัง modal dialog boxes และ message boxes ไม่เหมือนกัน แมสเสจที่ส่งไปยัง modeless dialog boxes ผ่านมาจาก message queue ของโปรแกรมของเรา ซึ่งจำเป็นต้องแก้ไขเพื่อส่งผ่านแมสเสจไปยัง dialog box procedure
Common Dialog Boxes
วินโดวส์(Windows)ได้จัดเตรียมไดอะล็อกบ็อกที่ใช่ร่วมกันสำหรับใช้ในโปรแกรมของเรา นั่นก็คือ ไดอะล็อกบ็อก File, Print, Colour, Font, และ Search ซึ่งเป็นรูปแบบของส่วนติดต่อกับผู้ใช้ (user interface) ที่เป็นมาตรฐาน และจัดเก็บอยู่ใน comdlg32.dll ในการใช้งานเราต้องทำการลิ้งค์ไปยัง comdlg32.lib เราสร้างไดอะล็อกบ็อกเหล่านี้โดยการเรียกใช้งานฟังก์ชั่นที่เหมาะสมใน common dialog library สำหรับไดอะล็อก “Open file” เราเรียกใช้ฟังก์ชั่น GetOpenFileName, สำหรับไดอะล็อก “Save as” เราจะเรียกใช้ GetSaveFileName เป็นต้น ฟังก์ชั่นต่างๆ เหล่านี้จะส่งผ่านค่าพารามิเตอร์โดยใช้พ้อยเตอร์(pointer)ชี้ไปยังตัวแปรโครงสร้าง เราสามารถดูได้จากรายการที่อยู่ใน Win32.hlp
WinMain, Handles and Instances
จุดเริ่มต้นเพื่อเข้าสู่วินโดว์โปรแกรม นั่นก็คือ ฟังก์ชั่น WinMain ซึ่งถูกเรียกใช้งานโดยระบบปฏิบัติการ โดยจะเริ่มเอ็กซ์คิวหลังจากโปรแกรมได้โหลดจากดิสเข้าสู่หน่วยความจำ(memory) โดยวินโดวส์โหลดเดอร์ (windows loader) ในโปรแกรมที่เขียนด้วยภาษา C/C++ จะประกาศใช้เลเบล “WinMain” เพื่อใช้งาน แต่ในภาษาแอสแซมบลีสามารถใช้งานเลเบลอะไรก็ได้ เมื่อโปรแกรมถูกแมปเข้าไปในหน่วยความจำโดยวินโดวส์โหลดเดอร์ (windows loader) เวอร์ชั่นที่อยู่ในหน่วยความจำเรารู้จักกันในชื่อ โมดูล (module) ในวินโดวส์ (windows) 32 bit ตำแหน่งเริ่มต้นของหน่วยความจำ ที่จุดเริ่มต้นของไฟล์แมปปิ้งจะเท่ากันโมดูลแฮนเดิล (module handle)
แฮนเดิล(handle)ก็คือตัวเลข 32 bit ที่ระบบปฏิบัติการใช้ในการอ้างอิงถึงทุกๆ ออปเจ็คที่ไม่ซ้ำกันที่ถูกสร้างขึ้น เช่น หน้าต่างโปรแกรม (window) หรือ ปุมกด (Push Button) เราสามารถอ่านค่าแฮนเดิล(handle)จากวินโดวส์ และจากนั้นก็ใช้ค่าแฮนเดิล (handle) ในฟังก์ชั่นอื่นๆ ได้ ซึ่งโมดูลแฮนเดิล(module handle) ใช้ในการระบุถึงโปรแกรม ซึ่งเราจะรู้จักกันในชื่อว่า “อินสแตนซ์ แฮนเดิล” (instance handle) ซึ่งเป็นค่าพารามิเตอร์แรกที่ถูกใช้ในฟังก์ชั่น WinMain และเป็นค่าอากิวเม้นที่ต้องการสำหรับวินโดว์ฟังก์ชั่นอื่นๆ
ในวินโดว์เวอร์ชั่นแรกๆ เมื่อเรารันหลายๆ อินสแตนซ์ (instance) ที่เป็นโปรแกรมเดียวกัน ทุกอินสแตนซ์ของโปรแกรมใช้โค้ดร่วมกันและเป็นหน่วยความจำที่อ่านได้อย่างเดียว (read-only memory) โปรแกรมจะมีการตรวจสอบการทำงาน เกี่ยวกับการกระโดดไปทำงานในตำแหน่งของอินสแตนซ์อื่นและเคลื่อนย้ายข้อมูลจากอินสแตนซ์ที่ผ่านมาไปยังพื้นที่ขอตัวเอง ใน windows เวอร์ชั่นที่เป็น 32bit หลักการนี้ได้ถูกยกเลิกแล้ว แต่ “Previous instance handle” ซึ่งเป็นพารามิเตอร์ที่สองของ WinMain ยังคงมีอยู่ แต่มันต้องถูกกำหนดให้เป็น NULL หรือ 0 เสมอ
Easy GUI Apps Using Dialog Boxes
ไดอะล็อกบ็อก(Dialog boxes) อาจถูกใช้เป็นส่วนในการป้อนข้อมูลสำหรับหน้าต่างหลักของโปรแกรมของเรา หรือ เป็นหน้าต่างหลักเอง ซึ่งทำได้โดยเรียกใช้ DialogBoxParam เพื่อสร้าง Modal dialog box โดยไม่ต้องอาศัย Parent window การทำแบบนี้เป็นการออกแบบโปรแกรมซึ่งพิจารณาโดยการใช้ Dialog procedure แทน Window procedure และไม่จำเป็นต้องสร้าง message loop แมสเสจ(message)จะถูกส่งไปยัง Dialog procedure โดยตรง ไดอะล็อกบ็อก(dialog box)สามารถสร้างได้จาก resource script ที่สร้างใน Visual editor และใช้ window controls ซึ่งดำเนินการกับแมสเสจ( message)ของตัวเองเป็นส่วนใหญ่ เราไม่ต้องแม้กระทั้งลงทะเบียน (register) window class เราจะใช้รูปแบบนี้สำหรับตัวอย่างในบทความต่อไปที่จะนำเสนอ
เรียบเรียงจาก: Win32 Assembler Coding โดย Goppit