Thứ Năm, 4 tháng 2, 2010

Parallel Port - Kiến trúc & Hoạt động

Giới thiệu

Ða số các máy vi tính đều trao đổi thông tin thông qua các ngã sau đây: Parallel port, Serial port, USB và Network card. Parallel port là một phần không thể thiếu trong việc sử dụng computer để giao tiếp với các thiết bị điện tử khác. Bài nầy chủ yếu dùng cho sinh viên hardware nhưng việc hiểu biết của nó cũng không thừa đối với các bạn học software.

Cấu trúc của Parallel port nhìn trên phương diện hardware Parallel port bao gồm 25 pins (chân) được bố trí theo sơ đồ dưới đây, đa số giao diện đầu cắm của Parallel port đều ở dạng female:

  • 8 pins dùng để gởi và nhận data (từ pin số 2 đến số 9) gọi là DATA Port (dân software cũng gọi như thế). Dữ liệu trao đổi qua 8 pin này được gói gọn trong 1 byte.
  • 5 pins dùng để hiển thị tình trạng hoạt động của parallel port: đang bận, đang gởi/nhận thông tin...(các pin số 10-13 và pin số 15) gọi là STATUS Port. Dữ liệu trao đổi qua 8 pin này dùng 5 bit cao của byte.
  • 4 pins dùng để điều khiển gọi là CONTROL Port, là các pin số 1, 14, 16 và 17. Dữ trao đổi qua pnin này dùng 4 bit thấp của byte.
  • 8 pins còn lại được dùng tùy theo ý người sử dụng. Nếu không được sử dụng thì chúng sẽ được grounded (nối đất-thuật ngữ ngành điện?).

Ðây là cấu hình được thống nhất trong công nghệ vi tính và được công nhận bởi IEEE (vốn là một tổ chức lớn nhất về qui định hardware quốc tế). Bạn có thể kiểm tra lại các số pin và đánh dấu bằng cách nhìn rõ hơn vào các dây parallel port cũng như parallel port phía sau máy vi tính của bạn.

Vài ví dụ cho hoạt động của parallel port DATA port là nơi thông tin sẽ được trao đổi từ computer đến các thiết bị khác (hai chiều). Khi lập trình ắt hẳn cũng có khi bạn nghe nói đến chuyện viết 1 program/driver cho các hardware (nếu bạn làm cho một số hãng máy in, viễn thông...). Ở đây driver cho parallel port chính là chương trình quản lý và điều khiển quá trình trao đổi thông tin này. DATA port có 8 pins tức là 1 bytes. Bạn có lẽ từng nghe kỹ thuật tải thông tin qua ngã parallel port là nhanh nhất (trong quá khứ) nhưng kỳ thực nó cũng chỉ dùng có 1byte = 8 bit = 8 cái pins nầy mà thôi! Tôi sẽ trỡ lại sau trong việc bàn thảo thế nào là một sợi dây parallel tốt.

STATUS port là nơi hiển thị các quá trình vận hành của parallel port. Một ví dụ đơn giản là giả sử bạn muốn in một bài viết ra printer (dĩ nhiên là qua ngã parallel port) nhưng khi nhấn nút "print" thì lại thấy máy vi tính hiển thị một thông báo hết giấy! Trên thực tế phía sau những hàng động nầy là một chuổi phối hợp giửa software và hardware. Khi bạn click "print" tức là bạn kích hoạt một trong những pins của CONTROL port bằng software để bảo cái printer in bài ra. Nhưng trước khi thực hiện việc in printer cũng tự biết nó hết giấy và tự kích hoạt một trong số những pins của STATUS port để báo cho computer biết là hết giấy. Kết quả là software điều khiển quá trình in kiểm tra (trước khi in) thấy được cho nên nó hiện thông báo hết giấy cho bạn. Nhiều hoạt động tương tự như printer chưa on, printer hết mực, printer bị kẹt giấy...cũng do phối hợp giửa những cái pins nầy mà ra. Tôi sẽ bàn kỹ hơn ở mức độ cấu trúc điện sau này.

