รู้จักกับ Preprocessor Directive

Preprocess Directive หรือเรียกสั้นๆ ว่า Directive หมายถึงข้อความกำกับใน Source Code ของเรา ซึ่งจะถูกตีความหมายก่อนการ Compile Source Code เสียอีก เนื้อความในบรรทัดต่างๆ ของ Source Code จะมีความหมายหรือไม่อย่างไร ขึ้นอยู่กับการกำหนดของ Directive
Directive ที่ภาษา VB หรือ C# มีมาให้นั้นมีอยู่หลายตัว แต่ที่ได้ใช้โดยทั่วไปแล้ว (สำหรับผม) เห็นจะมีอยู่แค่

VB C#
#Region, #End Region
#If, #Else, #ElseIf, #End If
#Const
#region, #endregion
#if, #else, #elif, #endif
#define

ในที่นี้ผมขอกล่าวถึงเฉพาะภาษา VB ส่วนของ C# นั้น โดยหลักการแล้วก็เทียบเคียงกันได้ เพียงแค่เปลี่ยน syntax เท่านั้น

#Region
Directive #Region จะต้องตามด้วยคำบรรยายของ region ครอบเอาไว้ใน double quote
Region เป็น directive ที่จะว่าไปแล้วไม่มีผลกับ compiler เลย แต่มีไว้ให้คน (programmer) ดูมากกว่า การใส่ directive region จะทำให้ source code ภายในบริเวณ region นั้น ถูก collapse หรือยุบให้มองเห็นได้สั้นๆ (ภายใน Visual Studio) ตัวอย่างดังรูป ถ้าเรามี code ที่ครอบด้วย region เอาไว้แบบนี้

เราสามารถกดปุ่ม collapse ที่ต้นบรรทัด เพื่อยุบให้ source code มองเห็นเป็นแบบนี้

จะมีประโยชน์มากในกรณีที่ code บริเวณนั้นยาวมากๆ ช่วยให้การไล่ code ง่ายขึ้นเยอะ เพราะเราสามารถข้ามไปเป็น section ๆ ได้เลย

#If, Then, Else
Directive #If เอาไว้ทำการตรวจสอบเงื่อนไขบางอย่างเพื่อที่จะบอกว่าจะ “เอา” หรือ “ไม่เอา” บริเวณของ source code ต่อไปนี้
ตัวอย่างเช่น สมมติ code ของเรา ถ้าเขียนเพื่อ run ในการ test อาจต้องการให้มีการพิมพ์ข้อความเอาไว้ที่ console แต่ถ้า run ใน production อาจไม่ต้องการให้มีการพิมพ์ ซึ่งปกติแล้วเวลาที่เราจะ build โปรแกรมนั้น Visual Studio อนุญาตให้เรากำหนดได้ว่าจะ build เพื่อการ “Debug” หรือ “Release” (คลิกขวาที่ Solution แล้วเลือก Configuration Manager ดู) เราสามารถใช้ directive #If ตรวจสอบดูได้ว่าการ build ครั้งนี้เป็นการ build ในแบบ Debug หรือไม่ดังนี้

#If DEBUG Then
        Console.WriteLine("You have reach this line")
#End If

คำว่า DEBUG ในบรรทัดที่ 1 เป็นค่าคงที่ ที่จะเกิดขึ้นมาหาก Visual Studio กำลัง build ด้วย Debug Configuration ดังนั้นจาก code ข้างต้น หากเราสั่ง VS เอาไว้ว่าเราจะ build แบบ Debug แล้ว code ในบรรทัดที่ 2 จะ “ถูกนำไป compile” ด้วย แต่หากเรากำหนดเอาไว้ว่าจะ build แบบ Release แล้ว code ในบรรทัดที่ 2 จะ “ไม่ถูกนำไป compile”
สังเกตนะครับ ว่า directvie #If นั้น ไม่เหมือนกับคำสั่ง If ตามปกติในการเขียนโปรแกรม เพราะ If ในโปรแกรมนั้นถือว่าเป็นส่วนหนึ่งของ source code ซึ่งถูก compile เสมอ แต่ directive #If สามารถทำให้บริเวณใดบริเวณหนึ่งใน source code นั้น ถูกมองข้ามไป (จากการ compile) ราวกับว่าไม่มี code บรรทัดนั้นอยู่เลยก็ได้

