protect

ถ้าเอาบทความไปเผยแพร่แล้วไม่ให้เครดิต ดำเนินคดีด้วย พรบ. คอมพิวเตอร์ฉบับใหม่ ขั้นสูงสุดและไม่ยอมความครับ

วันพฤหัสบดีที่ 15 ตุลาคม พ.ศ. 2558

Assembly Module : ตอนที่ 4 table ต่างๆ ใน Metadata


execute file ชนิด Managed module จะมี header file ที่มีส่วนสำคัญอยู่ 4 ส่วนดังที่ผมได้เคยอธิบายไปแล้ว หนึ่งใน 4 ส่วนของ header file คือ metadata

Metadata เป็นพื้นที่เล็กๆ ไว้เก็บข้อมูลของ module โดยทำการจัดเก็บในรูปแบบของตาราง (table) โดยสามารถแบ่งออกตารางได้เป็น 3 กลุ่มใหญ่ๆ คือ

Definition tables
table ชนิดนี้จะเก็บข้อมูลของ module นั้นๆ เช่น version, MVID และ Type ชนิดต่างๆ ที่ถูกสร้างขึ้นใน module.

Reference tables
table ชนิดนี้จะเก็บรายละเอียดของ library ที่ module มีการอ้างถึง เช่นรายละเอียดของ System.Console จะถูกเก็บไว้ใน table ส่วนนี้

Manifest tables
table ชนิดนี้แหละครับ เป็นตัวบอกว่า execute file เป็น Assembly Module หรือ Managed module

ในบทความหน้าผมจะอธิบายรายละเอียด tables ที่อยู่ในกลุ่มของ Definition tables ทั้งหมดพร้อมทั้งโชว์ให้ดูว่าเราจะดูข้อมูล tables ของส่วนนี้ได้ที่ไหน

พบกันใหม่บทความหน้าครับ
TuChay

วันพุธที่ 14 ตุลาคม พ.ศ. 2558

Assembly Module : ตอนที่ 3 รู้จักกับ Response File


ก่อนที่จะขึ้นรายละเอียดของ Metadata ผมขออธิบายอีกนิดเกี่ยวกับการ save พารามิเตอร์ในการ compile code ลงใน file

โดยปกติตอนที่เรา compile code ด้วยคำสั่ง csc เราอาจจะต้อง reference หลายๆ library ซึ่งจะต้องใส่ /r กับ library ทุกตัว ดังนั้นคำสั่ง csc จะยาวมาก csc หรือ C# Compiler ได้อนุญาติให้เอาพารามิเตอร์ใส่ไว้ลงใน file โดยเรียก file ชนิดนี้ว่า response file ที่มีนามสกุล .rsp เมื่อ คำสั่ง csc compile code และต้องการใช้พารามิเตอร์ใน response file ก็จะใช้เครื่องหมาย @ นำหน้า response file นั้น

ตัวอย่างของ response file




 เมื่อ csc ทำการ conpile code ก็สามารถทำได้ด้วยคำสั่ง

               csc @MyResponse.rsp program.cs

ที่นี้เราจะลดข้อผิดพลาดในการใส่ พารามิเตอร์ของ csc และเรายังใช้พารามิเตอร์ชุดนี้กับ project อื่นๆ ได้อีกด้วย

C# compiler ยังอนุญาติให้มี response มากว่า 1 file ลองดูตัวอย่างครับ

     csc @MyResponse.rsp @AnotherResponse.rsp program.cs

ในบทความที่แล้วผมได้ยกตัวอย่างการ compile code โดยไม่ใส่พารามิเตอร์อะไรเลยเป็นเพราะว่า C# compiler มีการเรียกใช้ response file ที่เป็น global ชื่อ response file ที่เป็น global นี้จะถูกเก็บไว้ในชื่อ csc.rsp โดยถูกเก็บไว้ที่ %SystemRoot%\Microsoft.NET\Framework\vX.X.X

%SystemRoot% คือ directory ที่เก็บ System ของ Windows โดยส่วนใหญ่คือ c:\Windows
vX.X.X คือ version ของ csc โดยปกติจะเป็น version เดียวกับ .Net Framework เราสามารถเช็ค version ของ csc ได้โดยพิมพ์คำสั่ง csc และ enter ดังรูปตัวอย่างข้างล่างครับ