Cấu trúc của parallel port nhìn trên phương diện software Thực ra thì với dân software, họ cũng không cần biết phía parallel port sau lưng máy tính có bao nhiên pin và mỗi pin cần bao nhiêu điện, cấu trúc như thế nào...Mấy cái nầy hơi thừa cho dân software! Tất cả những gì mà một người lập trình cần biết là address của các pin trên parallel port là đủ! 25 pins kia sẽ được chia làm 3 phần với tên gọi là DATA port (hay là DATA register), STATUS port (hay là STATUS register), và CONTROL port (hay là CONTROL register). Mỗi port là 8 bits với address hẵn hòi. Như mô tả từ đầu, DATA port sẽ là 8 bits, STATUS port có 5 pins cho nên sẽ cộng thêm 3 bit trống để tạo một byte, tương tự như thế cho CONTROL port. Riêng phần địa chỉ cho các port nầy cũng khá là phức tạp, vì nó liên quan đến BIOS. Nếu bạn đã hiểu cách phân bố memory của máy tính thì đơn giản hơn, còn không thì hy vọng là lối giải thích của tôi sẽ làm bạn hiểu phần nào. Ðại khái là khi máy tính bật lên (turn on) thì BIOS sẽ làm việc trước, nó sẽ tìm kiếm và định địa chỉ cho cái port trong máy của bạn. Vì BIOS không cái nào giống cái nào cho nên lối qui định địa chỉ của nó cũng khác, tuy nhiên dưới đây là một ví dụ điển hình (bạn thường thấy) trong các máy vi tính ngày nay. Những địa chỉ nầy bạn có thể thấy khi khởi động máy trong các thông số BIOS hiện ra.

Port Address
Ghi chú
3BCh - 3BFh
dùng cho prallel port vốn dính vào Video Card (cách cũ)
378h - 37Fh
khu vực memory thường dùng cho LPT 1
278h - 27Fh
khu vực memory thường dùng cho LPT 2 ....

(nên nhớ là mỗi khoản là 8 bits, tính theo hệ hexadecimal) Một điều tôi muốn nhắc các bạn là những thông tin đưa ra trên đây thường là thay đổi tùy theo từng máy tính, một máy có thể có nhiều LPT, thông thường thì BIOS sẽ dò xem trong máy có bao nhiêu port và sẽ qui định địa chỉ cho từng port. Theo tôi thường thấy thì nếu máy bạn có hai cái parallel port (nếu bạn mua motherboard có hai parallel port) thì LPT1 sẽ được gán vào điạ chỉ 378h-37Fh (8 bits). Nếu có LPT2 thì sẽ được gán vào địa chỉ 278h-27Fh. Riêng phần 3BCh-3BFh trong quá khứ thường được dùng khi parallel port cài sẵn trong video card. Những loại nầy đã củ rồi, cho nên nhiều BIOS sẽ gán vào LPT1 cũng không chừng. Ðiều tốt nhất là bạn vào BIOS kiểm tra là biết ngay (life was not meant to be easy!).

Cũng lưu ý các bạn các điạ chỉ trên là port address qui định trên BIOS, khi BIOS qui định những địa chỉ trên (tắt máy vẫn còn) nó sẽ qui định kèm theo điạ chỉ lưu thông tin (tắt máy sẽ mất) cho từng port. Những địa chỉ dưới đây sẽ được dùng đa số bởi các bạn lập trình để kiểm tra xự hiện diện của parallel port trên máy bạn.

Start Address
Function
Software Identify
0000:0408h

LPT1's Base Address

Base
0000:040Ah

LPT2's Base Address

Base + 1
0000:040Ch

LPT3's Base Address

Base + 2
0000:040Eh

LPT4's Base Address (note 1)

Base + 3

Hãy lấy một ví dụ đơn giản về một chương trình kiểm tra vị trí parallel port trên máy bạn để minh hoạ cho lập trình điều khiển parallel port dưới đây:

#include #include void main(void) { /* Pointer to location of Port Addresses */ unsigned int far *ptraddr; /* Address of Port */ unsigned int address; int a; ptraddr = (unsigned int far *)0x00000408; for (a = 0; a < 3; a++) { address = *ptraddr; if (address == 0) printf("No port found for LPT%d \n", a+1); else printf("Address assigned to LPT%d is %Xh\n", a+1, address); *ptraddr++; } } Chương trình C đơn giản này là tìm đến điạ chỉ 0000:0408h và dò tìm xem có port nào tồn tại hay không. Nó sẽ dò từ 0000:0408h đến 0000:040Dh để kiểm tra LPT1, LPT2, và LPT3. Bạn nên nhớ mổi base address sẽ có 2 bytes (sizeof(unsigned int) = 2 bytes!). Như tôi mô tả phía trên, Base+1, Base+2...chẳng qua là lối gọi phổ thông trong cách lập trình. Chẳng hạn bạn kiểm tra nhà số 10, 11, 12. Bạn có thể kiểm tra nhà số 10 sau đó nhà số 10 +1 = 11 vân vân...