ในทำนองเดียวกันหากเราเขียน code แบบนี้

#If DEBUG Then
        Console.WriteLine("You have reach this line by Debug build")
#Else
        Console.WriteLine("You have reach this line by Release build")
#End If

หมายความว่าหากเรา build ด้วย Release แล้ว พฤติกรรมของโปรแกรมที่เราเขียนก็จะเปลี่ยนไปเป็นอีกแบบหนึ่งได้ โดยที่เราไม่ต้องมานั่งแก้ code ซ้ำซาก เราสามารถเขียน directive กำหนดเอาไว้ทีเดียวได้เลย แล้วตอน build มันเป็นแบบไหน ก็ให้มันเลือกเอา

สถานการณ์ในการใช้ directive #If ในทางปฏิบัติยังมีอีกมาก ลองดูตัวอย่างต่อไป เป็นตัวอย่างการเช็ค platform ของ code ที่จะเอาไป run
สมมติเรามีฟังค์ชั่นง่ายๆ อยู่ตัวหนึ่งดังนี้

    Public Function ParseInt(ByVal s As String) As Integer
        Dim v As Integer
        Integer.TryParse(s, v)
        Return v
    End Function

เป็นฟังค์ชั่นเอาไว้เป็น string เป็นตัวเลข (ดูไม่ค่อยมีประโยชน์, ก็แค่ยกตัวอย่างนะครับอย่าคิดมาก)
เราก็ใช้งานมันได้ตามปกติ แต่แล้ววันหนึ่ง เราต้องมาเขียนโปรแกรมบน Pocket PC ซึ่งต้องสร้าง project ประเภท Smart Device ใน Visual Studio เราก็เลยนำ source code เดียวกันนี้มาใช้ใน Smart Device Project ด้วย แต่ผลลัพธ์ก็คือ จะเกิด Error ขึ้น ไม่สามารถ compile ได้ นั่นก็เพราะว่า .NET Framework Compact Edition ที่ไว้ใช้บน Pocket PC นั้น ไม่มีเมธอดชื่อ Integer.TryParse ครับ ทางแก้ก็คือเราต้องเลี่ยงไปเขียนวิธีอื่นเท่าที่ Compact Framework จะสามารถทำได้ โดยอาจเขียน code ลักษณะนี้

    Public Function ParseInt(ByVal s As String) As Integer
#If PocketPC Then
        Try
            Return Integer.Parse(s)
        Catch
        End Try
#Else
        Dim v As Integer
        Integer.TryParse(s, v)
        Return v
#End If
    End Function

ค่าคงที่ชื่อ PocketPC มันจะโผล่ขึ้นมาเองครับ หากใน Project ของเรานั้นเป็นประเภท Smart Device Project
ด้วยวิธีนี้จึงจะสามารถทำให้ source code เดียวกันนี้ สามารถใช้งานได้ทั้งบน .NET Framework ใหญ่ และ .NET Framework Compact Edition

อีกซักตัวอย่างหนึ่ง คราวนี้มาดู Silverlight Project
สมมติว่าโจทย์ของเราคือ ต้องการเช็คดูว่า ใน List of String ต่อไปนี้ มี item ที่ความยาวเกิน 5 ตัวอักษรอยู่บ้างหรือไม่ ปกติเราสามารถเขียน code ได้สั้นๆ แบบนี้

        Dim anyLonger As Boolean = False
        Dim list As New List(Of String)
        list.AddRange(New String() {"Apple", "Papaya", "Banana", "Orange"})
        anyLonger = list.Exists(Function(s) s.Length > 5)