ในรูปข้างล่างเป็นตัวอย่างเนื้อหาของ file csc.rsp


%SystemRoot%\Microsoft.NET\Framework\vX.X.X นอกจากจะเก็บ global response file แล้วยังเก็บ standard library ที่ชื่อ MSCorLib.dll ด้วย

ถ้าเราไม่ต้องการใช้ response file ตัวที่เป็น global เราก็สามารถใส่พารามิเตอร์ /noconfig นอกจากนั้นเรายังใส่พารามิเตอร์ /looger ให้กับ csc เพื่อที่จะ log ข้อมูลของการ compile code ได้ด้วย แต่พารมิเตอร์ /logger ใช้ได้กับ .Net Framework version 4.5 เป็นต้นไป

Response file ที่ผมอธิบายมาทั้งหมดนี้จะใช้ได้เฉพาะ csc compiler เท่านั้นน่ะครับจะไม่สามารถใช้ได้กับ Visual Studio สำหรับ Visual Studio ผมจะอธิบายอย่างละเอียดในบทความถัดๆ ไปครับ

บทความหน้าผมจะเริ่มอธิบายรายละเอียดของ metadata ที่ทำให้ managed module กลายมาเป็น Assembly module น่ะครับ

พบกันใหม่บทความหน้าครับ
TuChay







วันจันทร์ที่ 12 ตุลาคม พ.ศ. 2558

Assembly Module : ตอนที่ 2 จาก Type สู่ Assembly Module


เราได้เรียนรู้แล้วว่าการเขียนโปรแกรมด้วยภาษาในตระกูลของ ,Net Framework ซึ่งมีกฏของ CTS ระบุไว้ว่าใน 1 program จะต้องมีอย่างน้อย 1 Type หรือ 1 class การแปลงจาก Type มาเป็น assembly module เป็นยังไง เรามาลองดูตัวอย่าง code ง่ายๆ ตาม code ข้างล่างน่ะครับ

using System;

public class Program
{
    public static void Main()
    {
            System.Console.WriteLine("Hello World");
   
    }
}

code ข้างบนให้เขียนด้วยโปรแกรม notepad แล้ว save เป็น file ชื่อ Program.cs ที่ผมให้เขียนด้วยโปรแกรม notepad เพราะในบทความถัดไปผมจะอธิบายเรื่อง .netmodule ซึ่ง VisualStudio ไม่อนุญาติให้ compile code เป็น .netmodule เฉพาะคำสั่ง csc เท่านั้นที่อนุญาติ

จากนั้นเปิดโปรแกรม Command Prompt ของ Visual Studio .Net ตามรูปน่ะครับ





แล้วก็เริ่ม compile code ด้วยคำสั่ง

          csc.exe /out:Program.exe /t:exe /r:MSCorLib.dll Program.cs

คำสั่ง csc.exe เป็นคำสั่งของ C# compiler โดยมีรายละเอียดของพารามิเตอร์ดังต่อไปนี้

 /out:Program.exe (/out = output) เพื่อให้ compiler สร้าง execute file ที่ชื่อ Program.exe

/t:exe (/t = target) โดยปกติ C# compiler แบ่งโปรแกรม execute file ที่ใช้บน Windows ออกเป็น 2 ชนิดได้แก่ GUI (graphisl user interface) และ CUI (Console user interface)
พารามิเตอร์ /t:exe จะเป็นการ compiler ให้ comiple code แล้วสร้าง execute file ในรูปแบบ CUI ( Console user interface)
พารามิเตอร์ /t:winexe  จะเป็นการบอก compiler ให้ compile เป็นแบบ GUI (graphical user interface)
พารามิเตอร์ /t:library จะเป็นการบอก compiler ให้ compile เป็นแบบ library

พารามิเตอร์ /t:exe และ พารามิเตอร์ /t:winexe C# compiler จะสร้าง entry point ลงไปใน CLR Healder ด้วย ดังนั้นตัว code จำเป็นต้องมี function Main อยู่เสมอ

/r:MSCorLib.dll (/r = reference) เนื่องจากใน code มีการใช้ Type System.Console ซึ่งเป็น library มาตรฐานอยู่ใน MSCorLib.dll เราจึงต้องมีการ บอกให้ compiler ทำการ reference Type ภายใน library MSCorLib.dll