Ứng dụng của việc điều khiển parallel port Việc hiểu hoạt động và biết điều khiển parallel port là tối cần thiết cho các bạn đi chuyện sâu trong các kỹ nghệ hardware. Hầu hết các dụng cụ tân tiến thời nay điều liên quan đến việc dùng software để vận hành hardware, ví dụ như bạn có thể gỡi một lệnh từ máy vi tính làm cho tên lửa phóng đi, shutdown computer...điều thuộc dạng software điều khiển hardware. Và với trách nhiệm một người điều khiển nó, bạn phải thấu hiểu tất cả. Một ví dụ đơn giản khác trong điều khiển học như điều khiển robot, nếu bạn dùng software từ máy vi tính kích hoạt một pin nào đó của cổng parallel và gỡi tới robot như mệnh lệnh, chẳng hạn đi tới phía trước, quay qua bên trái....

Cấu trúc đào sâu bên trong của Parallel port Dưới đây là liệt kê 25 chân của parallel port với tên gọi (hardware và software) và thứ tự của từng chân.

Chân

tên signal (dùng cho hardware)

Direction/type (nhìn từ PC)

Tên signal và thứ tự của bit (dùng cho software)

Normal signal line function
1 -STROBE OC/Pullup Control register bit 0 kích hoạt thông báo gỡi hoặc nhận data, 0 là đọc, 1 là viết
2 D0 hai chiều Data register bit 0 bit 0 chứa data
3 D1 hai chiều Data register bit 1 bit 1 chứa data
4 D2 hai chiều Data register bit 2 bit 2 chứa data
5 D3 hai chiều Data register bit 3 bit 3 chứa data
6 D4 hai chiều Data register bit 4 bit 4 chứa data
7 D5 hai chiều Data register bit 5 bit 5 chứa data
8 D6 hai chiều Data register bit 6 bit 6 chứa data
9 D7 hai chiều Data register bit 7 bit 7 chứa data
10 -ACK Input Status register bit 6 Pulsed low by printer to acknowledge data byte Rising (usually) edge causes IRQ if enabled
11 BUSY Input Status register bit 7 kích hoạt khi printer đang bận (busy)
12 NOPAPER Input Status register bit 5 kích hoạt khi printer hết giấy
13 SELECTED Input Status register bit 4 kích hoạt khi printer đang hoạt động
14 -AUTOFEED OC/Pullup Control register bit 1 kích hoạt thông báo data đã sẵn sàng để đọc hoặc viết
15 -ERROR Input Status register bit 3 kích hoạt khi printer bị lổi (vì nhiều lý do)
16 -INITIALIZE OC/Pullup Control register bit 2 kích hoạt để printer reset lại vị trí ban đầu
17 -SELECT OC/Pullup Control register bit 3 kích hoạt để đánh dấu printer nhận được valid address
18 Ground
... Ground chân (18-25) bỏ trống, dùng tùy ý
25 Ground

Thêm một hình minh hoạ tổng thể hoạt động của parallel port.

Làm sao để truyền dữ liệu tới parallel port bằng software đây? Chắc các bạn học lập trình không xa lạ gì với lệnh outport() inport() của C. Nếu bạn viết outportb(0x378,0xff); trong một chương trình C thì nó sẽ gởi giá trị 0xff (hệ hexa) = 128 (hệ decimal) = 11111111 (hệ binary) ra data register của parallel port (LTP1). Theo giá trị vừa tính thì "11111111" nghĩa là 8 chân của data register trên parallel port sẽ có dòng điện 5volts hiện hữu. Nếu bạn là dân hardware, bạn có thể đo dòng điện tại các chân của parallel port (nên nhớ là với high frequency). Riêng với các bạn software, một khi đã hiểu sự hoạt động của cổng parallel port, bạn có thể hiểu rõ hơn cách hoạt động của các driver software.

Thứ Ba, 6 tháng 1, 2009

Thuộc tính của .NET

Thuộc tính là một trong những khái niệm quan trọng nhất của .NET, nó ảnh hưởng đến nhiều phương diện khác nhau của một ứng dụng .NET như khả năng giao tiếp với các thành phần COM, khả năng tạo ra trình dịch vụ, tính năng bảo mật, tính năng lưu dữ liệu của đối tượng vào tập tin...

Thuộc tính là gì?

Sức mạnh của .NET (so với các đời trước) có được phần lớn là do ý tưởng về thông tin mô tả (metadata) đem lại. Chính những thông tin này đã giúp cho các assembly tự mô tả đầy đủ chính nó, nhờ đó việc giao tiếp và sử dụng lại các chương trình viết bằng những ngôn ngữ khác nhau cũng trở nên dễ dàng, hiệu quả hơn. Việc lập trình tất nhiên cũng đơn giản hơn! Làm sao cung cấp những thông tin này? Câu trả lời là: dùng thuộc tính.

Thuộc tính là những đối tượng chuyên dùng để cung cấp thông tin mô tả cho các phần tử trong một assembly .NET. Phần tử ở đây bao gồm assembly, lớp, các thành viên của lớp (gồm hàm tạo, hàm thuộc tính, trường, hàm chức năng, tham biến, giá trị trả về), sự kiện.

