Archive

Archive for the ‘C++’ 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 ตัวอย่าง

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

วิธีตรวจจับ Memory Leak โดยง่ายของ Visual C++

วิธีนี้จะตรวจจับได้เฉพาะ Memory ที่ Alloc โดยใช้ฟังชั่นของ CRT (C-Runtime) เท่านั้นนะคับ เช่น malloc, realloc, operator new ฯลฯ และจะต้องเป็น Debug Build เท่านั้น หากเป็น Release Build ตัวตรวจจับจะถูกถอดออกอัตโนมัติ ส่วน Memory ที่ Alloc โดยใช้ฟังชั่นนอกเหนือจากนั้น (เช่น HeapAlloc ของ Win32 API) จะไม่สามารถตรวจจับด้วยวิธีนี้ได้ โดยการตรวจจับจะรายงานผลตอนที่โปรแกรมจบการทำงานแล้วผ่านทาง Debug Message ซึ่งดูได้ด้วยโปรแกรม DebugView หรือช่อง Output ของ Visual Studio หาก Debug ใน Visual Studio

วิธีการคือ ให้เรียกฟังชั่น _CrtSetDbgFlag (ต้อง include crtdbg.h เข้ามาก่อน) แบบตัวอย่างด้านล่างตอนที่โปรแกรมเริ่มการทำงาน

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

ตัวอย่างการใช้งานใน DllMain

#include <crtdbg.h>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID /* lpReserved */)
{
	if (DLL_PROCESS_ATTACH == ul_reason_for_call)
	{
		DWORD dwResult, dwBufferSize;
		wchar_t *pszModuleName;

		// Enable C-Runtime memory leak reporter
		_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

		// Get Module Name
		pszModuleName = nullptr;
		dwBufferSize = MAX_PATH / 2;

		do
		{
			free(pszModuleName);
			pszModuleName = reinterpret_cast<wchar_t *>(malloc((dwBufferSize *= 2) * sizeof(wchar_t)));

			dwResult = GetModuleFileName(hModule, pszModuleName, dwBufferSize);
		} while (dwResult == dwBufferSize);

		PathStripPath(pszModuleName);

		g_pszModuleName = pszModuleName;

		// Init WTL
		_Module.Init(NULL, hModule);
	}
	else if (DLL_PROCESS_DETACH == ul_reason_for_call)
	{
		// Free allocated memory
		free(const_cast<wchar_t *>(g_pszConfigDirectory));
		free(const_cast<wchar_t *>(g_pszNppDirectory));
		free(const_cast<wchar_t *>(g_pszModuleName));

		// Terminate WTL
		_Module.Term();
	}

	return TRUE;
}

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

Categories: C, C++, Programming, Software Development Tags:

Performance ระหว่าง i++ และ ++i

คนเก่งหลายๆคน มักจะใช้ ++i แทนการใช้ i++ ใน for ลูป โดยคิดว่า มันทำงานเร็วกว่า i++ ซึ่งก็ไม่แปลกที่จะคิดแบบนั้น เพราะตัวผมเองก็เห็นคำตอบของชาวต่างชาติหลายคนบอกไว้แบบนี้เหมือนกัน แต่ในความเป็นจริงแล้ว บน Visual C++ มันไม่ต่างกันเลย

เมื่อ Visual C++ คอมไพล์ Code นี้

int _tmain(int argc, _TCHAR* argv[])
{
	for (int i = 0; i < argc; ++i)
		_tprintf(_T("%d"), i);

	return 0;
}

Code ที่ได้จะเป็น

	mov	DWORD PTR _i$5259[ebp], 0
	jmp	SHORT $LN3@wmain
$LN2@wmain:
	mov	eax, DWORD PTR _i$5259[ebp]
	add	eax, 1
	mov	DWORD PTR _i$5259[ebp], eax
$LN3@wmain:
	mov	eax, DWORD PTR _i$5259[ebp]
	cmp	eax, DWORD PTR _argc$[ebp]
	jge	SHORT $LN1@wmain
; Line 10
	mov	esi, esp
	mov	eax, DWORD PTR _i$5259[ebp]
	push	eax
	push	OFFSET ??_C@_15KNBIKKIN@?$AA?$CF?$AAd?$AA?$AA@
	call	DWORD PTR __imp__wprintf
	add	esp, 8
	cmp	esi, esp
	call	__RTC_CheckEsp
	jmp	SHORT $LN2@wmain

และเมื่อคอมไพล์ Code นี้

int _tmain(int argc, _TCHAR* argv[])
{
	for (int i = 0; i < argc; i++)
		_tprintf(_T("%d"), i);

	return 0;
}

Code ที่ได้จะเป็น

	mov	DWORD PTR _i$5259[ebp], 0
	jmp	SHORT $LN3@wmain
$LN2@wmain:
	mov	eax, DWORD PTR _i$5259[ebp]
	add	eax, 1
	mov	DWORD PTR _i$5259[ebp], eax
$LN3@wmain:
	mov	eax, DWORD PTR _i$5259[ebp]
	cmp	eax, DWORD PTR _argc$[ebp]
	jge	SHORT $LN1@wmain
; Line 10
	mov	esi, esp
	mov	eax, DWORD PTR _i$5259[ebp]
	push	eax
	push	OFFSET ??_C@_15KNBIKKIN@?$AA?$CF?$AAd?$AA?$AA@
	call	DWORD PTR __imp__wprintf
	add	esp, 8
	cmp	esi, esp
	call	__RTC_CheckEsp
	jmp	SHORT $LN2@wmain

สังเกตตรง

$LN2@wmain:
	mov	eax, DWORD PTR _i$5259[ebp]
	add	eax, 1
	mov	DWORD PTR _i$5259[ebp], eax

จะเห็นว่ามันเหมือนกันเปะ

Categories: C, C++, Programming, Software Development Tags:

virtual จำเป็นหรือไม่สำหรับ Method ที่เป็นตัว Override ของคลาสที่สืบทอดไป

เวลา Override method คนส่วนมากมักจะใช้ virtual บน Method ที่เป็นตัว Override แบบด้านล่าง

class Root
{
public:
	Root()
	{
	}

	virtual ~Root()
	{
	}

	virtual void Bar()
	{
	}
};

class Foo : public Root
{
public:
	Foo()
	{
	}

	virtual ~Foo()
	{
	}

	virtual void Bar()
	{
	}
};

แต่ในความเป็นจริงแล้ว มันไม่มีความจำเป็นเลย Code ด้านล่างนี้ทำงานเหมือนกับ Code ข้างบน

class Root
{
public:
	Root()
	{
	}

	virtual ~Root()
	{
	}

	virtual void Bar()
	{
	}
};

class Foo : public Root
{
public:
	Foo()
	{
	}

	~Foo()
	{
	}

	void Bar()
	{
	}
};

และด้านล่างนี้คือ Code ตัวอย่างพร้อมผลลัพธ์การทำงาน

#include <stdio.h>

class Root
{
public:
	Root()
	{

	}

	virtual ~Root()
	{
		printf("In ~Root()\n");
	}

	virtual void Boo()
	{
		printf("In Root::Boo()\n");
	}

	virtual void Bee()
	{
		printf("In Root::Bee()\n");
	}
};

class Foo : public Root
{
public:
	Foo()
	{

	}

	~Foo()
	{
		printf("In ~Foo()\n");
	}

	void Boo()
	{
		printf("In Foo::Boo()\n");
	}

	virtual void Bee()
	{
		printf("In Foo::Bee()\n");
	}

	virtual void Bam()
	{
		printf("In Foo::Bam()\n");
	}
};

class Bar : public Foo
{
public:
	Bar()
	{

	}

	~Bar()
	{
		printf("In ~Bar()\n");
	}

	void Boo()
	{
		printf("In Bar::Boo()\n");
	}

	void Bam()
	{
		printf("In Bar::Bam()\n");
	}
};

int main(int argc, char *argv[])
{
	Foo *foo = new Bar();
	Root *root = foo;

	foo->Boo();
	foo->Bam();
	root->Bee();

	delete root;

	return 0;
}

ผลลัพธ์

In Bar::Boo()
In Bar::Bam()
In Foo::Bee()
In ~Bar()
In ~Foo()
In ~Root()

อะไรจะเกิดขึ้นเมื่อทำการเรียก Default Assigment Operator กับคลาสที่มี Constant Member

ปรกติแล้ว ตามมารตฐาน C++ คอมไพล์เลอร์จะทำการสร้าง Assignment Operator มาให้ทุกคลาสโดยอัตโนมัติหากเราไม่สร้างมันขึ้นมา ซึ่งการทำงานของ Default Assignment Operator ตัวนี้ จะทำการเรียก Assignment Operator ของ Member ทุกตัวภายในคลาสหาก Member ตัวนั้นเป็น Object แต่หาก Member ตัวนั้นเป็นตัวแปรทั่วๆไป (เช่น int) รวมถึงตัวแปรแบบ struct ก็จะทำการ Copy ข้อมูลแบบปรกติ

หาก Member ของคลาสมีตัวที่เป็น Constant อยู่ละจะเกิดอะไรขึ้น? ผลลัพธ์คือ…

สรุป

หาก Member ของคลาสมี Constant อยู่ Assignment Operator จะไม่ถูกสร้างขึ้นมาโดยอัตโนมัติ