เนื่องจาก Type ที่เป็นพื้นฐานต่างๆ เช่น int byte double ต่างเป็น Type ที่อยู่ใน library MSCorLib.dll และทุกๆ โปรแกรมส่วนใหญ่จะมีการใช้ที่บ่อยครั้ง ดังนั้น C# compiler ก็จะอัตโนมัติ reference ไปที่ library นี้ โดยที่ ไม่จำเป็นต้องระบุ /r:MSCorLib.dll ก็ได้

defaul target ของ C# Compiler เป็น CUI รวมทั้ง default output file name ก็เป็นชื่อเดียวกับชื่อ file ของ source code (ในตัวอย่างคือ Program.cs) ดังนั้นเราจึงตัด พารามิเตอร์ /t:exe และ พารามิเตอร์ /out:Program.exe ออกไปได้เช่นกัน

ดังนั้น เมื่อต้องการ compile code ก็สามารถเขียนสั้นๆ ได้เป็น

                   csc.exe Program.cs

สำหรับคำสั่ง csc ยังมี พารามิเตอร์อีกหนึ่งตัวคือ /nostdlib สำหรับกรณีที่เราไม่ต้องการใช้ standard library ของ .Net Framework หมายความเราทำ library ของตัวเองขึ้นมาทั้งหมด ดังนั้นการใช้ /nostdlib เมื่อ compile code ไปแล้วเราจะได้ managed module และยังเป็น Assembly Module ในตัวเดียวกัน

แต่จาก code ตัวอย่างจะเห็นว่า Program.cs ไม่สามารถทำงานได้ด้วยตัวเองยังต้องใช้ standard library ของ .Net Framework  ดังนั้นตัวโครงสร้างของ execute file Program.exe ในส่วนของ metadata (metadata เป็นตัวเก็บข้อมูลของ Type ที่ผมได้อธิบายคร่าวๆ แล้วที่ บทความนี้) จึงเป็นตัวทำให้ Program.exe กลายเป็น Assembly Module

metadata ผมจะอธิบายอย่างละเอียดในบทความถัดไปครับ

พบกันใหม่บทความหน้าครับ
TuChay

วันพฤหัสบดีที่ 1 ตุลาคม พ.ศ. 2558

Assembly Module : ตอนที่ 1 บทนำ


บทความที่ผ่านมาผมได้แนะนำให้รู้จักกับ Managed Module กันมาแล้ว ซึ่งใน Managed module จะมีทั้งหมด 4 ส่วนคือ PE, CLR Header, Metadata และ IL language แต่จริงๆแล้วโปรแกรมที่เราเขียนขึ้นจะทำงานได้ไม่ใช่แค่ Managed module เพราะโปรแกรมจะมีการเรียกใช้ library พื้นฐาน ต่างๆของ .Net framework เช่น int หรือ string ซึ่งเป็น Type ที่อยู่ใน namespace System ของ MSCorelib.dll

การทำงานรวมกันระหว่างโปรแกรมที่เราเขียนและ library อื่นๆ ไม่ว่าจะเป็นของ .Net framework หรือ library ของคนอื่นๆ จะทำงานร่วมกันภายใต้ Assembly module ที่ผมกำลังจะอธิบายต่อจากนี้

เนื่องจากผมจะพยายามอธิบายในเรื่องของหลักการ ดังนั้นตัวอย่างการเขียน code จะเน้นไปที่เขียน code ด้วย notepad และการ compile code ก็จะใช้คำสั่ง csc ผ่าน command เพราะว่า Visual Studio มี option ที่ให้นักพัฒนาสามารถ config compiler ได้โดยที่ไม่ต้องรู้รายละเอียดมากนัก จนทำให้เราขาดความรู้ความเข้าใจอย่างลึกซึ้งเกี่ยวกับ .Net framework ผมหวังว่าบทความต่อไปนี้ผู้อ่านจะเข้าใจหลักการพื้นฐาน ของ .Net framework มากขึ้นครับ

พบกันใหม่บทความหน้าครับ
TuChay

วันจันทร์ที่ 28 กันยายน พ.ศ. 2558

type conversion แบบ explicit และ implicit ตอนที่ 3