Cách sử dụng thuộc tính trong C#

Có một số qui tắc bắt buộc phải tuân theo khi dùng thuộc tính để viết mã chương trình:

• Thuộc tính phải đặt trong dấu ngoặc vuông.

Ví dụ: Khi bạn tạo ra một ứng dụng loại Console trong VS.NET IDE, bạn sẽ thấy hàm Main được áp dụng thuộc tính STAThread như sau:

[STAThread]

static void Main(string[] args){

...

}

• Tên các lớp thuộc tính thường có đuôi là "Attribute" nhưng bạn có thể không ghi đuôi này.

Ví dụ: Hãy thử đổi [STAThread] thành [STAThreadAttribute] và biên dịch chương trình. Bạn sẽ thấy không có lỗi gì xảy ra.

• Thuộc tính có thể có nhiều biến thể ứng với nhiều bộ tham biến khác nhau. Khi cần truyền tham số cho thuộc tính, ghi chúng trong cặp ngoặc đơn. Riêng đối với biến thể không tham biến, có thể ghi hoặc không ghi cặp ngoặc rỗng "()". Ngoài ra, các tham số phải là các biểu thức hằng, biểu thức typeof hay biểu thức tạo mảng (như new Type[]{typeof(TargetException)}).

Ví dụ 1: Có thể thay [STAThread] bằng [STAThread()].

Ví dụ 2: Khi cần đánh dấu một lớp, hàm là "đã cũ, cần dùng phiên bản thay thế", ta có thể dùng thuộc tính ObsoleteAttribute. 1 trong 3 biến thể của thuộc tính này là:

[Obsolete(string message, bool error)]

trong đó: message dùng để cung cấp thông tin chỉ dẫn về lớp, hàm thay thế. error dùng để hướng dẫn cho trình biên dịch biết cần làm gì khi biên dịch lớp, hàm sử dụng phần tử được áp dụng Obsolete. Nếu error bằng true, trình biên dịch báo lỗi và không biên dịch. Ngược lại, trình biên dịch chỉ cảnh báo và vẫn biên dịch bình thường.

Như vậy, ta có thể sử dụng như sau:

[Obsolete("Nên dùng lớp NewClass", false)]

public class OldClass{

...

}

// lớp này không được áp dụng thuộc tính Obsolete

public class ClientClass{

private OldClass a = new OldClass();

...

}

Khi biên dịch lớp ClientClass, VS.NET IDE sẽ thông báo ở cửa sổ Task List như hình 1:

Hình 1
 

Nếu bạn sửa false thành true thì bạn sẽ thấy bảng báo lỗi như hình 2:

Hình 2
 

Ví dụ 3: không thể dùng:

private string s = "Nên dùng lớp NewClass";

[Obsolete(s, false)]

nhưng nếu thêm const vào phần khai báo của s thì hợp lệ.

• Thuộc tính có mục tiêu áp dụng (do người viết ra thuộc tính qui định) xác định nên vị trí đặt cũng bị hạn chế. Nói chung, thuộc tính phải đặt trước mục tiêu áp dụng và không thể đứng bên trong thân hàm. Nếu thuộc tính có nhiều mục tiêu áp dụng được thì có thể chỉ định mục tiêu cụ thể bằng một trong các từ khoá: assembly, module, type, event, field, property, method, param, return.

Ví dụ:

[assembly: AssemblyTitle("Demo")] // Đúng chỗ

namespace Demo;

[assembly: AssemblyTitle("Demo")] // Sai chỗ

[type: Obsolete] // Đúng chỗ

// [method: Obsolete] // Sai chỗ

public class OldClass{

[type: Obsolete] // Sai chỗ



}

}

• Thuộc tính có thể đặt trong các cặp ngoặc vuông liên tiếp nhau hay đặt trong cùng một cặp ngoặc vuông nhưng cách nhau bởi dấu phẩy.

Ví dụ:

[type: Obsolete("Nên dùng lớp NewClass", false),Serializable]

tương đương với

[type: Obsolete("Nên dùng lớp NewClass", false)]

[Serializable]

• Có những thuộc tính có thể được áp dụng nhiều lần cho cùng một mục tiêu. Điều này cũng do người viết ra thuộc tính qui định.

Ví dụ 1:

// Trình biên dịch sẽ báo lỗi "Duplicate Obsolete attribute"

[type:Obsolete]

[type:Obsolete]

public class OldClass{

...

}

Ví dụ 2:

// Trình biên dịch không báo lỗi

// Thuộc tính ExpectedException ở đây là thuộc tính custom mà ta sẽ tự tạo trong phần 5-