ปรากฏว่า code เดียวกันนี้ ไม่สามารถนำไป run บน Silverlight ได้ เพราะบน Framework ของ Silverlight ไม่มีเมธอด List.Exists ทำให้เราต้องเลี่ยงไปเขียนด้วยวิธีอื่น ซึ่งเราสามารถเอา directive #If มาช่วยได้ดังนี้

        Dim anyLonger As Boolean = False
        Dim list As New List(Of String)
        list.AddRange(New String() {"Apple", "Papaya", "Banana", "Orange"})
#If SILVERLIGHT Then
        For Each s In list
            If s.Length > 5 Then
                anyLonger = True
                Exit For
            End If
        Next
#Else
        anyLonger = list.Exists(Function(s) s.Length > 5)
#End If

นี่ก็เป็นตัวอย่างสมมตินะครับ ในความเป็นจริงมีวิธีแก้โจทย์นี้อีกตั้งมากมาย ประเด็นในที่นี้ต้องการแสดงให้เห็นวิธีใช้ directive #If ตรวจสอบ platform เท่านั้น

Compile Options
ถึงตรงนี้หลายคนอาจจะเริ่มสงสัยแล้วว่า ค่าคงที่อย่าง DEBUG, PocketPC, SILVERLIGHT มาจากไหน เป็นสิ่งที่ถูกบังคับมาจาก Visual Studio เลยหรือไม่ว่า Project ประเภทนี้ จะมีค่าคงที่แบบนี้ คำตอบก็คือ ความจริงแล้วมันถูกตั้งมาจาก Compile Options
ลองมาดูใน Visual Studio สมมติเรามี Smart Device Project อยู่ตัวหนึ่ง ให้คลิกขวาที่ Project แล้วเลือก Property จากนั้นไปที่แท็บ Compile แล้วให้ scroll ไปบริเวณด้านล่างๆ เลยจะเห็นปุ่ม “Advanced Compile Options” ดังรูป (ตัวอย่างเป็น Visual Studio 2008)

ให้กดปุ่มเข้าไปก็จะมี dialog ดังรูป

สังเกตในส่วนของ Compilation Constants ตรงนี้เอง คือจุดที่ระบุว่าจะมีค่าคงที่อะไรปรากฏให้ directive ใช้ตรวจสอบได้บ้าง
อย่างที่เห็นก็คือนอกจากมีค่าคงที่ชื่อ DEBUG หรือ TRACE แล้ว ยังมี Custom constant ชื่อ PocketPC อยู่ด้วย ซึ่งค่าตรงนี้มันถูก set ขึ้นมาเองจาก Template ของ Smart Device Project จึงเป็นสาเหตุที่ทำให้เรามองเห็นค่าคงที่ชื่อ PocketPC เวลาที่เราอยู่ใน Smart Device Project ทุกครั้ง
เห็นอย่างนี้แล้วก็เป็นที่แน่นอนว่า เราเองก็สามารถที่จะใส่ค่าคงที่อะไรเข้าไปเองด้วยก็ได้ อย่างเช่นอาจจะใส่เข้าไปว่า TrialVersion=True เพื่อทำให้โปรแกรมของเรามีการเช็ควันหมดอายุถ้าเป็น TrialVersion compile แบบนี้เป็นต้น

#Const
ดังที่ได้เห็นแล้วว่าเราสามารถกำหนดค่าคงที่สำหรับ Compile Option ได้เอง ซึ่งค่าคงที่ดังกล่าวนั้นก็จะสามารถมองเห็นได้ทุกจุด อย่างไรก็ตามยังมี directive อีกแบบหนึ่ง ที่เอาไว้ใช้กำหนดค่าคงที่เพิ่มเติมเองได้ แต่ค่าคงที่นั้น จะมองเห็นแค่ภายใน source code ไฟล์นั้นไฟล์เดียว, Directive ที่ว่าก็คือ #Const ตัวอย่างเช่น

#Const ALGORITHM="QuickSort"