ในตอนที่ 2 ผมแสดงให้เห็นการ convert Type ถ้าเลือก Type ไม่เหมาะสม ตัวแปรที่เก็บค่าอาจจะมีความผิดพลาดได้ และผมได้ทิ้งคำถามว่าถ้างั้นเราใช้ Type ที่เก็บค่าได้มากๆ เลยดีไหมจะทำให้ไม่มีการผิดพลาดในการเก็บข้อมูล

เพื่อพิสูจน์ความคิดนี้เรามาลองดูตัวอย่าง code นี้น่ะครับ

     const int _count = 1000000;
     long bytes1 = GC.GetTotalMemory(true);
     int[] array = new int[_count];
     array[1] = int.MaxValue;     
     long bytes2 = GC.GetTotalMemory(true);
     long difference = bytes2 - bytes1;
     double per = (double)difference / _count;
     Console.WriteLine("Program used: {0:0.0} MB",
            (double)difference / (1024 * 1024));
     Console.WriteLine("Each decimal element required: {0:0.0}  

             bytes", per);


ในตัวอย่าง โปรแกรมมีการถาม GC (Garbage Collector - ผมจะอธิบายอย่างละเอียดในบทความถัดๆ ไปครับ) ว่า ณ ตอนนี้โปรแกรมมีการใช้หน่วยความจำไปเท่าไร ด้วยคำสั่ง

             long bytes1 = GC.GetTotalMemory(true);

จากนั้นโปรแกรมจะทำการจองหน่วยความจำ โดยทำการสร้าง Array ที่เก็บข้อมูลชนิด integer จำนวน 1,000,000 dataและให้ array ตำแหน่งที่ 1 เก็บค่ามากที่สุดของ Type ชนิด integer.

     int[] array = new int[_count];
     array[1] = int.MaxValue; 
   


จากนั้นโปรแกรม ถาม GC อีกครั้งว่าโปรแกรมมีการใช้หน่วยความจำไปเท่าไร

     long bytes2 = GC.GetTotalMemory(true);

ดังนั้นโปแกรมก็จะรู้ว่าการจองหน่วยความจำ array เพื่อเก็บข้อมูลชนิด integer จำนวน 1,000,000 ใช้หน่วยความจำไปเท่าไรด้วยคำสั่ง

    
     long difference = bytes2 - bytes1;

และจาก code โปรแกรมยังบอกหน่วยความจำที่ใช้แต่ล่ะ array ด้วยคำสั่ง

     double per = (double)difference / _count;

จากนั้นโปรแกรมก็พิพม์ผลออกทางหน้าจอ

     Console.WriteLine("Program used: {0:0.0} MB",
            (double)difference / (1024 * 1024));
     Console.WriteLine("Each decimal element required: {0:0.0}  

             bytes", per);

สำหรับ  Console.WriteLine("Program used: {0:0.0} MB",(double)difference / (1024 * 1024)); สามารถเขียนได้อีกแบบ คือ

     Console.WriteLine("Program used: " + ((double)difference / (1024 * 1024)).ToString("0.0") + " MB");

เนื่องจาก GC.Collector จะ ให้ค่ามีหน่วยเป็น byte
ถ้าหารด้วย 1024 เราก็จะได้หน่วยที่เป็น KB
ถ้าหารด้วย 1024*1024 เราก็จะได้หน่วยที่เป็น MB

ที่นี้เรามาดูผลการรันน่ะครับ



จะเห็นว่าโปรแกรมใช้หน่วยความจำไป 3.8M เราแต่ล่ะ block ของ array จะใช้หน่วยความจำ 4 byte

ที่นี้เราลองมาเปลี่ยน code เป็นดังนี้

            const int _count = 1000000;
     long bytes1 = GC.GetTotalMemory(true);
     decimal[] array = new decimal[_count];
     array[1] = int.MaxValue;
     long bytes2 = GC.GetTotalMemory(true);
     long difference = bytes2 - bytes1;
     double per = (double)difference / _count;
     Console.WriteLine("Program used: {0:0.0} MB",
                (double)difference / (1024 * 1024));
     Console.WriteLine("Each decimal element required: {0:0.0} 

                ytes",per);


ในตัวอย่าง code ที่สองเปลี่ยนแค่ array ที่เก็บ Type ชนิด integer ไปเก็บ Type ที่เป็น decimal

ลองดูผลการรันโปรแกรม



จะเห็นว่ามีการใช้หน่วยความจำไปถึง 15.3 MB ทั้งๆ ที่เก็บข้อมูลเหมือนๆ กับตัวอย่าง code แรก

จากผลการทดลองนี้เราก็สรุปได้ว่า การใช้ตัวแปรที่เก็บข้อมูลได้เยอะโปรแกรมก็จะต้องการหน่วยความจำมากขึ้นดังนั้นเมื่อเราเขียนโปรแกรมเราจึงควรเลือกใช้ Type ให้เหมาะสม โดยเฉพาะตัวแปรของ library ที่พัฒนาเพื่อให้คนอื่นนำไปใช้

พบกันใหม่ในบทความหน้าครับ
TuChay

    



type conversion แบบ explicit และ implicit ตอนที่ 2


จากตอนที่ 1 เราเห็นแล้วว่า การใช้ explicit กับ implicit จะมีรูปแบบการเขียน code เหมือนกัน ในตอนนี้เราจะมาดูข้อแตกต่างกันน่ะครับ

op_implicit
จะใช้สำหรับการแปลง Type โดยที่ไม่สนใจกับการสูญหายของข้อมูลและความแม่นยำในการแปลง ตัวอย่างเช่น Type ชนิด Decimal เมื่อแปลงเป็น Type ชนิด integer ข้อมูลบางส่วนอาจจะหายได้ 

ลองดู code ตัวอย่างน่ะครับ

       double testDouble = (double)Int32.MaxValue + 1;
       int testInt = (int)testDouble;
       Console.WriteLine("double value = " + testDouble.ToString());
       Console.WriteLine("int value = " + testInt.ToString());

เมื่อเราทำการ run software จะได้ดังรูป



จะเห็นว่าถ้าค่าที่แปลงน้อยกว่า 2,147,483,648 การแปลงจาก Type ชนิด double ไปเป็น Integer จะไม่มีปัญหาแต่ถ้าเกินเมื่อไรค่าที่ได้จะให้ตัวเลขที่ผิดพลาดทันที

op_explicit
จะใช้สำหรับการแปลง Type โดยที่สนใจกับการสูญหายของข้อมูลหรือต้องการความแม่นยำในการแปลงเช่น การแปลง Type ชนิด Decimal ไปเป็น Double
ลองดูตัวอย่าง code น่ะครับ

     decimal testDecimal = Decimal.MaxValue;
     double testDouble = (double)testDecimal + 1;
     Console.WriteLine("decimal value = " + testDecimal.ToString());
     Console.WriteLine("double value = " + testDouble.ToString());


เมื่อเราทำการ run software จะได้ดังรูป


จะเห็นว่าเมื่อแปลงข้อมูลมาแล้ว ข้อมูลยังคงถูกต้องเสมอ

จากตัวอย่างจะเห็นว่างั้นเราควรเลิกใช้ int แล้วหันมาใช้ decimal อย่างเดียวกันดีกว่าเพราะการแปลงข้อมูลแม่นยำมาก .... ในบทความหน้าผมจะแสดงให้ดูครับว่าเราควรเลิกใช้ int โดยเปลี่ยนไปใช้ decimal ดีกว่าหรือเปล่า

พบกันใหม่บทความหน้าครับ
TuChay

วันพฤหัสบดีที่ 24 กันยายน พ.ศ. 2558

Type conversion แบบ explicit และ implicit ตอนที่ 1



ผมได้อธิบายเกี่ยวกับ Type และ CLS มาได้หลายบทความแล้ว วันนี้ผมจะอธิบายเกี่ยวกับ การ convert Type ซึ่งเป็นคนล่ะอย่างกับการ convert ตัวแปร ผมเข้าใจว่าผู้อ่านคงรู้จักการ convert ชนิดตัวแปร เช่นการ convert จาก ตัวเลขเป็นตัวหนังสือโดยใช้ method ToString() หรือการ cast ตัวแปรเช่น (int)100

ในชุด .Net Framework มี operation ในการ convert Type อยู่ 2 อย่างคือ op_Implicit กับ op_Explicit วิธีการใช้การ convert Type ของทั้งอย่างจะเหมือนกัน แต่ข้อแตกต่างผมจะไปอธิบายในตอนที่ 2 น่ะครับ ตอนนี้ผมจะอธิบายวิธีใช้ของทั้งคู่ได้ยังไงบ้าง

สมมุติผมมี Type ที่ชื่อ Currency ซึ่งมีรายละเอียดดังนี้

    public class Currency
    {
        public double Rate;
        public string Sign;

        public Currency(double rate, string sign)
        {
            Rate = rate;
            Sign = sign;
        }
        public static implicit operator double(Currency rhs)
        {
            return rhs.Rate;
        }
    }


 จาก code ข้างบนเรามา focus ที่ method นี้ น่ะครับ

        public static implicit operator double(Currency rhs)

ในที่นี้เราจะบอก compiler ว่าเรามี method ชนิด static ที่เป็น operator double โดยมีการรับค่าเข้ามาเป็น Type Currency และมีการ return ค่าที่เป็น Type double  

ตรงนี้สำคัญน่ะครับ Type ที่จะ return ออกไปจะต้องเป็น Type ชนิดเดียวกับ operator ในตัวอย่างนี้ method คือ operator double ดังนั้น Type ที่จะ return จะต้องเป็น Type double ตรงนี้สำคัญมาก

เมื่อมาดูที่ IL code เราจะเห็นว่า C# compiler ได้สร้าง method ที่ชื่อ op_Implicit : float64(class ImplicitTester.Currency)จริงๆ วิธีการตั้งชื่อก็มาจากการนำคำว่า "op_Implicit" มาเป็นชื่อ method เลย โดยที่จะมีการ convert Type ชนิด Currency ไปเป็น float64 



Type ชนิด double ในภาษา C# จะถูกแปลงเป็น Type float64 ใน IL ถ้าอยากใช้ Type double ในภาษา IL จะต้องใช้คำสั่ง [mscorlib]System.Double 

เมื่อเรา double click ที่ method op_Implicit : float64(class ImplicitTester.Currency) เราจะได้ IL code ของ method นี้ดังรูปข้างล่าง



ที่นี้เรามาดูตัวอย่างวิธีการใช้ implicit operator ของ Type Currency กับครับ

            Currency thai = new Currency(35.20, "BTH");
            Console.WriteLine((double)thai);


ที่คำสั่ง (double)thai ก็คือ method public static implicit operator double(Currency rhs) นั้นเอง โดย (double) ก็คือ operator double และ Thai ก็คือ Type ชนิด Currency ตัวอย่างนี้ถ้าพูดเป็นภาษาไทยง่ายๆ ก็คือการแปลง Type ชนิด Currency ไปเป็น Type double

ที่นี้เรามาลองเพิ่ม explicit operator ของ Type Currency ดูน่ะครับ (ตัวอย่างข้างบนเป็น implicit operator น่ะครับ อย่าเพิ่งงง)

        public static explicit operator string(Currency rhs)
        {
            return rhs.Sign;
        }


จะเห็นว่า operator ของ method นี้เป็น Type ชนิด string นั้นหมายความ method นี้จะต้อง return ค่าออกมาเป็น string

เมื่อ C# compiler ทำการ compile code ตัว compiler ก็จะสร้าง method ขึ้นมาใหม่ชื่อ op_Explicit ตามรูปข้างล่าง



method op_Explicit จะทำการ convert Type Currency ออกมาเป็น string

และเมื่อ doublic clik เพื่อเข้าไปดู code ข้างใน method ก็จะได้ดังรูปข้างล่าง




สำหรับตัวอย่างวิธีการใช้ก็คือ

            Currency thai = new Currency(35.20, "BTH");
            Console.WriteLine((string)thai);


ลองมาดูตัวอย่างสุดท้าย โดยการเพิ่ม operator Currency ลงไปใน Type Currency

        public static explicit operator Currency(string sign)
        {
            return new Currency(0, sign);
        }



วิธีการใช้ก็ตามตัวอย่างข้างล่างครับ

       Currency cny = (Currency)("CNY");

พูดง่ายๆ ก็คือ convert Type ที่เป็น string ไปเป็น Type Currency

รูปแบบการเขียน code ระหว่าง explicit และ implicit จะเหมือนกันแต่วิธีการเอาไปใช้จะแตกต่างกันเล็กน้อยซึ่งผมจะอธิบายพร้อมยกตัวอย่างในตอนที่ 2 ครับ

พบกันใหม่บทความหน้าครับ
TuChay