[type: ExpectedException( typeof(xxxException) )]

[type: ExpectedException( typeof(xxxException) )]

public class OldClass{
...

}

• Một số thuộc tính có tính kế thừa. Khi bạn áp dụng những thuộc tính này cho một lớp nào đó, hãy nhớ là các lớp con của lớp đó cũng mặc nhiên được áp dụng các thuộc tính đó. Bạn sẽ thấy rõ điều này trong phần "Tạo một thuộc tính custom".

• Cuối cùng, khi sử dụng thuộc tính nào, nhớ tạo ra tham chiếu tới không gian kiểu chứa nó. Chẳng hạn như, để dùng các thuộc tính như AssemblyTitle, AssemblyVersion, cần thêm:

using System.Reflection;

Đặc điểm của thuộc tính

1. Khi thêm thuộc tính vào mã chương trình, ta đã tạo ra một đối tượng mà các thông tin của nó sẽ được lưu vào assembly chứa mục tiêu áp dụng của thuộc tính. Tùy theo thuộc tính thuộc loại custom hay p-custom (p- là pseudo) mà những thông tin này sẽ được lưu thành chỉ thị .custom hay khác (.ver, .hash, serializable,... ) trong tập mã IL.

Ví dụ: lớp OldClass sau sẽ có mã IL (xem bằng ILDasm.exe) như hình 3:

[Obsolete("Nen dung lop NewClass", false)]

[Serializable]

public class OldClass{


}

Hình 3
 

• Tuy được lưu trong assembly nhưng thuộc tính hoàn toàn không ảnh hưởng gì đến các mã lệnh khác. Thuộc tính chỉ có ý nghĩa khi có một chương trình nào đó cần đến và truy xuất nó thông qua tính năng Reflection của .NET. Dĩ nhiên, ý nghĩa của thuộc tính sẽ do chương trình đó qui định. Điều đó cũng có nghĩa là cùng một thuộc tính nhưng "dưới mắt" các chương trình đọc khác nhau sẽ có thể có công dụng khác nhau. Đây là đặc điểm đáng chú ý nhất của thuộc tính.
Ví dụ: thuộc tính Obsolete được trình biên dịch dùng để phát hiện những phần tử sẽ không được sử dụng nữa, [TestFixture] được NUnit dùng để chọn những lớp có chứa hàm kiểm tra cần được kích hoạt tự động,...

• Dữ liệu chỉ định trong thuộc tính gắn chặt với mục tiêu áp dụng của thuộc tính chứ không lỏng lẻo và do đó không linh hoạt như dữ liệu trong tập tin cấu hình. Nhờ vậy, dữ liệu mô tả lưu bằng thuộc tính an toàn hơn, khó sửa hơn.

• Thuộc tính còn có những đặc điểm khác như: có mục tiêu áp dụng xác định, có khả năng áp dụng nhiều lần cho cùng một mục tiêu, có thể được thừa kế.

Một số ví dụ minh họa ứng dụng của thuộc tính

a - Thuộc tính CLSCompliant:


Mục tiêu của .NET là tạo ra một nền tảng giao tiếp thống nhất giữa nhiều ngôn ngữ lập trình khác nhau. Để đạt được điều đó, .NET định ra 2 chuẩn là CTS và CLS, trong đó CTS bao gồm các kiểu cơ bản mà một ngôn ngữ .NET có thể chọn hỗ trợ còn CLS là một tập các qui tắc bắt buộc mọi ngôn ngữ .NET phải áp dụng cho các phần tử dùng để giao tiếp với nhau. Như vậy, một ngôn ngữ có thể hỗ trợ những kiểu mà ngôn ngữ khác không hỗ trợ. Kết quả là khi các ngôn ngữ muốn phối hợp với nhau thì những kiểu "không chung" này sẽ "phá đám", gây ra hiểu nhầm. Để tránh tình huống này, .NET tạo ra thuộc tính CLSCompliant dùng để nhờ trình biên dịch theo dõi và cảnh báo xem có phần tử nào vi phạm luật CLS hay không. Thuộc tính này có mục tiêu áp dụng là mọi phần tử.

Ví dụ:

// Kiểm tra xem mọi phần tử của assembly này có tương thích CLS không

[assembly: CLSCompliant(true)]

namespace Demo{

// Riêng: bỏ qua các phần tử của lớp này

[type: CLSCompliant(false)]

public class A{

private uint a;

public uint b;

}

public class B{

private uint a; // không bị coi là vi phạm vì có tầm vực private

public uint b; // vi phạm

}

}

b - Các thông tin mô tả về assembly:

Khi sử dụng VS.NET IDE để tạo một dự án, bạn sẽ thấy là luôn có một tập tin tên AssemblyInfo.xx (tùy theo ngôn ngữ, xx có thể là cs với C#, vb với VB.NET,...). Sau đây là nội dung tập tin AssemblyInfo.cs đã lược bỏ phần chú thích:

using System.Reflection;

using System.Runtime.CompilerServices;

[assembly: AssemblyTitle("")]

[assembly: AssemblyDescription("")]

[assembly: AssemblyConfiguration("")]

[assembly: AssemblyCompany("")]

[assembly: AssemblyProduct("")]

[assembly: AssemblyCopyright("")]

[assembly: AssemblyTrademark("")]

[assembly: AssemblyCulture("")]

[assembly: AssemblyVersion("1.0.*")]

[assembly: AssemblyDelaySign(false)]

[assembly: AssemblyKeyFile("")]

[assembly: AssemblyKeyName("")]

(Lưu ý: Có thể bạn ngộ nhận tập tin trên là bắt buộc phải có. Nhưng không, nó chẳng qua là một công cụ mà VS.NET cung cấp, giúp bạn tập trung các thông tin chung về assembly lại một chỗ. Bạn hoàn toàn có thể xóa bỏ tập tin trên và tạo lại các mục tương tự nhưng để rải rác ở các tập tin trong dự án.)

Như bạn thấy, tập tin trên chỉ chứa toàn các thuộc tính với mục tiêu áp dụng là assembly. Những thuộc tính ấy nằm trong 2 không gian kiểu System.Reflection và System.Runtime.CompilerServices. 8 thuộc tính đầu dùng để cung cấp các thông tin chung về assembly (có thể xem những thông tin này bằng ILDasm.exe hay Windows Explorer). AssemblyVersion dùng để ghi nhận số phiên bản cho assembly, số này sẽ được CLR cần đến. Cụ thể là nếu assembly A tham chiếu đến assembly B thì trong assembly A sẽ ghi nhận phiên bản của B mà A tham chiếu. Nhờ đó, khi CLR cần tải B để hỗ trợ cho A thì CLR có thể biết được và tải đúng phiên bản thích hợp của B.

AssemblyKeyFile dùng để chỉ định tập tin chứa cặp khóa chung/riêng mà trình biên dịch sẽ dựa vào để tạo ra assembly duy nhất. Nếu không dùng AssemblyKeyFile thì có thể dùng AssemblyKeyName thay thế, chỉ khác là cần chỉ định tên của khóa đã được cài đặt vào Crypto Service Provider trên máy. Cũng có thể dùng cùng lúc cả 2 thuộc tính để chỉ định khóa; khi ấy, AssemblyKeyName sẽ được ưu tiên dùng trước.

Cuối cùng, AssemblyDelaySign dùng để yêu cầu trình biên dịch tạo ra một assembly giả duy nhất (vì chỉ cần dựa vào khóa chung) giúp cho việc thử nghiệm dễ dàng hơn. Đến khi cần triển khai ứng dụng thực sự mới phải dùng khóa riêng để tạo ra assembly duy nhất. Nhờ có AssemblyDelaySign, khóa riêng có thể được giữ bí mật bởi một người nào đó mà không làm ảnh hưởng đến quá trình phát triển phần mềm chung của cả nhóm.

Tạo một thuộc tính custom

Trong các phần trước, chúng ta đã sử dụng các thuộc tính có sẵn của .NET. Trong phần này, chúng ta sẽ tìm hiểu cách tự tạo lấy các thuộc tính cho riêng mình "xài" thông qua quá trình xây dựng thuộc tính ExpectedException.

Cũng như những thuộc tính custom có sẵn, thuộc tính tự tạo của chúng ta phải là một lớp con của lớp System.Attribute:

// Theo qui ước, tên thuộc tính nên có đuôi là Attribute

public class ExpectedExceptionAttribute : System.Attribute{

...

}

Thuộc tính tự tạo có thể có các hàm tạo và hàm thuộc tính như một lớp thông thường:
...

private Type expected = null;

private string msg = "";

public ExpectedExceptionAttribute(Type expectedType):this(expectedType, ""){}

public ExpectedExceptionAttribute(Type expectedType, string message){

if (expectedType as Exception == null)

throw ...

expected = expectedType;

msg = message;

}

public Type ExpectedType{

get{

return expected;

}

}

public string Message{

get{

return msg;

}

set{

msg = value;

}

}
...

Khi sử dụng, các tham biến của hàm tạo trở thành các tham số vị trí (tức là bắt buộc có và được truyền theo đúng thứ tự khai báo), còn các hàm thuộc tính trở thành tham số có tên (tức không bắt buộc có và có thể được truyền theo thứ tự tùy ý, miễn là phải sau các tham số vị trí). Sau đây là một số cách dùng hợp lệ:

[ExpectedException(typeof(Exception))]

[ExpectedException(typeof(Exception), "Expected type: System.Exception")]

[ExpectedException(typeof(Exception), Message="Expected type: System.Exception")]

[ExpectedException(typeof(Exception), "Expected type: System.Exception"), Message="Expected type: System.Exception")]