อันนี้ก็เป็นการกำหนดค่าคงที่ขึ้นมาเพื่อใช้เองภายในไฟล์นี้ไฟล์เดียวเท่านั้น ตอนล่างๆ ของ code ก็สามารถนำค่าคงที่นี้ไปใช้ได้ อย่างเช่นในที่นี้อาจทำให้โปรแกรมเลือกใช้ sort algorithm ที่แตกต่างกันไป ตามแต่เราจะกำหนดเอาไว้ที่ Const นี้

วิธีการใช้ code เดียวกันหลาย platform
เนื้อหาส่วนใหญ่ข้างต้นที่พูดถึงว่า เราสามารถใช้ source code เดียวกันสำหรับหลาย platform โดยใช้ directive ได้นั้น บางทีคุณทั้งหลายก็อาจจะยังสงสัยอยู่ว่าจะต้องใช้ directive ไปทำไม เพราะถึงยังไงเราก็ต้องเขียน code ใหม่ สำหรับ project แต่ละตัวอยู่แล้ว เราก็เขียน code แบบ PC บน Project ธรรมดา และเขียน code แบบ Pocket สำหรับ Smart Device Project ไปเลยไม่ได้หรือ
มันก็ใช่ครับ แต่ความจริงเรามีทางที่จะ maintain code ให้อยู่ในไฟล์เพียงไฟล์เดียวเลยก็ได้ วิธีการเป็นแบบนี้ครับ
สมมติเรามี Project ธรรมดาอยู่หนึ่งตัว มีไฟล์ชื่อ Utility.vb ดังรูป

แล้วเราก็มี Smart Device Project เพิ่มมาอีกหนึ่งตัว และต้องการจะใช้ code ที่เราเคยเขียนไว้แล้วใน Utility.vb ให้ทำดังนี้
คลิกขวาที่ Smart Device Project แล้วสั่ง Add –> Existing Item จนขึ้น dialog สำหรับเลือกไฟล์ ก็ให้ browse ไปยังไฟล์ Utility.vb
จุดสำคัญอยู่ตรงที่ว่าตอนจะกดปุ่ม Add ครับ ให้เลือกเป็นการ Add แบบ “Add as Link” ดังรูป

ซึ่งการ Add แบบนี้ จะไม่ได้เป็นการ copy ไฟล์ที่เลือกมาใส่ใน Project แต่จะเป็นการดึงเอาไฟล์นั้นมาร่วม compile ใน project เลย สังเกตดูว่ามีสัญลักษณ์ short cut อยู่ที่ Utility.vb ซึ่งแสดงให้เห็นว่าเป็นการ link ไปยังไฟล์จากที่อื่น

ตรงนี้เองคือสิ่งที่ทำให้ไฟล์ไฟล์เดียว สามารถถูกใช้งานได้จากหลาย platform เป็นที่มาของการนำ directive มาใช้ประโยชน์

บทส่งท้าย
ขึ้นต้นมาเป็นเรื่อง Directive แต่เนื้อหาจะออกหนักไปทาง Compile Options ซะมากกว่า ก็ขอลงท้ายเกี่ยวกับ Compile Options นิดหนึ่งครับ เราต้องเข้าใจว่า อย่างไรก็ตามค่าคงที่เหล่านี้ ไม่ใช่ Setting ของโปรแกรม อย่าเอามาปนกัน เพราะฉะนั้นการที่เราจะกำหนดค่าคงที่อะไรขึ้นมาสักอย่างต้องดูให้แน่ใจว่ามันไม่ได้เป็นเพียงแค่ Setting ของโปรแกรม แต่เป็น “Compile Option” จริงๆ ไม่เช่นนั้นก็จะผิดวัตถุประสงค์ และทำให้โปรแกรมไม่ยืดหยุ่นเท่าที่ควร

One Response to “รู้จักกับ Preprocessor Directive”

  1. Photkeerati Says:

    ขอบคุณสำหรับบทความดีๆครับ เขียนเข้าใจง่ายดี ผมกำลังงงๆ กับเรื่อง Directive อยู่ นึกไม่ออกมาว่ามันมีประโยชน์ยังไง ใช้ยังไง พอมาอ่านบทความที่คุณเขียนเข้าใจในทันที


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: