Archive

Archive for the ‘.NET Framework’ Category

การ Embed Common Language Runtime 4.0 โดยใช้ Visual C++ ภาค 1

สิ่งที่ต้องมี

  • ความรู้ Visual C++
  • ความรู้เกี่ยวกับ Component Object Model (COM)
  • ความรู้เกี่ยวกับ Common Language Runtime (CLR) และเรื่องอื่นๆของ .NET Framework

ภาพรวม

Component สำหรับ Embed CLR นั้น หลักๆจะมีอยู่สามส่วน คือ

  1. ICLRMetaHost เป็น Interface สำหรับจัดการกับ CLR ทั้งหมดที่ติดตั้งอยู่ในเครื่อง เช่น ลิสเวอร์ชั่นที่ติดตั้ง
  2. ICLRRuntimeInfo เป็น Interface สำหรับจัดการกับ CLR เช่น Get พาธของแฟ้มที่ติดตั้ง CLR ตัวนั้น หรือสั่งโหลด CLR เข้ามาใน Process
  3. ICLRRuntimeHost เป็น Interface สำหรับจัดการกับ CLR ที่ถูกโหลดเข้ามาใน Process แล้ว เราจะสั่งรัน Common Intermediate Language (CIL) โดยใช้ Interface นี้

การ Embed นั้น งานหลักๆที่ต้องทำคือ

  1. เรียกฟังชั่น CLRCreateInstance เพื่อสร้าง Object ที่ Implement ICLRMetaHost
  2. ลิส CLR ที่ติดตั้งอยู่ในเครื่องทั้งหมดด้วย ICLRMetaHost::EnumerateInstalledRuntimes แล้วเลือกเวอร์ชั่นที่ต้องการจะ Embed หรือเรียก ICLRMetaHost::GetRuntime เพื่อระบุเวอร์ชั่นที่ต้องการได้เลย
  3. เรียก ICLRRuntimeInfo::GetInterface เพื่อโหลด CLR เวอร์ชั่นที่เลือกเข้ามาใน Process
  4. เริ่มการทำงานของ CLR ด้วยการเรียก ICLRRuntimeHost::Start
  5. รัน CIL ด้วย ICLRRuntimeHost::ExecuteInDefaultAppDomain

รายละเอียด

ก่อนอื่น ให้เรา Initialize COM สำหรับเธรดหลักโดยใช้ CoInitializeEx ก่อน ตัวอย่าง

hrResult = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_SPEED_OVER_MEMORY);

จากนั้น ให้เรียก CLRCreateInstance เพื่อที่จะ Get ข้อมูลของ CLR เวอร์ชั่นที่ต้องการ ตัวอย่าง

ATL::CComPtr pClrMetaHost;

hrResult = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, reinterpret_cast(&pClrMetaHost));

ต่อไปสิ่งที่เราจะทำก็คือ เลือก CLR เวอร์ชั่นที่ต้องการใช้งาน ตัวอย่างการเลือกเวอร์ชั่น 4.0

ATL::CComPtr pClrInfo;

hrResult = pClrMetaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, reinterpret_cast(&pClrInfo));

พารามิเตอร์แรกของ ICLRMetaHost::GetRuntime จะเป็นเวอร์ชั่นของ CLR ที่ต้องการ โดยชื่อจะต้องตรงกับชื่อแฟ้มที่อยู่ใน C:\Windows\Microsoft.NET\Framework
พอได้ ICLRRuntimeInfo มาแล้ว สิ่งต่อไปที่เราจะทำก็คือ โหลด CLR ตัวนั้นเข้ามาใน Process ตัวอย่าง

ATL::CComPtr pClr;

hrResult = pClrInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, reinterpret_cast(&pClr));

เมื่อ CLR ถูกโหลดเข้ามาใน Process แล้ว เราต้องเริ่มการทำงานของ CLR ตัวนั้นเสียก่อน ถึงจะสามารถรัน CIL ได้ ตัวอย่างการรัน CLR

hrResult = pClr->Start();

เมื่อทุกอย่างพร้อมแล้ว สิ่งที่เราจะทำต่อไปก็คือ รัน CIL ที่เขียนขึ้นด้วยโค๊ดของ C# ต่อไปนี้

using System.Windows.Forms;

namespace Putta.CLREmbedding
{
    public class Application
    {
        public static int Start(string commandLine)
        {
            return (int)MessageBox.Show(string.Format("Message from CIL World ! Application Command Line is: {0}", commandLine), "Message", MessageBoxButtons.OKCancel);
        }
    }
}

ตัวอย่างการรัน

hrResult = pClr->ExecuteInDefaultAppDomain(L"Application.dll", L"Putta.CLREmbedding.Application", L"Start", pszCommandLine, &dwCilResult);

พารามิเตอร์แรกจะเป็นชื่อ Assembly ของ CIL ที่เราต้องการจะรัน พารามิเตอร์ที่สองจะเป็นชื่อคลาสของ Method ที่จะรัน ส่วนพารามิเตอร์ที่สามจะเป็นชื่อ Method ที่ต้องการจะรัน โดยต้องประกาศเป็นแบบ public static int และรับพารามิเตอร์เป็น string หนึ่งตัวเท่านั้น ส่วนค่าที่ Return จาก Method จะถูกเก็บไว้ใน dwCilResult

Project ตัวอย่าง

ดาวน์โหลดได้จาก ที่นี่

ประโยชน์ของ DateTimeOffset และการใช้งานเบื้องต้น

ปรกติแล้ว ใน .NET Framework เรามักจะใช้ DateTime ในการจัดเก็บเวลากัน ซึ่ง คลาสนี้จะมีข้อเสียอยู่ คือ มันไม่สามารถเก็บ Timezone ไว้ได้ ทำให้เวลามันไม่อิงที่เวลา UTC เพราะมันเก็บแค่เวลาของพื้นที่นั้นๆ ผลที่ตามมาคือ เราจะไม่สามารถแสดงผลและเปรียบเทียบเวลาได้อย่างถูกต้องในบางสถานการณ์

ตัวอย่างสถานการณ์ที่จะเกิดปัญหา

  1. นาย A อยู่ที่ญี่ปุ่น (UTC +09.00)
  2. นาย B อยู่ที่อังกฤษ (UTC +00.00)
  3. นาย B ส่งข้อความไปให้นาย A โดยข้อมูลที่ถูกส่งไปมีสองอย่างคือ ข้อความ และ เวลาที่ข้อความถูกส่งออกมา สมมุติให้เวลาขณะนั้นของนาย B เป็น 07:00 นาฬิกา และเวลาของนาย A จะเท่ากับ 16:00 นาฬิกา (เอาเวลาของนาย B บวกด้วย 09:00 ซึ่งเป็น Timezone ของนาย A จะได้เวลาปัจจุบันของนาย A สาเหตุที่บวก 09:00 ตรงๆเพราะว่า Timezone ของนาย B เป็น +00:00)
  4. หากใช้ DateTime เก็บเวลา นาย A จะเห็นว่าข้อความมันถูกส่งมาตอน 07.00 นาฬิกาของญี่ปุ่น (ซึ่งขณะนั้นญี่ปุ่นเป็นเวลา 16:00 นาฬิกา) ทั้งๆที่มันพึ่งถูกส่งออกมา !

ปัญหานี้แก้ได้โดยการใช้ DateTimeOffset เมื่อนาย B ส่งข้อความไปให้นาย A พร้อมด้วยเวลาที่ข้อความถูกส่งออกมาโดยใช้ DateTimeOffset เก็บเวลา เมื่อนาย A ได้รับข้อความ จะรู้ทันทีว่านาย B อยู่ Timezone ไหน และจะสามารถปรับการแสดงผลของเวลาได้ถูกต้อง เพราะเวลาที่เก็บใน DateTimeOffset เป็นเวลา UTC +00.00 เสมอ (แต่เวลาแสดงผลจริงมันจะบวก Timezone ให้เราด้วย) ไม่ว่าโปรแกรมจะทำงานอยู่ที่ Timezone ไหนก็ตาม

สิ่งน่ารู้เกี่ยวกับ DateTimeOffset

  • เวลาเปรียบเทียบ มันจะเปรียบเทียบโดยใช้เวลาที่เก็บอยู่อย่างเดียว (ซึ่งก็คือเวลา UTC +00:00) โดยไม่เปรียบเทียบ Timezone ตัวอย่าง
    using System;
    
    namespace TestDateTimeOffset
    {
        class Program
        {
            static void Main(string[] args)
            {
                DateTimeOffset thailand, japan;
    
                thailand = DateTimeOffset.Now;
                japan = thailand.ToOffset(TimeSpan.FromHours(9));
    
                Console.WriteLine("Thailand Time: {0}", thailand);
                Console.WriteLine("   Japan Time: {0}", japan);
    
                if (thailand == japan)
                    Console.WriteLine("This line will be printed.");
            }
        }
    }
    

    ผล

  • เวลาที่ได้จาก DateTimeOffset.Now จะเป็นเวลา UTC +00:00 เสมอ แต่ Timezone ที่ได้จะเป็น Timezone ของเครื่องที่โปรแกรมทำงานอยู่ ทำให้การเปรียบเทียบและการแสดงผลของเวลาที่ได้ถูกต้องตลอด