Thuộc tính của ta chỉ cần áp dụng cho hàm tạo, hàm chức năng, hàm thuộc tính. Do đó, ta cần chỉ định mục tiêu áp dụng cho nó thông qua thuộc tính AttributeUsage như sau:

[AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property)]

public class ExpectedExceptionAttribute:System.Attribute{...}

Mặt khác, một hàm có thể phát ra nhiều lỗi khác nhau, tức là thuộc tính ExpectedException có thể áp dụng nhiều lần cho cùng một mục tiêu. Ta chỉ định thêm:

[AttributeUsage(..., AllowMultiple = true)]

Cuối cùng, ta muốn rằng nếu các hàm virtual của lớp A được áp dụng thuộc tính ExpectedException thì các hàm override tương ứng của lớp con của A cũng kế thừa thuộc tính này. Do đó ta thêm:

[AttributeUsage(..., ..., Inherited = true)]

Xin lưu ý là chỉ khi cả AllowMultiple và Inherited đều bằng true thì lớp con mới được kế thừa toàn bộ thuộc tính với cùng giá trị đã áp dụng cho lớp cha.

Đến đây coi như ta đã hoàn tất phần định nghĩa thuộc tính. Ta đặt thuộc tính vừa tạo vào assembly tên DemoAttrLib.dll. Tiếp đến, ta xây dựng một chương trình sử dụng ExpectedExceptionAttribute. Ta đặt chương trình này trong assembly DemoAttrClient.exe.

/* Chương trình này gồm 2 lớp là DemoParentClient và DemoChildClient */

using System;

using System.Reflection;

using DemoAttrLib;

namespace DemoAttrClient

{

public class DemoParentClient

{

[method:ExpectedException(typeof(TargetException))]

public DemoParentClient(){...}

[method:ExpectedException(typeof(ArgumentException))]

[method:ExpectedException(typeof(TargetException))]

public void TestMethod1() {...}

[method:ExpectedException(typeof(TargetException))]

public virtual void TestMethod2() {...}

}

class DemoChildClient:DemoParentClient

{

[method:ExpectedException(typeof(ArgumentException))]

public override void TestMethod2() {...}

[method:ExpectedException(typeof(ArgumentException))]

public new void TestMethod1() {...}

}

}

Như đã nói, một thuộc tính chỉ có giá trị khi một chương trình nào đó dùng đến nó. Chương trình này sẽ dùng các lớp trong không gian kiểu System.Reflection để kiểm tra các thuộc tính đi kèm từng phần tử trước khi ra quyết định xử lý thích hợp đối với phần tử đó. Dưới đây là một ví dụ đơn giản về chương trình như thế (trong assembly DemoAttrReader.exe):

/* Đây là chương trình loại Console dùng để liệt kê các hàm được áp dụng thuộc tính ExpectedException trong assembly chỉ định. */

using System;

using System.Reflection;

using DemoAttrLib;

namespace DemoAttrReader

{

class DemoReader

{

// Hàm này trả về một chuỗi chứa thông tin báo cáo về mọi hàm được áp dụng thuộc tính ExpectedException trong assembly chỉ định.
public static string Read(string assemblyName)

{...}

// Hàm này trả về một chuỗi chứa thông tin báo cáo về mọi hàm được áp dụng thuộc tính ExpectedException trong kiểu chỉ định.
private static string AttrRead(Type t)

{...}

[STAThread]

static void Main(string[] args)

{

if (args.Length != 1)

{

Console.WriteLine("Hay chi dinh mot assembly nao do.");

return;

}

Console.WriteLine("BAO CAO:");

Console.WriteLine(DemoReader.Read(args[0]));

}

}

}

Kết quả chạy chương trình như ở hình 4- (chú ý là có tới 2 hàm TestMethod1 đối với lớp DemoChildClient):

Hình 4
 

Các bạn có thể tải mã nguồn của phần này (DemoAttr.rar) trên website www.pcworld.com.vn.

Nếu bạn muốn có một ví dụ phức tạp hơn, mời bạn tham khảo mã nguồn của NUnit (www.nunit.org), chương trình kiểm tra tự động khá thông dụng với các lập trình viên .NET. Cách hoạt động của NUnit đơn giản là: dò trong assembly chỉ định những lớp nào có thuộc tính TestFixture và kích hoạt những hàm được đánh dấu bằng thuộc tính Test, SetUp, TearDown,... trong các lớp ấy.

