MSIL cơ bản – Part 4/6: Lập trình hướng đối tượng

Mặc dù là một ngôn ngữ bậc thấp nhưng CIL vẫn hỗ trợ lập trình hướng đối tượng (OOP). Trong các ví dụ trước đây bạn chỉ mới làm quen với việc tạo ra các phương thức tĩnh đơn lẻ và không tận dụng được thế mạnh OOP này. Trong phần này, tôi sẽ giới thiệu cách để tạo và sử dụng namespace, class, struct, method, constructor, field và property…

Namespace và class

Việc tạo namespace rất đơn giản, bạn chỉ cần sử dụng chỉ thị .namespace và theo sau đó là tên gọi bạn muốn đặt cho namespace của mình.

.namespace Foo

{

// […]

}

Việc tạo một class cũng tương tự như trong C#, bạn có thể sử dụng các từ khóa như sealed, abstract và có thể thừa kế từ một class khác. Chỉ thị để định nghĩa một class là .class, cú pháp chung để định nghĩa là:

.class attributes classname extends basetype implements interfaces

Trong đó các attribute có thể sử dụng là:

  • abstract: không thể tạo instance
  • ansi/ unicode: các chuỗi dùng trong class sẽ được xem là ansi hoặc unicode
  • private/ public/family: phạm vi truy xuất (family tương đương protected trong C#)
  • sealed: không thể được thừa kế
  • serializable: có thể được serialized.

.class abstract Bar1

{

// […]

}

.class abstract sealed Bar2

{

// […]

}

Để thừa kế một lớp, bạn dùng từ khóa extends và để hiện thực hóa một interface, bạn dùng từ khóa implements giống như trong Java.

.class Bar3 extends [mscorlib]System.Object implements ITest

{

// […]

}

Định nghĩa một interface:

.class interface ITest

{

// […]

}

Định nghĩa một struct bằng cách cho kế thừa từ System.ValueType:

.class MyStruct extends [mscorlib]System.ValueType

{

// […]

}

Method

Định nghĩa

Cú pháp chung để định nghĩa một phương thức:

.method attributes returnType methodName (arguments)

Ngoài các attribute này tương tự các từ khóa quen thuộc như public, private, static,final, virtual, abstract,… mà ta sử dụng trong C#. Đối với một số phương thức đặc biệt, ta còn cần phải sử dụng các attribute sau:

  • specialname: phương thức sử dụng attribute này là các trường hợp đặc biệt ví dụ như các getter và setter.
  • rtspecialname: phương thức sử dụng tên đặc biệt, ví dụ như constructor.

Ngoài ra có một số từ khóa bổ sung phía sau phần tham số của phương thức như là cil, native và managed, unmanaged. Chúng có mục đích xác địch rằng mã lệnh của phương thức là mã CIL hay mã native và được quản lý bởi .Net (managed) hay không (unmanaged).

Gọi phương thức

Có hai loại phương thức mà bạn cần phân biệt là phương thức static và phương thức instance. Mặc định các phương thức đã được đặt là instance nên bạn không cần dùng từ khóa này để định nghĩa. Muốn gọi một phương thức instance bạn cần phải có một đối tượng tham chiếu ở trong stack

Để gọi phương thức static, bạn dùng lệnh call theo sau là khai báo đầy đủ của phương thức cần gọi, theo dạng sau

call returnType className::method(arguments)

Nếu đó là một phương thức instance bạn phải thêm từ khóa instance ngay sau lệnh call.

Truyền và sử dụng tham số

Để truyền các tham số cho phương thức, bạn đưa các giá trị cần truyền vào stack và gọi phương thức như cách ta vẫn làm khi sử dụng phương thức WriteLine().

Trong thân phương thức, để làm việc với các tham số truyền vào ta dùng lệnh:

ldard.<chỉ số của tham số>

Và để trả về một giá trị cho phương thức, ta chỉ cần đặt giá trị đó vào stack và gọi ret.

Hãy thử một ví dụ về phương thức tìm giá trị lớn nhất của hai số:

.assembly extern mscorlib {}
.assembly Foo{}

.method static void main()
{
    .entrypoint
    .maxstack 2

	ldc.i4 6
	ldc.i4 5

	call int32 Max(int32,int32)

	ldstr "Max value is: "
	call void [mscorlib]System.Console::Write(string)
    	call void [mscorlib]System.Console::WriteLine(int32)

	ret
	}

.method static int32 Max(int32, int32)
{
	.maxstack 2
	ldarg.0
	ldarg.1
	bgt greater
	ldarg.1
	ret

greater:
	ldarg.0
	ret
}

Một điểm cần chú ý là đối với các đối tượng instance, mỗi phương thức của đối tượng được gọi sẽ truyền một tham số trỏ đến chính đối tượng đó (this) một cách ngầm định (có chỉ số là 0). Vì ví dụ trên chưa cần sử dụng đến tham số đặc biệt này nên tôi sẽ giải thích kĩ hơn trong phần về Field và Property).

Constructor

Trong CIL, constructor là một method nhưng tên phải được đặt là .ctor. Để tạo một đối tượng của class thông qua constructor ta sử dụng lệnh newobj để gọi constructor tương tự như gọi các phương thức thông thường. Việc gọi constructor có mục đích khởi tạo các giá trị cần thiết cho các thành viên của lớp.

Ta hãy viết lại chương trình tìm max của hai số ở trên dưới dạng phương thức instance và class để chương trình có tính OOP hơn:

.assembly extern mscorlib {}
.assembly Foo{}

.class Program
{
	.method static void main()
	{
		.entrypoint
		.maxstack 3

		ldc.i4 4
		ldc.i4 2

		newobj void Y2.Math::.ctor()
		call instance int32 Y2.Math::Max(int32,int32)

		ldstr "Max value is: "
		call void [mscorlib]System.Console::Write(string)
		call void [mscorlib]System.Console::WriteLine(int32)

		ret
	}
}
.namespace Y2
{
	.class Math
	{
		.method public void .ctor()
		{
			.maxstack 1

			ldstr "constructor"
			call void [mscorlib]System.Console::WriteLine(string)

			ret
		}

		.method public int32 Max(int32, int32)
		{
			.maxstack 2

			ldarg.0
			ldarg.1
			bgt greater

			ldarg.1
			ret

		greater:
			ldarg.0
			ret
		}
	}
}

Output:

constructor

Max value is: 4

Chuỗi constructor được xuất ra chứng tỏ phương thức .ctor() của lớp Math được thực thi. Trong ví dụ trên bởi vì phương thức .ctor() không cần thiết để khởi tạo các giá trị gì cho lớp, ta có thể thay đó bằng bất kì lệnh nào nạp một đối tượng tham chiếu vào stack. Đơn giản nhất là lệnh ldnull để nạp một giá trị null vào stack. Như vậy ta có thể sửa phương thức main() như sau:

.method static void main()
{
	.entrypoint
	.maxstack 3

	ldc.i4 4
	ldc.i4 2

	ldnull
	call instance int32 Y2.Math::Max(int32,int32)

	ldstr "Max value is: "
	call void [mscorlib]System.Console::Write(string)
	call void [mscorlib]System.Console::WriteLine(int32)

	ret
}

Field và Property

Việc tạo field trong CIL rất đơn giản, ta sử dụng chỉ thị .field theo sau đó là từ khóa tương tự như khai báo trong C#. Ví dụ

.field private initonly int32 myField

Trong đó từ khóa initonly tương đương với từ khóa readonly trong C#.

Property được định nghĩa bằng sử dụng chỉ thị .property kết hợp với các phương thức get/set.

Ta có một ví dụ về cách sử dụng field và property:

.assembly extern mscorlib {}
.assembly TestStudent{}

.class private Program
   extends [mscorlib]System.Object
{
	.method public static void  Main()
	{
		.entrypoint
		.maxstack 2
		.locals init (class Student s)

		newobj instance void Student::.ctor()
		stloc.0
		ldloc.0

		ldstr "YinYang"
		call instance void Student::set_Name(string)

		ldloc.0
		call instance string Student::get_Name()
		call void [mscorlib]System.Console::WriteLine(string)

		ret
	}

}

.class private Student
   extends [mscorlib]System.Object
{
	.field private string name

	.method public string get_Name()
	{
		.maxstack 1
		ldarg.0
		ldfld string Student::name
		ret
	}

	.method public void set_Name(string)
	{
		.maxstack 1
		ldarg.0
		ldarg.1
		stfld string Student::name
		ret
	}

	.method public void  .ctor()
	{
		.maxstack  2
		ldarg.0
		call instance void [mscorlib]System.Object::.ctor()

		ldarg.0
		ldstr "NoName"
		call instance void Student::set_Name(string)

		ret
	}

	.property instance string Name()
	{
		.get instance string Student::get_Name()
		.set instance void Student::set_Name(string)
	}
}

Property Name ở trên sử dụng hai phương thức get_Name() và set_Name() cho các chỉ thị .get và .set. Trong đó sử dụng hai lệnh mà có thể bạn chưa rõ cách dùng: ldfldstfld.

Công dụng và cơ chế của hai phương thức này như sau:

  • ldfld: lấy giá trị của một field đưa vào stack. Lệnh này yêu cầu trong stack phải có sẵn tham chiếu của đối tượng chứa field đó. Lệnh ldfld sẽ lấy tham chiếu đối tượng ra khỏi stack và sau đó mới thêm giá trị của field vào.
  • stfld: lệnh này thực hiện việc gán một giá trị vào field. Cũng tương tự như ldfld, nó cần một tham chiếu của đối tượng chứa field và một giá trị cần gán ở trong stack trước khi thực hiện.

Ta đi phân tích phương thức get_Name() để hiểu rõ hơn cơ chế của chúng:

.method public string get_Name()
	{
		.maxstack 1
		ldarg.0
		ldfld string Student::name
		ret
	}

Như đã nói trong phần về phương thức, mỗi phương thức của một đối tượng instance sẽ truyền chính nó vào làm tham số đầu tiên trong phương thức đó. Lúc này ta dùng ldarg.0 để nạp con trỏ của đối tượng đó vào stack, sau đó mới có thể sử dụng lệnh ldfld. Bởi vì như cơ chế lệnh ldfld như đã giải thích ở trên, phương thức get_Name() chỉ cần một slot trong stack.

Bài viết liên quan:

MSIL – Part 1: Giới thiệu về MSIL
MSIL – Part 2: Biến cục bộ
MSIL – Part 3: Rẽ nhánh và vòng lặp
MSIL – Part 4: Lập trình hướng đối tượng
MSIL – Part 5: Conversion, casting và boxing
MSIL – Part 6: Sử dụng mảng

https://yinyangit.wordpress.com

Advertisements

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s