Vậy là chúng ta đã cơ bản tìm hiểu xong về thuộc tính của .NET. Hy vọng những điều vừa trình bày sẽ giúp ích cho các bạn trong công việc lập trình của mình.

Nguyên Phương

Thứ Năm, 18 tháng 12, 2008

Bios Setup

Vào Bios Setup
Đối với vài Bios thông dụng, ta có những cách để vào Bios Setup như sau:
Bios: Chuỗi phím
Ami: Phím Del
Award: Ctrl+Alt+Esc
Dtk: Esc
Phoenix: Ctrl+Alt+Esc
hay Ctrl+Alt+S
Sony: F3
Compaq: F10
Phoenix-Toshiba: F2
Đối với những Bios lạ, hay nếu không nhớ chuỗi phím quy định, bạn có thể ép buộc máy phải vào Bios Setup bằng cách cố tình tạo ra các trục trặc về phần cứng như: tháo bớt RAM, tháo rời dây cáp tín hiệu ổ mềm, ổ cứng... Điều này gây ra lỗi cấu hình trong Bios nên máy sẽ yêu cầu (và hướng dẫn) bạn vào Bios Setup để xác lập lại.
Xoá CMOS
- Ngưng cấp điện cho máy, tháo pin “nuôi” CMOS, để một thời gian cho các linh kiện trên mainboard xả hết điện, CMOS sẽ bị xoá.
- Nhiều mainboard có jumper (cầu nối) để xoá CMOS, nên bạn chỉ cần ngắt điện rồi nối hai chân này lại (nhớ xem sách hướng dẫn hay các ghi chú trên mainboard để tránh nối sai jumper, vì rất nguy hiểm).
Chú ý: Đối với loại mainboard có jumper xoá mật khẩu và xoá CMOS riêng, bạn bắt buộc phải xoá mật khẩu trước rồi mới xoá các thông tin khác.
- Có Mainboard xoá CMOS bằng cách bấm và giữ phím f hay h hay j trong thời gian khởi động máy.
Hai loại mật khẩu trong BIOS Setup
Đối với loại BIOS chia ra hai xác lập mật khẩu riêng cho Supervisor và User, bạn nên chú ý một đặc điểm là nếu bạn nhập mật khẩu của người dùng (user) khi vào BIOS Setup, màn hình của BIOS sẽ thiếu một số chức năng hay việc hiển thị sẽ không đầy đủ như khi bạn nhập mật khẩu (của supervisor). Do khi vào BIOS Setup chương trình chỉ yêu cầu bạn nhập mật khẩu mà không thông báo rõ là “Supervisor” hay “User” nên đặc điểm này có thể khiến nhiều người lầm tưởng là BIOS bị hỏng.
BIOS bị hỏng sau khi nâng cấp
Khi nâng cấp BIOS, nếu nửa chừng bị cúp điện hay chương trình cập nhật BIOS không chính xác sẽ làm hỏng BIOS và máy tính không thể khởi động được nữa. Bạn không có cách nào khắc phục ngoài cách đem ra cho thợ chuyên nghiệp phục hồi lại BIOS.
Tuy vậy, nếu bạn là người thích “vọc” và can đảm, e-CHÍP xin bày cho bạn một cách tự làm như sau:
- Chuẩn bị đĩa mềm khởi động có đúng chương trình cập nhật cho BIOS.
- Tìm một mainboard bất kỳ cùng đời CPU và cho phép cập nhật BIOS (không cần cùng hiệu hay cùng BIOS) đang khởi động tốt.
- Sau khi khởi động bằng đĩa mềm đã chuẩn bị, cẩn thận gỡ CMOS ra khỏi mainboard (trước khi khởi động bạn có thể “nhóm” CMOS lên cho dể tháo gỡ). Gắn CMOS đã bị hỏng vào rồi chạy chương trình cập nhật BIOS như bình thường.
- Tắt máy và trả các CMOS về đúng mainboard.
- Khởi động máy lại như bình thường.
Chú ý: Khi tháo hay gắn CMOS, bạn phải nhớ vị trí chân số một vì rất dễ gắn ngược đầu CMOS nếu sơ ý.
Pin nuôi CMOS hết?
Thông thường khi pin nuôi CMOS hết, máy vẫn có thể khởi động được nhưng bạn phải vào BIOS Setup để xác lập lại các thông số cần thiết. Tuy nhiên, có một số mainboard của nhiều hãng sản xuất không thể khởi động được khi hết pin nuôi CMOS. Trường hợp này có thể khiến các kỹ thuật viên chưa từng trải dễ kết luận nhầm là “hư mainboard”.