Saturday, October 31, 2009

WINDOWS API 2 - Hiểu rõ cách làm việc của Hệ điều hành Windows

Khi nắm rõ bản chất của Windows API để lập trình sâu với hệ thống, ta cũng cần hiểu biết sơ bộ về Hệ điều hành Windows cách thức điều khiển của Hệ điều hành đối với ứng dụng để có thể can thiệp như bổ sung chức năng thậm chí biến đổi nó, bắt thực hiện theo hướng của mình, ngay cả khi hướng này ngược hẳn với công dụng truyền thống !!!

Các chương trình ứng dụng trong Windows có thể có nhiều cửa sổ phục vụ cho nó. Cửa sổ có thể là Form thậm chí là Dialog. Mỗi cửa sổ này đều có một handle (Cán) để hệ thống nhận biết do chính hệ điều hành Windows tạo ra. Cán cửa sổ này là chỉ số duy nhất.

Hệ điều hành và chương trình ứng dụng đều duy trì các hàng đợi các chỉ lệnh cần thực hiện. Mỗi ứng dụng đều có hàng đợi (Message Queue). Khi người sử dụng ra lệnh hoặc có một biến cố, các chương trình điều khiển thiết bị nhập (INPUT) sẽ chuyển các thông tin vào thành chỉ lệnh và đặt chỉ lệnh này vào hàng đợi hệ thống (System Message Queue). Hệ điều hành lấy lần lượt các chỉ lệnh trong hàng đợi hệ thống kiểm tra để xác định cửa sổ nào sẽ tiếp nhận thi sẽ đặt vào hàng đợi của nó (thread message) một chỉ lệnh tương ứng. Các chương trình ứng dụng căn cứ vào chỉ lệnh này để thực hiện cũng như xử lý chúng.

Các cửa sổ giống như một động cơ tự động chạy theo một vòng lặp. Tiếp "nhiên liệu" cho các "động cơ" này là hệ điều hành Windows. Hệ điều hành Windows nhận các chỉ lệnh (message) từ hàng đợi của hệ điều hành, dùng một hàm dạng API (Như kỳ 1 - Hàm này bản chất là một thủ tục) để cung cấp chỉ lệnh tới cửa sổ thông qua cán (handle) của cửa sổ.

Có nghĩa là bản thân trong mỗi cửa sổ luôn có một hàm gọi là WinProc (Đôi khi gọi là WinMain()). Hàm này là cốt lõi xử lý của cửa sổ. Trong hàm, nó lặp đi lặp lại liên miên 2 dòng lệnh sau thông qua cấu trúc:
Do While 0 <>GetMessage (message, 0, 0,0)
TranslateMessage message
DispatchMessage message
Loop

Trong đó message là chỉ lệnh mà Hệ điều hành cung cấp, thông qua cán (handle) của cửa sổ. Đương nhiên, nếu chỉ lệnh có giá trị WM_QUIT thì hàm WinProc trong cửa sổ chấm dứt vòng lặp.

Còn nếu chỉ lệnh message khác giá trị trên, thì 2 dòng lệnh trên sẽ thực hiện. Cụ thể:
TranslateMessage message -> Dịch chỉ lệnh thành dạng dữ liệu khác đặt kết quả này vào hàng đợi của ứng dụng.
DispatchMessage message ->Nhận chỉ lệnh từ hàm GetMessage và gửi cho hệ thống. Hệ thống sẽ đưa chỉ lệnh cho ứng dụng.

Windows có hàng ngàn chỉ lệnh khác nhau đó là các hằng dạng WM_* (Windows đặt tên cho tiện gọi thôi, vì bản chất các hằng này là một con số - Rất khó nhớ. Nếu phân tích chi tiết ra, những con số này lại là dãy số 0 và 1, tức là bật tắt ấy mà).
Một hàm WinProc luôn nhận vào trong nó các biến theo khuôn mẫu sau để xử lý:
Function WinProc(hwnd as Long, wc as WNDCLASSEX, message as MSG, wParam as Long, lparam as Long)

Nếu hàm WinProc không xử lý các chỉ lệnh, nó phải đưa trả chỉ lệnh cho hệ điều hành xử lý thông qua hàm DefWindowProc. Hàm DefWindowProc gởi lại chỉ lệnh WM_CLOSE cho WinProc. Hàm WinProc sẽ lại gởi trả WM_CLOSE cho DefWindowProc một lần nữa như mô tả ở trên.

Bạn có thể thấy, kết quả và cơ chế xử lý rất lằng nhằng. Ta có thể tóm lại sơ bộ như sau:

Các chỉ lệnh đưa tới ngăn chờ trên thông thường từ các nguồn sau:
1. Hệ thống đặt vào
2. Chương trình khác đặt vào
3. Chính chương trình của mình đặt vào thông qua các hàm SendMessage() và PostMessage().

Tuy nhiên nếu bạn chọn sử dụng hàm SendMessage() thì sau khi chỉ lệnh được WinProc lấy ra xử lý thì chương trình mới tiếp tục chạy tiếp lệnh kế sau. Còn bạn dùng PostMessage() chỉ có tác dụng đặt chỉ lệnh vào hàng đợi và thực hiện ngay lệnh kế tiếp.

Từ đây ta nhận thấy việc xử lý hệ thống của Windows thông qua cơ chế trên thì giản đơn đi rất nhiều. Ta sẽ có các hướng giải quyết tiếp theo như sau:
1. Nếu ta thiết kế một hàm WinProc() mới, Ví dụ NewWinProc(), thay thế hàm WinProc() truyền thống
- Rồi ta đổi địa chỉ của WinProc() gốc sang địa chỉ của WinProc() mà ta thiết kế. Trong thủ tục này sử dụng cấu trúc Select Case để tuỳ vào chỉ lệnh message mà xử lý theo ý mình.
- Trường hợp chỉ lệnh message nào không thể viết được thủ tục thi hành (Không cần thay đổi hoặc khó quá) thì ta gọi thủ tục DefWinProc() của Windows xử lý. (Tức là dễ thì làm, khó trả lại ấy mà). Đây chính là Kỹ thuật Subclass.
- Tuy nhiên nếu như hàm WinProc() cũ, xử lý tốt một số tính năng nào đó thì ta có thể tận dụng thủ tục bằng cách gọi lại bằng hàm CallWindowProc().
- Thậm chí ta có thể lồng WinProc() vào trong NewWinProc() để xử lý
(Xem các bài ví dụ của Nguyễn Phương Thảo có sử dụng API).

2. Trường hợp không thể thay được hàm WinProc() bằng NewWinProc() (Ví dụ như bạn lập trình với các chương trình ứng dụng khác như Winword, Excel...) bạn phải chặn các chỉ lệnh trước khi nó được lấy ra khỏi hàng đợi. Đó chính là Kỹ thuật Hooking, một Kỹ thuật cực kỳ mạnh để làm việc với Windows.

Trước khi chỉ lệnh được hàm SendMessage() lấy từ hàng đợi gửi đi, hoặc cũng có thể chỉ lệnh được lấy bằng hàm PickMessage() hay GetMessage(), ta có thể đăng ký với Windows để sử dụng bộ lọc HookFilter. Khi đó những chỉ lệnh cần xử lý đã đăng ký, đều qua HOOK Filter. Ta chỉ việc viết một thủ tục dùng hàm WinProc lấy chỉ lệnh từ HOOK Filter để xử lý. (Xem bài viết chặn các thủ tục in và nhập dữ liệu của Nguyễn Phương Thảo).

3. Các đặc điểm của lớp cửa sổ:

- Mỗi ứng dụng có thể tạo ra nhiều cửa sổ, thông thường các cửa sổ này có những đặc điểm giống nhau và được phân theo từng lớp CLASS. Khi lập trình, bao giờ ta cũng đăng ký lớp với hệ thống thông qua một hàm là RegisterClassEx(). Khi lớp đã được đăng ký (Chỉ 1 lần) thì các thông tin window và địa chỉ hàm WinProc sẽ được lưu trong suốt thời gian mà nó tồn tại.

- Ta có thể thay đổi các thông tin trong bộ đăng ký lớp, khi đó nó sẽ ảnh hưởng đến toàn bộ cửa sổ trong lớp này.

Bạn thân mến! Như vậy bạn đã nắm chắc về cơ chế làm việc của Windows và các thủ tục hệ thống của nó. Hi vọng bạn hãy đọc thật kỹ và hiểu rõ về nó, để những bài viết sau chúng ta sẽ mổ xẻ giải phẫu từ những hàm API cơ sở được dễ dàng hơn.

WINDOWS API 1 - Bản chất của Windows API

Trong lập trình Visual Basic độc lập hoặc Visual Basic for Application, Microsoft đã cung cấp cho chúng ta một bộ các hàm lập sẵn, hàng trăm hàm API (Aplication Programming Interface) được lưu trong các tệp thư viện liên kết động (Tệp đuôi *.DLL - Dynamic Link Library). Đó là công cụ tuyệt vời cho phép bạn phát triển ứng dụng cực mạnh, tại sao bạn lại bỏ qua và không sử dụng nó?

Tôi sẽ cùng bạn khám phá những gì mà Microsoft cung cấp các hàm Windows API trong bộ Visual Studio. Tuy nhiên vì khuôn khổ cũng như kích thước của bài viết, ta chỉ đi vào các nét chính căn bản nhất, bạn có thể tham khảo trong Help hoặc các bài viết của Nguyễn Hồ Thiên Đăng, Nguyễn Thị Thanh Phương tại WebsiteLH.

I. Hàm API - Nhìn từ góc độ người ít có điều kiện học Tin học

Nếu bạn chưa từng lập trình những chương trình lớn, bạn sẽ phát hoảng khi đọc khai báo (rắc rối và kỳ cục!!!) của API:

Private Declare Function CallNextHookEx Lib "user32" Alias "CallNextHookEx" (ByVal hHook As Long, ByVal ncode As Long, ByVal wParam As Long, lParam As Any) As Long

Tuy nhiên bạn có thể chép phần khai báo trên thật đơn giản bằng Text API Viewer. Theo các chuyên gia về Tin học thì đừng bao giờ sử dụng thẳng hàm API trong thủ tục thiết kế chính của mình. Thay vào đó ta thiết kế một hàm hay thủ tục Visual Basic thay thế hàm API để đơn giản hoá (Gọi là wrapper - Tôi không tìm được từ tiếng Việt tương ứng để nói đủ bản chất của nó.)

Ví dụ một wrapper sau:
Public Sub ThoatVaTatMay ()
'Thoát và tắt máy
Dim thoat
Thoat = ExitWindowsEX(2,0)
End Sub

Khi đó, trong chương trình Visual Basic của ta khi cần thoát và tắt máy chỉ việc gọi:
ThoatVaTatMay
hoặc Call ThoatVaTatMay
Là máy tính thực hiện thoát và tắt máy.

Chính vì thế, khi học viên dân tộc được học phổ cập API tại Trung tâm Dạy nghề và Phổ cập Tin học Miền núi ABC của chúng tôi đã gọi chức năng tạo Wrapper chẳng khác dán nhãn Tiếng Việt cho từng loại thuốc tây API vậy. Nên khi lập trình ta nên tạo các Wrapper tương ứng với các chức năng mà mình muốn sử dụng. Đó cũng là lời khuyên của Bill Gate cho chúng ta.

Để tạo các wrapper bạn hãy chèn một Module vào Project. Nếu máy của bạn không cài Visual Basic thì bạn phải copy hay đánh thật chính xác những dòng khai báo dạng như khai báo trên, thật khổ sở nếu như phần tiếng Anh của bạn không thạo lắm, vì một sai sót nhỏ có thể dẫn tới lỗi nặng cho máy. Nếu máy có cài Visual Basic thì quá tốt, chỉ việc khởi động API Viewer để hiện bảng giao tiếp như thế này:

Bạn phải mở các tệp TXT trong thư mục API của thư mục cài Visual Basic trong máy bạn. Ví dụ tệp Win32api.TXT. Máy sẽ hỏi có chuyển thành dạng cơ sở dữ liệu không, thì hãy chọn có để sử dụng API thuận lợi và nhanh chóng hơn. Khi đã copy vào clipboard, bạn có thể dán vào module của mình. Nó sẽ thành dạng tương tự như thế này:

Declare Function CallNextHookEx Lib "user32" Alias "CallNextHookEx" (ByVal hHook As Long, ByVal ncode As Long, ByVal wParam As Long, lParam As Any) As Long

Bạn phải đánh thêm vào trước khai báo trên cụm từ Private để được:
Private Declare Function CallNextHookEx Lib "user32" Alias "CallNextHookEx" (ByVal hHook As Long, ByVal ncode As Long, ByVal wParam As Long, lParam As Any) As Long

Tôi xin phép được nhắc lại 2 từ khoá khai báo trong Visual Basic là Private và Public.

Private - Khai báo dùng riêng trong Module. Có nghĩa là bạn chỉ sử dụng được nó trong Module này. Nếu chèn Module khác sẽ không sử dụng được nó.

Public - Khai báo dùng chung, bạn có thể dùng nó ở bất cứ Module nào.

Bạn biết đấy, ta khai báo các hàm API thì lại dùng Private, còn khai báo các Wrapper lại dùng Public. Đó chính là mẹo mà chúng ta đã học trộm được của Microsoft để tránh lỗi hệ thống.

Các hàm mà ta thiết kế trong Visual Basic có điều khác với các hàm API. Tại sao? Vì hàm ta thiết kế (Tạm gọi là hàm Visual Basic) thường chỉ có một kết quả trả về của hàm làm căn cứ xử lý. Còn hàm API không phải chỉ có một kết quả trả về mà nó còn trả về tiếp các giá trị vào các biến mà ta truyền cho nó. Nghĩa là có gọi nó là hàm thì bản chất nó là một thủ tục, ta gọi hàm để thực hiện và kiểm tra xem thủ tục trong hàm đó có thực hiện được thành công hay không mà thôi.

Điều này chỉ hơi giống như khi bạn lập trình với hàm Visual Basic mà bạn khai báo Public Static - Biến tĩnh dùng chung, để làm biến đổi nó trong hàm của bạn. Ta nghiên cứu một cách tổng quát như sau để hiểu cặn kẽ:

Giả sử ở một Wrapper bạn dùng công thức:
TENBIEN=TEN_HAM_API(Bien1, Bien2, Bien3)

Thực ra đây là một thủ tục. Nếu TENBIEN<>0 thì thủ tục này thành công.

Khi đó các Bien1, Bien2, Bien3 truyền vào, sẽ có một giá trị mới. Lập trình API lúc này không chỉ xử lý TENBIEN, mà bạn có thể xử lý các Bien1, Bien2, Bien3. Đó mới thực sự là sức mạnh của API.

Bản chất lập trình của biến là một vùng các ô nhớ trong RAM được đặt tên để tiện sử dụng. Tại một thời điểm biến chỉ có một giá trị duy nhất. Người ta có thể dùng biến này làm giá trị định vị hoặc kích thước lưu trữ cho biến kia. Các hàm API có thể được gọi nhiều lần để sử dụng kết quả trả về của các biến truyền Bien1, Bien2, Bien3 để xử lý theo quy luật xác định nào đó.

II. Xác định mục đích khi sử dụng WinAPI.

Trong lập trình API có thể phân làm nhiều mục đích sử dụng, tôi chưa từng được học Tin học một cách chính thống, nên có thể khả năng phân tích và tổng hợp những bài viết trên INTERNET của Microsoft khác với những bài học của những học viên học Tin học chính quy. Nhưng tôi nghĩ chúng ta nắm bản chất của vấn đề mới là điều quan trọng.

Có 3 vấn đề chính khi sử dụng và khai thác WinAPI đó là:

a. Kỹ thuật Subclass: Để cải tổ các đối tượng Visual Basic.
b. Kỹ thuật Hook: Câu móc từ chương trình Visual Basic với các chương trình khác. Lấy giá trị nhập vào các chương trình khác của người sử dụng đưa vào chương trình của mình để xử lý.
c. Kỹ thuật Multicasting: Dùng một đối tượng tạo lập để theo dõi, chi phối các đối tượng khác của Visual Basic.

Bạn có thể sử dụng từng Kỹ thuật hoặc cả 3. Tuy nhiên bước đầu chưa thạo, bạn hãy thực hiện từng Kỹ thuật một cho thành thạo. Sau khi kiểm soát được khả năng của mình, bạn sẽ đủ trình độ để sử dụng WinAPI để cải tổ Windows và máy tính.

Căn bản Unicode cho VB6 programmers

ISO (International Standard Organisation) 10646 là tiêu chuẩn quốc tế nhằm cung ứng đủ số ký tự để dùng cho mọi chữ của tất cả ngôn ngữ trên thế giới. Thay vì dùng 8 bits để biểu diễn chỉ 255 dấu hiệu, bây giờ người ta dùng đến 16 bits để có thể biểu diễn đến trên 65000 dấu hiệu.

Hãy tưởng tượng một bài viết có thể chứa nhiều ngôn ngữ cùng một lúc. Hay môt chương trình hiển thị các đề mục bằng ngôn ngữ địa phương, vì khi chương trình phát động nó nhận diện ra ngay là nó đang đuợc chạy ở xứ nào bằng cách đọc Locale từ Windows.

Một trong những ưu điểm của tiêu chuẩn nầy là sự cố gắng để xáp nhập các tiêu chuẩn 8 bit có sẵn để không cần phải thay đổi chúng. Thật ra ISO 10646 là tiêu chuẩn quốc tế chính thức, còn Unicode thì được Unicode Consortium (tập hợp đại diện các công ty Tin Học lớn) soạn ra. Nhưng cả hai tiêu chuẩn gần như y hệt nhau khi nói đến con số dùng để biểu diễn một chữ, con số nầy đuợc gọi là code point. Thí dụ như code point của chữ 1EA3, của chữ ơ01A1. Ðể biểu diễn một code point, tùy theo cách encoding, có khi ta cần 1, 2, 3 hay 4 bytes .v.v., mỗi byte dùng cho code point người ta không gọi là byte hay octet, mà lại gọi là code unit. Thỉnh thoảng, Unicode được cập nhật hóa, và ấn bản mới nhất hiện giờ là 3.0.1.

ISO 10646 định nghĩa hai bộ CCS (Coded Character Sets), UCS-2UCS-4. UCS-2 dùng 16 bits và là một phần nhỏ (subset) của UCS-4.
UCS-4 là một CCS dùng 31bits, chia thành 4 nhóm như sau:

7 bits 8 bits 8 bits 8 bits
1111111 11111111 11111111 11111111
Group Plane Row Cell


Cái Plane đầu tiên của UCS-4 với giá trị Group=0, Plane=0 cũng là chính UCS-2. Nó còn đuợc gọi là BMP (Basic Multilingual Plane).

Code points trong UCS thường được viết dưới dạng u+????, mà ???? là con số hexadecimal của code point. Characters có giá trị trong khoảng từ u+0021 đến u+007E thì giống như ASCII và các characters trong khoảng từ u+00A0 đến u+00FF thì giống như ISO 8859-1. Do đó rất dễ cho ta hoán chuyển giữa ASCII hay ISO 8859-1 với UCS.
Unicode (version 3.0.1) thì dùng 20bit subset của UCS-4 làm Coded Character Set.

Những Character Encoding Schemes

Có vài Character Encoding Schemes được đưa ra dùng: đó là UTF-8, UTF-16, UTF-16LE, và UTF-16BE. UTF là viết tắt chữ Unicode Transformation Format.

UTF-16

UTF-16 là một cách encoding dùng 20bit Unicode. Các characters trong BMP được diễn tả bằng cách dùng giá trị 16bit của code point trong Unicode CCS. Có hai cách để viết 16bit value trong một dòng (stream) 8bit . Có lẽ bạn đã nghe qua chữ endian. Big Endian có nghĩa là cho Most Significant Byte đi trước, tức là nằm bên trái - do đó ta có UTF-16BE. Còn Little Endian thì ngược lại, tức là Least Significant Byte đi trước - do đó ta có UTF-16LE. Thí dụ, giá trị 16bit của con số Hex1234 được viết là Hex12 Hex34 trong big endian và Hex34 Hex12 trong little endian.

Những characters không nằm trong BMP đuợc biểu diễn bằng cách dùng surrogate pair (cặp thay thế). Code points có giá trị từ u+D800 đến u+DFFF được dành riêng ra để dùng cho mục đích nầy. Trước hết, một code point có 20 bits được phân ra làm hai nhóm 10 bits. Nhóm Most Significant 10 bits đuợc mapped vào một giá trị 10bit nằm trong khoảng từ u+D800 đến u+DBFF. Nhóm Least Significant 10 bits đuợc mapped vào một giá trị 10bit nằm trong khoảng từ u+DC00 đến u+DFFF. Theo cách đó UTF-16 có thể biểu diễn được những characters Unicode có 20bits.

UTF-8

UTF-8 là một cách encoding để có tác dụng y như UCS-4 (cũng là UTF-16), chớ không phải có code point nào khác. UTF-8 được thiết kế cho upward-compatible với ASCII. UTF-8 không phải là chỉ dùng một byte, nhưng là dùng nhiều bytes - từ 1 đến 6 bytes.

Cách hoán chuyển UTF-16 qua UTF-8 và ngược lại đuợc làm theo bảng dưới đây. Trong Table dưới đây, những binary (Nhị phân) bits nằm bên trái của UTF-16 đuợc chuyển qua bên phải của UTF-8 theo cùng một màu như chỉ dẩn. Ta hãy thử lấy thí dụ chữ với code point, hay UTF-16, 1EA3. UTF-8 bytes của nó là E1BAA3. Nếu bạn chưa quen cách dùng số nhị phân hay Hex hãy đọc qua bài Hệ thống số nhị phân.

UCS-4 (UTF-16)

1 E A 3
0001 1110 1010 0011

UTF-8

1110 ???? 10?? ???? 10?? ????
1110 0001 1011 1010 10100011
E 1 B A A 3

UTF-16 (Nhị phân)

UTF-8 (Nhị phân)

00000000 00000000 00000000 0??????? 0???????
00000000 00000000 00000??????????? 110????? 10??????
00000000 00000000 ???????? ???????? 1110???? 10?????? 10??????
00000000 000????? ???????? ???????? 11110??? 10?????? 10?????? 10??????
000000?? ???????? ???????? ???????? 111110?? 10?????? 10?????? 10?????? 10??????
0??????? ???????? ???????? ???????? 1111110? 10?????? 10?????? 10?????? 10?????? 10??????

Vì chúng ta chỉ làm việc với một số tương đối ít Unicode code points ( tổng cộng 134 ký tự) và UTF-8, bạn có thể hoặc dùng một Look-up table dựa theo Bản đối chiếu, hoặc dùng hai Functions ToUTF8 và ToUTF16 với Listings như dưới đây để hoán chuyển từ encoding UTF-16 ra UTF-8, và ngược lại:

Function ToUTF8(ByVal UTF16 As Long) As Byte()
' Convert a 16bit UTF-16BE to 2 or 3 UTF-8 bytes
Dim bArray() As Byte
If UTF16 < &H80 Then
ReDim bArray(0) ' one byte UTF-8
bArray(0) = UTF16 ' Use number as is
ElseIf UTF16 < &H800 Then
ReDim bArray(1) ' two byte UTF-8
bArray(1) = &H80 + (UTF16 And &H3F) ' Least Significant 6 bits
UTF16 = UTF16 \ &H40 ' Shift UTF16 number right 6 bits
bArray(0) = &HC0 + (UTF16 And &H1F) ' Use 5 remaining bits
Else
ReDim bArray(2) ' three byte UTF-8
bArray(2) = &H80 + (UTF16 And &H3F) ' Least Significant 6 bits
UTF16 = UTF16 \ &H40 ' Shift UTF16 number right 6 bits
bArray(1) = &H80 + (UTF16 And &H3F) ' Use next 6 bits
UTF16 = UTF16 \ &H40 ' Shift UTF16 number right 6 bits again
bArray(0) = &HE0 + (UTF16 And &HF) ' Use 4 remaining bits
End If
ToUTF8 = bArray ' Return UTF-8 bytes in an array
End Function


Function ToUTF16(bArray) As Long
' Convert 2 or 3 UTF-8 bytes to a 16bit UTF-16BE
Dim IntUB
IntUB = UBound(bArray) ' Find out how many bytes UTF-8 takes
Select Case IntUB
Case 0 ' one byte UTF-8. Note that bArray starts with index=0
ToUTF16 = bArray(0) ' Use number as is
Case 1 ' two byte UTF-8
ToUTF16 = (bArray(0) And &H1F) * &H40 + (bArray(1) And &H3F)
Case 2 ' three byte UTF-8
ToUTF16 = (bArray(0) And &HF) * &H1000 + (bArray(1) And &H3F) * &H40 + (bArray(2) And &H3F)
End Select
End Function

Dưới đây là kết quả khi bạn click nút UTF-16 -->UTF-8 rồi click nút UTF-8 -->UTF-16:



Bạn có thể download chương trình Test hoán chuyển encodings giữa UTF-16 và UTF-8 để chạy thử.

BOM (Byte Order Mark)

Vì Unicode nói chung hổ trợ UTF-16BE, UTF-16LE và UTF-8, nên để có thể biết được Text file trong Microsoft Windows đang chứa Unicode encoding kiểu nào, ở đầu mỗi Unicode Text file có 2 hay 3 bytes gọi là Byte Order Mark như sau:

Character Encoding Scheme Byte Order Mark
UTF-16BE FE FF
UTF-16LE FF FE
UTF-8 EF BB BF

Khi viết Unicode ra Text file trong MSWindows, nếu dùng UTF-8 bạn chỉ cần viết ở đầu file Byte Order Mark EF BB BF. Các bytes kế tiếp cứ theo đúng hoặc UTF-8 characters thì dùng 2 hay 3 bytes hoặc ANSI characters thì dùng 1 byte.
Nếu dùng UTF-16LE (còn gọi gọn là Unicode trong ngôn ngữ Microsoft) thì viết Byte Order Mark FF FE. Kế đó mỗi character phải viết ra 2 bytes dù là Unicode hay không. Nếu character chỉ cần 1 byte, kể cả các characters như Carriage Return (&H0D) và LineFeed (&H0A), thì viết thêm byte thứ nhì là 00 (Null byte). Thí dụ:

Character Encoding Scheme Các bytes viết ra Text file
Text của file á à ả
UTF-16LE FF FE E1 00 20 00 E0 00 20 00 A3 1E
UTF-8 EF BB BF C3 A1 20 C3 A0 20 E1 BA A3

Khi bạn đọc Text file vào từng byte một bằng VB6, nhớ mở File dưới dạng Binary (as Binary) để có thể thấy luôn cả các Null bytes. Nếu bạn mở File dưới dạng Text File (as Input) thì sẽ không thấy các Null bytes. Câu hỏi đặt ra là Text File với encoding UTF-16LE hay UTF-8 chiếm nhiều chỗ hơn. Câu trả lời là tùy theo trường hợp. Ðối với UTF-16 thì mỗi character cần 2 bytes, kể cả ANSI characters. Ðối với UTF-8 thì nếu là Unicode character thì cần 2 hay 3 bytes, còn ANSI character thì chỉ cần 1 byte.

Các Functions thông dụng cho Unicode

Hoán chuyển ký tự từ Thường ra Hoa

Chúng ta có tổng cộng 134 ký tự dùng cho các nguyên âm có dấu và hai chữ đ, Ð. Nếu trong program có chứa danh sách các Unicode với 67 chữ Thường theo sau bởi 67 chữ Hoa, ta có thể biến đổi một chữ từ LowerCase ra UpperCase, hay ngược lại rất dễ dàng.
Dưới đây là Listing của Function UpperUniChar:

Function UpperUniChar(Ch) As String
' Return the Uppercase for a given vowel or dd
Dim Pos ' Position of character in Unicode vowel list
' Locate the character in list of Unicode vowels
Pos = InStr(UVowels, Ch)
If (Pos > 67) Then
UpperUniChar = Ch ' It's already uppercase - leave it alone
ElseIf (Pos > 0) Then
' It's a Lowercase Unicode Vowel - so get the corresponding Uppercase vowel in the list
UpperUniChar = Mid(UVowels, Pos + 67, 1)
Else
' It's just a normal ANSI character
UpperUniChar = UCase(Ch)
End If
End Function

Dưới đây là kết quả khi bạn click nút ToUppercase



Tương tự như vậy ta có Function LowerUniChar. Ðể hoán chuyển cả một Unicode String ra chữ thường hay chữ Hoa ta có các Function UpperUniStr và LowerUniStr.

Ngoài ra ta cũng có các Functions IsUniChar, IsUpperUniChar để kiểm tra xem character có phải là một Unicode Vowel hay là một Unicode Vowel Hoa.

Function GetFileEncoding giúp ta nhận diện một Text file đã được encoded theo UTF-16LE, UTF-8 hay ANSI.

Hoán chuyển ký tự cho trang Web

Và sau cùng Function ToUniDecimal đổi Unicode characters ra dạng thích hợp cho các trang Web. Listing của Function ToUniDecimal như sau:

Function ToUniDecimal(UniString As String) As String
' Return the HTML equivalent string of a Unicode string
Dim i As Integer ' Must declare as integer for CopyMemory to work
Dim TLen, TStr
Dim b1 As Byte
Dim b2 As Byte
Dim UTF16 As Long
TLen = Len(UniString) ' Get Length of input Unicode string
If TLen = 0 Then Exit Function ' Get out if null string
' Iterate through each character in the string
For i = 1 To TLen
If IsUniChar(Mid(UniString, i, 1)) Then
' Cast the String character to 2 bytes
CopyMemory b1, ByVal StrPtr(UniString) + ((i - 1) * 2), 1
CopyMemory b2, ByVal StrPtr(UniString) + ((i - 1) * 2) + 1, 1
' Combine the 2 bytes into the Unicode UTF-16
UTF16 = b2 ' assign b2 to UTF16 before multiplying by 256 to avoid overflow
UTF16 = UTF16 * 256 + b1
' Convert UTF-16 to format ✏ for HTML
TStr = TStr & "&#" & Trim(CStr(UTF16)) & ";"
Else
' Get here if it;s an ANSI character
TStr = TStr & Mid(UniString, i, 1)
End If
Next
ToUniDecimal = TStr ' Return the HTML string
End Function

Dưới đây là kết quả khi bạn click nút ToUniDecimal

Bạn có thể download chương trình Test có tất cả các Functions để dùng cho Unicode.

Ðọc viết Text file dưới dạng Unicode và UTF-8

Program Microsoft Notepad trong WindowsNT cho ta đánh Text dưới dạng ANSI, Unicode hay UTF-8. Ta có thể lập trình VB6 để đọc và viết Text file dưới cả hai dạng của Unicode. Khi Microsoft dùng chữ Save As Unicode là nói đến UTF-16LE.

Cách gọn và tiện nhất để đọc và viết Unicode (UTF-16LE) là dùng FileSystemObject của Microsoft Script Runtime. Khi Fs.OpenTextFile nhớ dùng TristateTrue để cho biết bạn muốn đọc viết Unicode Text.

Public Function ReadTextFile(FileName) As String
' Write a Unicode String to UTF-16LE Text file
' Remember to Project | References "Microsoft Scripting Runtime" to support
' FileSystemObject & TextStream
Dim Fs As FileSystemObject
Dim TS As TextStream
' Create a FileSystem Object
Set Fs = CreateObject("Scripting.FileSystemObject")
' Open TextStream for Input.
' TriStateTrue means Read Unicode UTF-16LE
Set TS = Fs.OpenTextFile(FileName, ForReading, False, TristateTrue)
ReadTextFile = TS.ReadAll ' Read the whole content of the text file in one stroke
TS.Close ' Close the Text Stream
Set Fs = Nothing ' Dispose FileSystem Object
End Function

Public Sub WriteTextFile(FileName, StrOutText)
' Read a Unicode String from UTF-16LE Text file
' Remember to Project | References "Microsoft Scripting Runtime" to support
' FileSystemObject & TextStream
Dim Fs As FileSystemObject
Dim TS As TextStream
' Create a FileSystem Object
Set Fs = CreateObject("Scripting.FileSystemObject")
' Open TextStream for Output, create file if necesssary
' TriStateTrue means Write Unicode UTF-16LE
Set TS = Fs.OpenTextFile(FileName, ForWriting, True, TristateTrue)
TS.Write StrOutText ' Write the whole StrOutText string in one stroke
TS.Close ' Close the Text Stream
Set Fs = Nothing ' Dispose FileSystem Object
End Sub

Cách gọn và tiện nhất để đọc và viết UTF-8 text files là dùng một VB6 Class tên clsUnicodeText dựa vào MS DOM (Document Object Model) và XML như sau:

Dim MyUnicodeText As clsUnicodeText
Set
MyUnicodeText = New clsUnicodeText
' Read Unicode Text from file txtFileName and display in TextBox1

TextBox1.Text = MyUnicodeText.ReadUnicode(txtFileName)

Listing của Class clsUnicodeText như sau:

Option Explicit
Private mDOMTextFile As DOMDocument
Private mXMLPath As String
Public Function ReadUnicode(TXMLPath)
Dim objTextFileRoot As IXMLDOMElement
Set mDOMTextFile = New DOMDocument
mXMLPath = TXMLPath
mDOMTextFile.Load mXMLPath
'start at the root element of the XML
Set objTextFileRoot = mDOMTextFile.documentElement
ReadUnicode = objTextFileRoot.nodeTypedValue
End Function

Public Sub WriteUnicode(OutText, Optional TXMLPath)
Dim tDOMNode As IXMLDOMElement ' Temporary Node for DOM
If IsMissing(TXMLPath) Then
' Save the information on the screen by creating a new element and add its children to the DOM object
mDOMTextFile.documentElement.Text = OutText
' Update the XML file
mDOMTextFile.save mXMLPath
Else
Set mDOMTextFile = New DOMDocument
' Create a Node called "Text" in DOM
Set tDOMNode = mDOMTextFile.createElement("Text")
' Make it the Root Node
mDOMTextFile.appendChild tDOMNode
' Assign Output Text to Root Node
mDOMTextFile.documentElement.Text = OutText
' Update the XML file
mDOMTextFile.save TXMLPath
End If
End Sub

Nhưng nếu bạn muốn viết và đọc trực tiếp một Text file với encoding UTF-16LE hay UTF-8 thì bạn cũng có thể làm được với VB6 bằng cách đọc và viết trong Binary mode, tức là Open file as Binary.
Khi viết ra thì sau khi viết BOM bytes, ta viết hoặc 2 bytes cho mỗi UTF-16 character trong UTF-16LE encoding, hoặc 2 hay 3 bytes cho Unicode character và 1 byte cho ANSI trong UTF-8 encoding.

Khi đọc vào, đối với UTF-16LE thì ta dùng trực tiếp 2 bytes cho mỗi character, nhưng đối với UTF-8 thì ta phải nhận ra &HE1 là byte đi đầu của 3 byte character , &HC3, &HC4, &HC5 và &HC6 là byte đi đầu của 2 byte character, để có thể hoán chuyển ra Unicode character cho input string. Các kỹ thuật nầy được cắt nghĩa trong Listing của Function UTF8ToUniStr như dưới đây:

Function UTF8ToUniStr(BArray) As String
' Convert a byte stream of UTF-8 to Unicode String
Dim i As Long
Dim TopIndex As Long
Dim TwoBytes(1) As Byte
Dim ThreeBytes(2) As Byte
Dim AByte As Byte
Dim TStr As String
TopIndex = UBound(BArray) ' Number of bytes equal TopIndex+1
If TopIndex = 0 Then Exit Function ' get out if there's nothing to convert
i = 0 ' Initialise pointer
' Iterate through the Byte Array
Do While i <= TopIndex AByte = BArray(i) ' fetch a byte
If AByte = &HE1 Then
' Start of 3 byte UTF-8 group for a character
' Copy 3 byte to ThreeBytes
ThreeBytes(0) = BArray(i): i = i + 1
ThreeBytes(1) = BArray(i): i = i + 1
ThreeBytes(2) = BArray(i): i = i + 1
' Convert Byte array to UTF-16 then Unicode
TStr = TStr & ChrW(ToUTF16(ThreeBytes))
ElseIf (AByte >= &HC3) And (AByte <= &HC6) Then
' Start of 2 byte UTF-8 group for a character
TwoBytes(0) = BArray(i): i = i + 1
TwoBytes(1) = BArray(i): i = i + 1
' Convert Byte array to UTF-16 then Unicode
TStr = TStr & ChrW(ToUTF16(TwoBytes))
Else
' Normal ANSI character - use it as is
TStr = TStr & Chr(AByte): i = i + 1 ' Increment byte array index
End If
Loop
UTF8ToUniStr = TStr ' Return the resultant string
End Function

Bạn có thể download program UniTextInOut.zip với tất cả các Functions cần thiết.
Program nầy biểu diễn các cách khác nhau để đọc và viết Text files với encodings UTF-16LE và UTF-8. Click nút Browse để chọn Text file (file TestUTF8.txt có UTF-8 encoding, file TestUTF16.txt có UTF-16LE encoding), rồi click nút DisplayFileHex để hiển thị bài chữ Việt bên trái và Hex string của file bên phải. Ðồng thời các Radio button cho biết là Text file có encoding Unicode (i.e. UTF-16LE) hay UTF-8. Kế đó bạn có thể chọn loại encoding bằng cách click lên Radiobutton Unicode hay UTF-8 trước khi click nút Save để lưu trử data trong Textbox bên trái vào Text file theo encoding bạn đã chọn.

Click vào đây để xem Listings của 13 Functions dùng để xử lý Unicode chữ Việt.

Những References và Components hổ trợ Unicode trong VB6

Trước hết muốn hiển thị Unicode cho chữ Việt ta cần phải dùng Menu command của VB6 IDE để Project | Components Microsoft Forms 2.0 Object Library. Cái ActiveX nầy cho ta những Label, TextBox, Listbox và ComboBox cần thiết để hiển thị chữ Việt trong Unicode.

Kế đó, để tiện việc đọc và viết chữ Việt dưới dạng UTF-8, ta chứa Unicode text file trong một XML file giữa một cặp tags tên Text hay tên gì cũng được (đó cũng là root node) , rồi dùng Microsoft Document Object Model (DOM) để đọc và viết chữ Việt. Bạn nhớ Project | References Microsoft XML, v3.0. Nguyên phần Text là nodeTypedValue của root node của DOM.

Làm như thế ta khỏi phải đọc từng byte rồi tìm cách chuyển data ấy qua Unicode String. Ở đây xin nhấn mạnh là bạn phải vui lòng dùng MSWindowsNT hay MSWindows2000 mới được. Bạn có thể dùng Notepad trong WindowsNTđể edit XML file chứa chữ Việt và lưu trử dưới format UTF-8.

Ngoài ra để hiển thị record data chữ Việt Unicode trong một Grid bạn cũng cần phải Project | Components Microsoft Hierarchical FlexGrid Control 6.0 (OLEDB).

Một khi Unicode text đã đuợc đọc vào trong VB6 Text String rồi, nó đuợc dùng y hệt như cho ANSI characters. Ðó là vì bên trong VB6 mỗi Unicode character hay ANSI character đều chiếm 2 bytes. Nói như thế có nghĩa là các Functions Left, Mid, InStr đều có thể đuợc dùng cho Unicode Text String như một ANSI String bình thường.

Xin lưu ý: Hình chữ Ð có hai code points: &HD0&H110. Code point &HD0 là của chữ Latin Eth hoa ( chữ Latin Eth thường là ð) , còn code point &H110 là của chữ Latin d hoa với stroke ( chữ Latin d thường với stroke là đ ) . Do đó ta phải dùng code point &H110. Bạn có thể copy chữ Ð nầy ngay từ Browser đểpaste vào chỗ nào cần thiết.

Vài nét về Access 2000

Từ đầu 1999, Microsoft giới thiệu bản beta 3 của Office 2000, trong đó chiếm vị trí xứng đáng là Access 2000 hay còn gọi là Access 9. Những tin tức đầu tiên về công cụ phát triển ứng dụng khách/chủ này chủ yếu tập trung vào 2 công nghệ mới với tên khá giống nhau là Microsoft Access Data Projects (ADPs) và Data Access Pages (DAPs). Cơ sở dữ liệu client/server dựng bằng Access bây giờ có thể được lưu trong tập tin với dạng thức kiểu mới gọi là .ADP (người dùng muốn dùng dạng thức .MDB cũng vẫn được). Tập tin ADP chứa các mẫu biểu (form), báo biểu (report) và các thành phần khác của giao diện, trong khi bảng và query (còn gọi là câu hỏi hoặc bảng truy vấn) được đưa vào tập tin dữ liệu của SQL Server 7. Nếu như trước kia, Access dùng Jet engine để truy xuất dữ liệu, thì nay có thể thay cơ cấu này bằng SQL Server 7, một cơ cấu CSDL mới, rất mạnh của Microsoft. Tất nhiên, vẫn có thể dùng Jet engine, nhưng cơ cấu này cũng được nâng cấp (phiên bản mới có số hiệu 4.x): ngôn ngữ SQL theo chuẩn ANSI 92, thêm khái niệm VIEW (khung nhìn), Procedure (thủ tục lưu sẵn), khái niệm giao tác phía Server, rồi một loạt kiểu dữ liệu mới như của SQL Server như Decimal,... Access 2000 hỗ trợ bộ ký tự Unicode mà không cần đến các phần mềm bổ trợ (add-on). Công cụ Access Upsizing Wizard nổi tiếng nay đã trở thành bộ phận cấu thành Access 2000 mà không cần phải tải xuống (download) từ cơ sở Web của Microsoft (đây là công cụ dùng để nâng cấp CSDL Jet hiện có lên SQL Server). Tập tin .MDB của Access 2000 không đọc được trong các phiên bản cũ của Access. Bạn có thể chuyển đổi tập tin .MDB thành .ADP một cách dễ dàng, tuy nhiên ngược lại thì không được. Tập tin .ADP cho phép vận hành Access như một ứng dụng mặt trước (front-end) trong khi SQL Server 7.0 dùng làm back-end (mặt sau, hậu trường). Không cần phải nạp cơ cấu Jet khi thực hiện ADP.

Còn DAP (Data Access Pages) thì sao? Nó cho phép dùng Access để tạo các ứng dụng Web động trên nền ASP (Active Server Pages). Truy xuất dữ liệu bây giờ chủ yếu thực hiện qua giao diện mới là ADO (Active Data Object). Có thể coi đây là mô hình đối tượng được sử dụng để làm việc với dữ liệu qua công nghệ OLE DB của Microsoft.

Bài báo này đề cập đến những tính năng mới được coi là thú vị nhất của Access 2000.

Đặc tính Dirty

Chắc bạn đã làm quen với đặc tính Dirty của mẫu biểu Access (chỉ có từ Access 97). Nó cho phép từ chương trình viết bằng VBA (Visual Basic for Applications) có thể xác định được dữ liệu trong bản ghi hiện thời (gắn với mẫu biểu) có thay đổi và đã được ghi chưa. Bạn có thể sửa đặc tính này thành giá trị False từ mã VBA để ghi bản ghi đó ra đĩa. Thế nhưng nếu ta cần thực hiện thao tác gì đó khi mẫu biểu bị thay đổi thì làm thế nào? Access 97 không có sự kiện gọi là Dirty. Chắc bạn đã đoán ra: Access 2000 cuối cùng đã có sự kiện (event) như vậy. Thủ tục xử lý sự kiện Dirty có tham biến Cancel. Khi cần có thể thực hiện lệnh Cancel = True để thôi (undo) không làm những gì vừa thay đổi với bản ghi hiện thời nữa. Nhược điểm lớn của sự kiện Dirty là nó chỉ xảy ra khi người nhập liệu bắt đầu sửa nội dung mẫu biểu, chứ không xảy ra khi đặc tính Dirty bị sửa thành False từ chương trình. Nên có thêm sự kiện, chẳng hạn với tên là Undo để xử lý tình huống đó (bạn có thể lấy trình FrmSmp 97.exe từ cơ sở Web của Microsoft để tham khảo cách mô phỏng sự kiện After undo, nó sử dụng một điều khiển tính toán với biểu thức là một hàm người dùng, và hàm này thực hiện mỗi khi đặc tính Dirty của Access 97 thay đổi. Dùng kỹ thuật này để mô phỏng sự kiện Dirty, nhưng không hiệu quả bằng sự kiện Dirty cài sẵn trong Access 2000).

Định dạng có điều kiện

Đây là một trong những tính năng mà người lập trình Access khao khát nhất: làm thế nào để chỉ một số dòng nào đó (ví dụ: các dòng với doanh số lớn hơn 10.000 USD) của mẫu hoặc báo biểu hiện lên với dạng thức theo ý muốn (ví dụ: có màu đậm hơn các dòng khác, để dễ đọc, dễ nhấn mạnh). Với report thì có thủ thuật thêm cấu trúc IF - Endif vào thủ tục xử lý sự kiện Format của vùng (section) chứa hộp văn bản cần kiểm tra và nhấn mạnh. (Nếu thỏa điều kiện nào đó thì sửa các đặc tính Font, Fontweight,... của điều khiển cần định dạng riêng.) Tương tự, đối với mẫu biểu kiểu Single (đơn bản ghi) đưa nhóm lệnh kể trên vào thủ tục xử lý Current. Thế nhưng với kiểu mẫu biểu liên kế (Continuous Form, mẫu biểu đa bản ghi), làm như vậy sẽ thay đổi dạng thức cho tất cả các bản ghi! Nghĩa là không đạt được mục đích đặt ra. Vâng, bây giờ thì nhóm Access của Microsoft đã lắng nghe nguyện vọng của người sử dụng: có cách dễ dàng định dạng có điều kiện (conditional formatting) cho từng hộp văn bản, từng hộp chọn combo, từng dòng một. Có thể xác lập nhiều điều kiện định dạng cho mỗi điều khiển (dưới dạng biểu thức luận lý hay trạng thái điều khiển đang trong focus hay không). Nếu có vài điều kiện đúng, sẽ chọn điều kiện đúng đầu tiên kể từ đầu danh sách. Bạn đặt điều kiện trong cả chế độ Design (khi thiết kế) hoặc chế độ Form (khi mở mẫu biểu để làm việc, tức là định dạng động, trong thời gian vào hoặc xem số liệu). Hình 1 cho thấy hộp thoại Conditional Formatting mở ra khi người dùng nhập liệu hoặc xem form bằng cách vào thực đơn Format, sau đó chọn Conditional Formatting.

Hình 1. Hộp thoại định dạng có điều kiện mở trong Form View.

Người lập trình VBA có thể kiểm soát việc định dạng có điều kiện bằng cách thêm hoặc loại bỏ các đối tượng kiểu FormatCondition từ sưu tập (collection) FormatConditions, và đặt lại các đặc tính của các đối tượng đó. Ví dụ, để doanh số hàng tháng trở nên đậm và có màu xanh dương nếu lớn hơn hạn mức nào đó (nhập sẵn vào hộp văn bản textMonthQuota của mẫu biểu), ta dùng đoạn mã sau:

With Forms!frmSales!txtMonthSales. FormatConditions
.Add(acFieldValue, acGreaterthan, "=[txtMonthQuota]")
.FontBold = True
.ForeColor = vbBlue
End With

Gắn mẫu biểu với bộ bản ghi

Ngay trong Access 2.0, người lập trình đã có nhiều cách để kiểm soát đặc tính Record Source của mẫu biểu. Bạn có thể gửi tên bảng, tên câu hỏi (query) hoặc cả một lệnh SELECT của SQL vào đặc tính này để đặc tả nguồn bản ghi. Với bộ bản ghi (recordset) tính được trong VBA thì rắc rối hơn: phải tạo bảng trung gian hoặc bảng truy vấn chứa dữ liệu cần thiết. Trong Access 2000 có thể gắn recordset tính được với mẫu biểu. Access 97 truy cập bộ bản ghi của mẫu biểu bằng đặc tính RecordsetClone, nhưng vẫn chưa có đặc tính Recordset để ấn định bộ bản ghi nguồn.

Tính năng mới này của Access 2000 rất linh hoạt, nhất là khi tạo recordset trong một giao tác (transaction). Khi quay lui (roll back) toàn giao tác, tất cả các thay đổi làm với mẫu biểu dựa trên bộ dữ liệu tạo trong giao tác, kể cả các thay đổi tác động đến nhiều bản ghi, sẽ được hủy. Trong quá khứ thì cực kỳ vất vả: thông thường phải dùng bảng trung gian hoặc mảng dữ liệu.

Hình 2: Đăng ký chế độ Compact on Close và tập tin Access sẽ được nén tự động khi đóng (miễn là không có ai khác vẫn còn mở nó).

Có thể gắn một recordset với vài mẫu biểu một lúc. Dữ liệu trong mẫu biểu này sẽ tự động đồng bộ với dữ liệu trong form khác, mà quay lui giao tác cũng sẽ quay lui đối với dữ liệu trong tất cả các biểu mẫu. Ngay cả khi dùng các recordset với nhiều mẫu biểu mà các recordset đó được tạo trong một giao tác, chúng cũng rollback được cùng nhau.

Trong các tập tin ADP, các bộ bản ghi ADO có thể cập nhập (sửa) được qua form nếu các ADP này được tối ưu để dùng OLE DB tương tác với SQL server. Nếu dùng Jet (tức dùng tập tin .MDB) vẫn phải sử dụng DAO (Data Access Object) để tạo recordset kiểu read/write cho mẫu biểu, còn dùng bộ bản ghi ADO cho mẫu biểu của .MDB thì nó chỉ có tính read-only mà thôi. Có lẽ Microsoft phải khắc phục nhược điểm này của ADO trong MDB khi đưa ra phiên bản cuối cùng của Access 2000.

Tự động nén tập tin

Đây là tính năng rất hữu ích, và để Access làm được điều đó khi đóng tập tin, chỉ việc vào trang General của hộp thoại Options, sau đó đánh dấu chọn ô Compact on Close (xem hình 2).

Thông thường ứng dụng Access được đưa vào MDB mặt trước (chứa query, form, report, module), dữ liệu tách ra tập tin MDB riêng (mặt sau). Đóng tự động được thực hiện cho MDB mặt trước, và cần một thủ thuật nhỏ để đảm bảo nén cả tập tin dữ liệu ở mặt sau. Access đòi hỏi tập tin này phải được mở trong chế độ Exclusive (độc chiếm) thì mới thực hiện compact được, tuy nhiên, ít nhất là từ nay không cần phải lo nén tập tin mặt trước nữa.

Tại sao lại phải thường xuyên nén dữ liệu, trong khi bạn không tạo bảng tạm thời nào cả? Trên thực tế, Access tạo rất nhiều đối tượng trung gian và làm cho CSDL liên tục phình ra, do đó bị phân đoạn và giảm hiệu năng truy xuất.

Hình 3. Từ nay Access 2000 dùng chung VBE với các thành phần khác của Office 2000.

Cần lưu ý rằng, nén tập tin có tác dụng đánh dấu các query cần được biên dịch lại trong lần vận hành kế tiếp. Khi bảng truy vấn lần đầu thực hiện sau khi thiết kế, cơ cấu Jet thiết lập một lược đồ truy vấn dựa trên các số liệu thống kê thu thập được. Nếu có ít số liệu (thường ban đầu dùng số liệu thử do người lập trình tự tạo), lược đồ truy vấn không sử dụng các chỉ mục mà trực tiếp quét các bản ghi trong bảng từ đầu đến cuối vì như thế sẽ nhanh hơn. Khi hệ thống áp dụng cho số liệu thực, liên tục biến đổi tăng dần, lược đồ truy vấn cũ không còn thích hợp nữa. Chính việc nén tập tin có tác dụng thiết lập lược đồ truy vấn mới cho query, dựa trên tình trạng mới nhất của số liệu. Nén cũng kéo theo việc sửa chữa tập tin, do đó không cần thực hiện thao tác repair như trong Access 2.0. Nén tự động sẽ không thực hiện nếu không tiết kiệm thêm ít nhất 256 KB.

Bộ soạn thảo VBE

Làm việc với Word, Excel chắc bạn đã quen thuộc với Visual Basic Editor (VBE). Bản thân Access 95 đã chấp nhận VBA nhưng đến Access 97 vẫn trung thành với giao diện soạn thảo chương trình theo kiểu riêng. Kể từ Office 2000, tất cả các ứng dụng cấu thành của nó, trong đó có Access, đều chung VBE với giao diện thống nhất (Hình 3).

Dường như đây là một bước thụt lùi của Access 2000. Trong phần lớn các phần mềm dùng VBA, các mẫu biểu được thiết kế ngay trong VBE. Người lập trình Access 2000 buộc phải tiến/lui giữa cửa sổ VBE và cửa sổ Access nơi có các mẫu biểu đang mở.

Nếu là người lập trình từng trải, bạn có thể đóng cửa sổ Properties trong VBE vì nó chỉ lặp lại những gì có trong các hộp thoại Access, không có cả các đặc tính của điều khiển. Cửa sổ Project Explorer theo phong cách treeview (dạng cây) thật hữu ích khi làm việc với các CSDL thư viện. Ngoài ra, Project Explorer còn có khả năng mở rộng, cho thấy cả những gì không có trong cửa sổ CSDL của Access. Bạn có thể làm cho cửa sổ Immediate di động tự do giống như cửa sổ Debug trước kia. Nếu không thấy cánh Locals hoặc Watch, có thể mở chúng trong cửa sổ riêng nằm trên phần còn lại là cửa sổ soạn thảo mã VBA, không khác gì mấy so với trong Access 97, ngoại trừ việc các đơn thể chỉ mở trong khuôn khổ của cửa sổ VBE mà thôi. Bản thân VBE có gì mới? Hấp dẫn nhất là thanh công cụ Edit: có cặp nút lệnh cho phép thêm/bớt câu chú thích cho toàn khối mã chỉ bằng một thao tác nhấp chuột. Điểm mới nữa là thanh Edit cho phép đặt thẻ đánh dấu (bookmark) vào văn bản mã trình. Khi cần thiết có thể dễ dàng quay lại những nơi đã đặt dấu, hoàn toàn chỉ bằng cách nhấp vào lệnh tương ứng trong thanh công cụ. Thực ra thì bookmark đã có từ thời Access 97 nhưng thao tác đặt lại nằm sâu trong hệ thống thực đơn, do đó nhiều người không để ý và chẳng bao giờ dùng đến.

Hình 4. CurrentProject cho phép nhận được nhiều thông tin mới, rất hữu ích.

VBE còn hỗ trợ mô hình các công cụ bổ trợ linh hoạt, cho phép tùy biến môi trường mã hóa của người lập trình. Bạn tự động thêm khối chú thích hoặc đoạn mã xử lý sự kiện trong thủ tục hoặc hàm mới? Có thể tự tạo add-in để làm điều đó hoặc dùng các công cụ do người khác cung cấp. Dự đoán sẽ có nhiều phương tiện bổ trợ cho VBE xuất hiện trên thị trường.

AutoCorrect

Giả sử bạn dùng trường tên là CustomerID trong một hoặc nhiều bảng. Một ngày nào đó bạn muốn đổi tên nó thành ClientID. Như trước kia thì thật vất vả: phải rà soát tất cả bảng truy vấn (query) để tìm các trường CustomerID đang sử dụng và đổi nó thành tên mới. Cũng làm như vậy với các form và report để thay lại các nguồn điều khiển gắn với trường cũ. Tin tốt lành cho chúng ta: Access 2000 tự động đổi tên trường trong tất cả các thành phần liên quan kể trên, nhưng đừng vội hân hoan: hệ chưa mò được tới các module, các đoạn mã lập trình và recordset. Bạn vẫn phải sửa bằng tay như thường làm, do đó đừng vội vứt bỏ SpeedFerret, một sản phẩm thứ ba, vì chắc nó vẫn còn hữu hiệu cho Access 2000. Tuy nhiên, dùng AutoCorrect của Access 2000 một cách thận trọng cũng giúp được nhiều việc.

Hình 5: Jet 4.0 bây giờ hỗ trợ khóa chốt đến từng bản ghi.

CurrentProject

Có một đối tượng mới trong Access 2000 gọi là CurrentProject.

Những ai từng lập trình DAO chắc khá quen thuộc với hàm CurrentDb () để tham chiếu tới CSDL đang mở. CurrentProject cũng có mục đích tương tự nhưng áp dụng cho cả ADO, ví dụ CurrentProject.Connection trả lại đối tượng kết nối ADO. Ngoài ra còn có CodeProject gần giống với hàm CodeDb () trong DAO để dùng với các add-in. Ta thực hiện một số lệnh sau để kiểm tra trong cửa sổ Immediate:

?CurrentProject.FullName
D:\Temp\db1.mdb
?CurrentProject.Name
db1.mdb
?CurrentProject.Path
D:\Temp

Đối tượng CurrentProject có các sưu tập (collection) chứa tất cả các đối tượng khác của Access, chẳng hạn AllForms, AllReports, AllMacros, AllModules và AllDataAccessPages. Mỗi sưu tập này chứa các đối tượng kiểu AccessObject với nhiều đặc tính (Property) rất hữu ích:

?CurrentProject.AllForms.Count
6
?CurrentProject.AllForms (5) .Name
frmQuery1
?CurrentProject.AllForms (5) .IsLoaded
False

Còn với các bảng và query thì sao? Có đối tượng khác gọi là CurrentData với đặc tính là các sưu tập AllTables, AllQueries, rồi cả AllViews, AllStoredProcedures và AllDatabaseDiagrams.

Tuy CurrentProject.Connection trả về đối tượng kết nối ADO, đặc tính ngầm định của đối tượng kết nối này là một chuỗi kết nối (Connect String):

?CurrentProject.Connection

Kết quả là một chuỗi ký tự bao gồm các tham trị chia tách nhau bằng dấu chấm phẩy (;). Dùng các hàm mới của Access 2000, trong đó có Split() và Filter() để tách và lấy ra các tham trị cần thiết.

Các hàm chuẩn mới

Một loạt hàm mới được bổ sung so với Access 97, dùng để xử lý xâu ký tự: Replace(), InstrRev(), StrReverse(), Split(), Join() và Filter().

Hàm Replace() dùng để thay xâu con của xâu nào đó bằng xâu con mới:

?Replace ("a1a2a3a4a5", "a", "b")
b1b2b3b4b5
?Replace ("a1a2a3a4a5", "a", "b", 3)
b2b3b4b5
?Replace ("a1a2a3a4a5", "a", "b", 3, 2)
b2b3a4a5

Trong 2 ví dụ cuối, số 3 ở tham số thứ tư dùng chỉ vị trí từ đó bắt đầu cần thay thế (và kết quả cũng từ đó luôn). Tham số thứ 5 trong ví dụ cuối cùng để đặc tả số lần phải thay (xâu con có thể hiện diện nhiều lần).

Hàm StrReverse() trả về chuỗi ký tự đảo ngược (không rõ thực tế ứng dụng CSDL có cần đến hàm này không?).

?StrReverse ("abcdef")
fedcba

Hàm InstrRev() có cơ chế làm việc như Instr() nhưng tìm kiếm ngược từ ký tự cuối đến ký tự đầu (hàm này thì thật sự cần thiết):

?InstrRv ("C:\MyPath\MyFile.ext", "\")
10

Các hàm Split(), Join() và Filter() cũng rất hữu ích. Split() chuyển đổi xâu ký tự có chia tách (bởi một ký tự nào đó, ví dụ dấu chấm phẩy) thành mảng các giá trị. Join() thực hiện thao tác ngược lại, từ mảng trở về xâu ký tự có chia tách. Filter() trích từ mảng các dòng thỏa mãn điều kiện nào đó.

? Filter(Split(CurrentProject.Connection, ","), _
"Data Source = ") (0)
Data Source=D:\Temp\db1.mdb

Thêm một bước nữa thì nhận được trị của Data Source.

? Split (Filter(Split(CurrentProject .Connection, ";"), _
"Data Source = ") (0) ,"=") (1)
D:\Temp\db1.mdb

Nếu đã quen thuộc với ngôn ngữ Lisp, chắc bạn rất hài lòng với bộ hàm trên và tự hỏi: tại sao đến bây giờ VBA mới có được khả năng phân tích xâu ký tự như vậy?

Ngang ngửa với VB 6

Những năm qua, người lập trình Access dùng VBA luôn lạc hậu so với Visual Basic, do đó có ý nghĩ cho rằng VBA là một tập con của VB. Sự thực thì VBA và VB luôn là một, có điều là người lập trình Visual Basic có được phiên bản mới hơn một chút. Giờ đây Microsoft đảm bảo Visual Basic và VBA đồng bộ với nhau. Như vậy, Office 2000 có ngôn ngữ lập trình hoàn toàn ngang ngửa với VB 6.0. Dưới đây tóm lược một số tính năng nâng cao của VBA mới.

- Bộ chuyển AddressOf cho phép truyền địa chỉ hàm gọi ngược cho thủ tục khác.

- Lệnh Implements được dùng với các đơn thể lớp để đặc tả lớp (class) thực thi giao diện (tập hợp các đặc tính và phương thức) định nghĩa trong một lớp khác. Như vậy, VBA bây giờ có tính năng đa hình (polymorphism), nghĩa là giao diện chuẩn có hành vi khác trong các lớp khác nhau.

- Với Access 97, bạn có thể dùng khai báo With Events trong đơn thể lớp để tạo khoang sự kiện, do đó đơn thể lớp đó có thể bắt được sự kiện phát sinh trong đối tượng. Từ nay có thể dùng lệnh Event để khai báo sự kiện theo ý muốn và lệnh RaiseEvent để làm cho nó xảy ra bất cứ lúc nào.

- Từ khóa Enum cho phép khai báo các hằng liệt kê, chẳng hạn như các hằng bắt đầu bằng ac, vb hoặc db. Các biến và tham số sau đó có thể được đăng ký với kiểu mới.

- Từ khóa Friend thêm lựa chọn thứ ba vào danh sách các khai báo (trước đó có Public và Private) khi cần định nghĩa phạm vi của thủ tục trong đơn thể lớp. Các thủ tục có tính Friend thấy được từ tất cả các thủ tục khác trong dự án. Khác với Public, chúng không thấy được bên ngoài dự án. Nếu dùng VBA để dựng các Com server cho các trình khác sử dụng, phạm vi kiểu Friend rất hữu ích.

- Đối tượng Debug có thêm phương thức Assert dùng đặt điểm ngắt vào mã trình. Ngắt sẽ xảy ra khi tại điểm đó biểu thức đặc tả trong Assert cho trị False. Lại thêm một cách mới để gỡ rối chương trình khi khó tìm được lỗi.

Jet Engine 4.0

Gần đây có nhiều tin đồn về giờ tận thế của Jet, một cơ cấu CSDL vốn dùng từ lâu nay cho Access (ở ta hay lẫn Jet với Access và một vài cổ động viên còn sót lại của FoxPro for DOS đã vội hoan hỉ là Access sắp chết - ND). Trên thực tế, người dùng được lựa chọn trong 2 khả năng: dùng Jet Engine 4.0 hoặc SQL Server 7.0 làm động cơ cho Access. Phiên bản 4.0 của Jet mạnh mẽ hơn bao giờ hết. Nhiều ứng dụng Access có thể không cần đến cơ cấu SQL Server 7 quá mạnh, và nhóm Jet của Microsoft vẫn nỗ lực hoàn thiện sản phẩm truyền thống của mình.

Động cơ Jet mới cho phép khóa chốt đến từng bản ghi (xem tùy chọn tương ứng trong trang Advanced thuộc hộp thoại Options ở hình 5).

Người dùng các phần mềm CSDL khác thường chê Access (đúng hơn: cơ cấu Jet của nó) là chỉ khoá chốt được toàn trang (page-level locking) khi bản ghi nào trong đó cần khóa. Từ Jet 3.x trở về trước một trang CSDL có dung lượng 2 KB. Jet 4.0 dùng trang 4 KB do đó nếu không hỗ trợ khóa chốt đến từng bản ghi thay cho việc khóa chốt toàn trang (gấp đôi so với trước), nhiều bản ghi sẽ bị "khóa lây". Dung lượng trang 4 KB là để hỗ trợ bộ mã Unicode, vì theo bộ này mỗi ký tự sẽ chiếm 2 byte. Giờ đây kích cỡ tối đa của tập tin MDB được nâng lên 2 GB so với 1 GB trước đây.

Việc khóa chốt theo kiểu nào cũng được Jet xác định một cách thông minh. Nếu có nhiều bản ghi trong trang 4 KB nào đó được khóa chốt, Jet tự động khóa cả trang cho hiệu quả, thậm chí nó khóa chốt cả bảng luôn nếu không có người dùng nào khác trên mạng truy cập bảng đó (đúng hơn: đang sửa - ND)

Nhiều đặc tính mới khác của Jet chủ yếu định hướng cho mô hình đối tượng ADO mà Microsoft khuyến nghị sử dụng thay cho mô hình DAO. Mô hình DAO cũ sẽ vẫn còn và giữ được giá trị sử dụng, nhất là về phương diện bảo mật và nhân bản (replication).

Dùng ADO nghĩa là sử dụng ngôn ngữ SQL đổi mới. Dưới đây liệt kê một số điểm mới của SQL theo chuẩn ANSI-92 mà Jet 4.0 hỗ trợ.

- Thêm các lệnh GRANT, REVOKE cho mục đích bảo mật

- Thêm các lệnh CREATE và DROP để tạo và loại bỏ câu hỏi (VIEW và PROCEDURE)

- CASCADE dùng để định nghĩa tính toàn vẹn tham chiếu

- Ràng buộc CHECK dùng để đặc tả biểu thức luận lý mà các bản ghi trong một hoặc nhiều bản phải thỏa.

- Các lệnh ALTER TABLE và ALTER COLUMN để sửa cấu trúc bảng mà không phải thêm cột mới, sao chép cột cũ sang và loại bỏ cột cũ như trước kia.

- IDENTITY là từ khóa đặc tả tính tăng tự động của cột

- Thêm một số kiểu cột mới để tương hợp với SQL Server, ví dụ kiểu Decimal

Có nên nâng cấp?

Khi bài báo này đến tay bạn đọc, vẫn còn quá sớm để phán xét về phiên bản mới của Access. Một số tính năng đề cập ở trên có thể không có trong bản chính thức của Access 2000 hoặc khác đi một chút. Việc tích hợp VBE với Access còn gây nhiều bàn cãi nhưng những bước tiếp theo có thể làm cho tính năng này sáng giá hơn. Microsoft rõ ràng là đã chuyển hẳn sang hướng xây dựng phần mềm tận dụng tối đa các thành phần dùng chung, tất nhiên có lúc phải trả giá về hiệu năng.

Ai đã từng trải qua thời kỳ lập trình trong Access 2.0 mới càng thấu hiểu giá trị của các thành phần nhiều mega đó, tuy chúng còn ở mức sơ khai và hy vọng hoàn chỉnh hơn theo đà hoàn thiện mô hình đối tượng chung của Microsoft. Sản phẩm mới chịu búa rìu dư luận là chuyện thường tình, và Access 2000 không là ngoại lệ.

Friday, October 30, 2009

A guide to DEBUG p2



A Guide to DEBUG
(Page Two)

A DEBUG Tutorial

Copyright©2005,2009 by Daniel B. Sedory

This page may be freely copied for PERSONAL use ONLY !
( It may NOT be used for ANY other purpose unless you have
first contacted and received permission from the author! )



Help on DEBUG Commands


For a reminder of all the commands (and most of the parameters) that are available while inside of DEBUG, simply enter a question mark (?) at the DEBUG prompt; when using DOS 5.0 or later. ( Note: Expanded Memory commands are rarely if ever used and will not be discussed here.)

Quick Alphabetical Links
Click on a command here for all its details:


H o w t o u s e t h e
C O M M A N D S


P a r a m e t e r s


NOTE: Parameters listed in brackets ( [ ] ) are optional. Optional parameters usually indicate there are a number of different ways a command could be used. I've listed the meanings of all the parameters here for you:

address - Memory location specified in hexadecimal. You can use either a simple Offset all by itself (in which case, the present CS 'Code Segment' will be assumed), or you can enter the full Segment:Offset location using either all hex numbers or substituting the name of a segment register for a number. Leading zeros are not required; thus 1F all by itself would be the location 'CS:001F' ( CS meaning whatever the CS happened to be at the time you entered this ). Examples:

    100    DS:12    SS:0    198A:1234
For a detailed discussion, see: Segment:Offset notation.

range - Two hexadecimal addresses separated by a single space. They may be listed as either full Segment:Offset pairs or just an Offset alone ( in which case, the Segment is assumed to be that of the present CS or "Code Segment" ). NOTE: Some commands, such as Compare (C), may require the second address be given only as an offset.

list - A string of Hexadecimal bytes separated by a space, or ASCII data enclosed within single or double quote marks. You can list any number of bytes from a single one up whatever number fits on the line before having to press the Enter key. A single byte, such as 00 is most often used with the FILL (f) command whereas an ENTER (e) command will most likely have a string of many hex bytes or ASCII characters per line; for example:
    e 100 31 C0 B4 09 BA 50 02 CD 21 B8 4C 00 CD 21

e 250 'This is an ASCII data string.$'

number - Remember all numbers and values used in any DEBUG command are understood as being Hexadecimal only! That includes the number of sectors in the LOAD or WRITE commands and even the number of instructions you want DEBUG to step through in the TRACE or PROCEED commands. It's all HEX all the time when using DEBUG!



A Simple DEBUG Tutorial

Details of each Command



NOTE: In the Examples below, commands which are entered by a user are shown in bold type; data displayed in response by DEBUG is in normal type. DEBUG (from MS-DOS 5.0 or later (which is true for the DEBUG version used by Windows™ XP) will display the following usage message, if you enter debug /? at a DOS prompt:

C:\WINDOWS>debug /?
Runs Debug, a program testing and editing tool.

DEBUG [[drive:][path]filename [testfile-parameters]]

[drive:][path]filename Specifies the file you want to test.
testfile-parameters Specifies command-line information required by
the file you want to test.

    Quit:  Q

Immediately quits (exits) the Debug program! No questions ever asked... should be the first command you remember along with the "?" command.

Back to TOC



Hex: H value1 value2
A very simple (add and subtract only) Hex calculator. Never forget that all numbers inside of DEBUG are always Hexadecimal. Enter two Hex values (no more than four digits each) and DEBUG shows first the SUM, then the DIFFERENCE of those values. Examples:
   -h aaa 531    -h fff 3      -h dbf ace
0FDB 0579 1002 0FFC 188D 02F1
- - -

Differences are always the second value subtracted from the first; AAA - 531 = 579. There are no carries past four digits.

Two's Complement arithmetic is always used in this calculator, so think of it as being limited to a maximum of plus 7FFFh (+ 32,767) or a minimum of minus 8000h (- 32,768). Positive values are represented by exactly the same digits as their numbers for 0000h through 7FFFh. A minus 7FFFh, however, is represented by the Hex digits 8001, and a minus 1h (-1) is represented by the Hex digits FFFF. Thus, the output of DEBUG after entering "h 4 fffc" would be a zero and an 8, because FFFC represents a minus 4h (-4) and 4 - (-4) = 8. Examples:

   -h 4 fffc    -h 100 123    -h 7fff 8000
0000 0008 0223 FFDD FFFF FFFF
- - -
Note the difference between 100h and 123h; what does FFDD represent? To find the numerical value of a Two's Complement number, first invert every bit (or find its logical inverse); that would be 0022, then add 1. So, this represents a negative 23h. Both the sum and the difference of 7FFFh and 8000h are a negative 1 (or FFFF); which can be arrived at using: 7FFFh + (- 8000h) = -1. However, it's much easier to think of the sums as having nothing to do with Two's Complement notation; thus, 7FFFh + 8000h = FFFFh (32,767 + 32,768 = 65,535). This will even hold true for the differences if the second value is less than the first. But any difference which produces a negative number, must be represented in Two's Complement.

Back to TOC



Dump: D [range]
D [address] [length]

Displays the contents of a block of memory. The Memory locations near the beginning of Segment C000 (even under Windows 2000/XP) should display information about the kind of video card installed on your PC. The first example below shows what a Matrox video card on our system displayed.

Examples:

-d c000:0010
C000:0010 24 12 FF FF 00 00 00 00-60 00 00 00 00 20 49 42 $.......`.... IB
C000:0020 4D 20 43 4F 4D 50 41 54-49 42 4C 45 20 4D 41 54 M COMPATIBLE MAT
C000:0030 52 4F 58 2F 4D 47 41 2D-47 31 30 30 20 56 47 41 ROX/MGA-G100 VGA
C000:0040 2F 56 42 45 20 42 49 4F-53 20 28 56 31 2E 32 20 /VBE BIOS (V1.2
C000:0050 29 00 87 DB 87 DB 87 DB-87 DB 87 DB 87 DB 87 DB )...............
C000:0060 50 43 49 52 2B 10 01 10-00 00 18 00 00 00 00 03 PCIR+...........
C000:0070 40 00 12 10 00 80 00 00-38 37 34 2D 32 00 FF FF @.......874-2...
C000:0080 E8 26 56 8B D8 E8 C6 56-74 22 8C C8 3D 00 C0 74 .&V....Vt"..=..t
-
-d 100 130
xxxx:0100 EB 24 0D 0A 54 68 69 73-20 69 73 20 6D 79 20 66 .$..This is my f
xxxx:0110 69 72 73 74 20 44 45 42-55 47 20 70 72 6F 67 72 irst DEBUG progr
xxxx:0120 61 6D 21 0D 0A 24 B4 09-BA 02 01 CD 21 B4 00 CD am!..$......!...
xxxx:0130 21 !
-
The last example above, is what you'd see after entering the code under the Assemble command. We could just as easily have used the length option with the command 'd 100 l31' (that's an 'L' in front of the "31") to produce the same results above. The following example shows only the '$'-terminated display string, which has a length of 24h bytes (remember numbers in DEBUG are always hexadecimal); so that's 36 in decimal:
-d 102 l24
xxxx:0100 0D 0A 54 68 69 73-20 69 73 20 6D 79 20 66 ..This is my f
xxxx:0110 69 72 73 74 20 44 45 42-55 47 20 70 72 6F 67 72 irst DEBUG progr
xxxx:0120 61 6D 21 0D 0A 24 am!..$
-

Back to TOC



Search: S range list

Searches within a range of addresses for a pattern of one or more byte values given in a list. The list can be comprised of numbers or character strings enclosed by matching single or double quote marks. [ NOTE: In the examples below, if you do find the same data on your computer, the locations could easily vary from ours! ]

Examples:

-s fe00:0 ffff "BIOS"
FE00:0021
FE00:006F

-d fe00:0
FE00:0000 41 77 61 72 64 20 53 6F-66 74 77 61 72 65 49 42 Award SoftwareIB
FE00:0010 4D 20 43 4F 4D 50 41 54-49 42 4C 45 20 34 38 36 M COMPATIBLE 486
FE00:0020 20 42 49 4F 53 20 43 4F-50 59 52 49 47 48 54 20 BIOS COPYRIGHT
FE00:0030 41 77 61 72 64 20 53 6F-66 74 77 61 72 65 20 49 Award Software I
FE00:0040 6E 63 2E 6F 66 74 77 61-72 65 20 49 6E 63 2E 20 nc.oftware Inc.
FE00:0050 41 77 03 0C 04 01 01 6F-66 74 77 E9 12 14 20 43 Aw.....oftw... C
FE00:0060 1B 41 77 61 72 64 20 4D-6F 64 75 6C 61 72 20 42 .Award Modular B
FE00:0070 49 4F 53 20 76 34 2E 35-31 50 47 00 DB 32 EC 33 IOS v4.51PG..2.3

-s 0:0 dff 'A20'
0000:0C42

-d 0:c40
0000:0C40 0D 0A 41 32 30 20 68 61-72 64 77 61 72 65 20 65 ..A20 hardware e
0000:0C50 72 72 6F 72 2E 20 20 43-6F 6E 74 61 63 74 20 74 rror. Contact t
0000:0C60 65 63 68 6E 69 63 61 6C-20 73 75 70 70 6F 72 74 echnical support
0000:0C70 20 74 6F 20 69 64 65 6E-74 69 66 79 20 74 68 65 to identify the
0000:0C80 20 70 72 6F 62 6C 65 6D-2E 0D 0A 24 1A 00 BA F6 problem...$....

-s 0:0 dff 43 4f 4d
0000:0774
0000:07C2
0000:07D4
0000:07E6

-d 0:770
0000:0770 7A 02 A6 02 43 4F 4D 31-20 20 20 20 8E 00 70 00 z...COM1 ..p.
0000:0780 C0 A0 7A 02 91 02 4C 50-54 31 20 20 20 20 A0 00 ..z...LPT1 ..
0000:0790 70 00 C0 A0 7A 02 98 02-4C 50 54 32 20 20 20 20 p...z...LPT2
0000:07A0 2D 01 70 00 C0 A0 7A 02-9F 02 4C 50 54 33 20 20 -.p...z...LPT3
0000:07B0 20 20 11 EA 27 27 3F FD-CA 00 70 00 00 80 7A 02 ..''?...p...z.
0000:07C0 AC 02 43 4F 4D 32 20 20-20 20 DC 00 70 00 00 80 ..COM2 ..p...
0000:07D0 7A 02 B2 02 43 4F 4D 33-20 20 20 20 00 00 6B 03 z...COM3 ..k.
0000:07E0 00 80 7A 02 B8 02 43 4F-4D 34 20 20 20 20 E8 D2 ..z...COM4 ..


Back to TOC



Compare: C range address
Compares two blocks of memory. If there are no differences, then DEBUG simply displays another prompt (-). Here's an example of what happens when there are differences:
     -c 140 148 340
127D:0143 30 6D 127D:0343
127D:0146 10 63 127D:0346
127D:0148 49 30 127D:0348
The bytes at locations 140 through 148 are being compared to those at 340 ( through 348, implied ); the bytes are displayed side by side for those which are different (with their exact locations, including the segment, on either side of them).


Back to TOC



Fill: F range list
This command can also be used to clear a whole segment of Memory as well as filling smaller areas with a continuously repeating phrase or single byte. Examples:
-f 100 12f 'BUFFER'
-d 100 12f
xxxx:0100 42 55 46 46 45 52 42 55-46 46 45 52 42 55 46 46 BUFFERBUFFERBUFF
xxxx:0110 45 52 42 55 46 46 45 52-42 55 46 46 45 52 42 55 ERBUFFERBUFFERBU
xxxx:0120 46 46 45 52 42 55 46 46-45 52 42 55 46 46 45 52 FFERBUFFERBUFFER

-f 100 ffff 0

This last example fills almost all of the assigned Segment with zero bytes (which can also be thought of as clearing the Segment). You should use this command whenever you want to be sure the bytes you'll be looking at in DEBUG's Segment are those you entered or loaded, or bytes DEBUG has changed; not soem previously used bytes from memory! If you want to examine a file from a disk in a 'clean' Segment, you'll first have to start DEBUG without any filename, clear the Segment using: f 100 ffff 0 and then finally load the file using the Name (n) and Load (L) commands in that order.

NOTE: Filling (clearing) any bytes in the area from 00h through FFh of our Segment can sometimes lead to problems; especially when file I/O is involved. DEBUG stores data for its own use in those locations, so we recommend you never overwrite bytes in that area; unless you have a reason for doing so!

Example: A student in an Assembly class was told to enter a string of commands under DEBUG, the last one being: JMP 0 which he was supposed to Trace (T) to the next command and then execute it. He was told it would be an INT 20 instruction. Well in most cases this is true, because DEBUG always sets the first two bytes of its working segment to "CD 20" for just this purpose. Let's test this out. First, open a new instance of DEBUG, then enter the following commands:

-f 100 ffff 0    [Zero-out 100 through FFFF]
-e 100 e9 fd fe [Enters a 'JMP 0' at 100]
-u 100 102 [Check for correct entry]
xxxx:0100 E9FDFE JMP 0000
-r
AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=xxxx ES=xxxx SS=xxxx CS=xxxx IP=0100 NV UP EI PL NZ NA PO NC
xxxx:0100 E9FDFE JMP 0000
-u 0 1
xxxx:0000 CD20 INT 20

If you don't see "INT 20" after entering "u 0 1", then restart DEBUG and try again.

-t  [The "T"(Trace) command]

AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=xxxx ES=xxxx SS=xxxx CS=xxxx IP=0000 NV UP EI PL NZ NA PO NC
xxxx:0000 CD20 INT 20
-p [Always make sure you use a "P"(Proceed) command for Interrupts!]

Program terminated normally
-q [Quit]

Well, this never worked for those students. Why? Because the teacher had mistakenly told them to Fill the whole segment with zero bytes (f 0 ffff 0), in essence telling them to delete the very instruction he'd wanted them to execute!


Back to TOC




Enter: E address [list]
Used to enter data or instructions (as machine code) directly into Memory locations.
Example. First we'll change a single byte at location CS:FFCB from whatever it was before to D2
:
   -e ffcb d2
This next example shows that either single(') or double(") quote marks are acceptable for entering ASCII data. By allowing both forms, entry strings can be created to include either type of quote mark as data:
   -e 200 'An "ASCII-Z string" is always followed by '
-e 22a "a zero-byte ('00h')." 0
But in order to enter more than a single line of ASCII data, the A (Assemble) command is more practical since it will calculate the next offset for you! (See that command for a Memory dump of these bytes.) Now we'll examine a string of 11 hex bytes you can enter into Memory at locations CS:0100 and following:
   -e 100 B4 09 BA 0B 01 CD 21 B4 00 CD 21
This is actually machine code for a program that will display whatever ASCII characters it finds at locations CS:010B and following, until it encounters a byte value of 24h (a $ sign). If you want to run this program, we'd recommend entering 24h at offset location 7EAh of the Segment so the program will terminate there:
   -e 7ea 24
-g =100
And you'll soon see: "Program terminated normally" on the display screen. Why did we pick 7EAh? Because many DOS screens are set to display only 25 lines of 80 (50h) characters, and this value allows you to view the maximum number of characters possible on a single screen between the "Go," termination and prompt ("-") lines.

Here's something a bit more interesting for you to try out: It's essentially the same program, but the data includes all of the byte values from 00h through FFh; except for 24h which we placed at the end of the last line. The DEBUG prompt symbol, - , has been purposely excluded from the lines below, so you can copy and paste the whole block into a DEBUG DOS-box (Help on using DOS-Window controls is here if needed):

e 100 B4 09 BA 0B 01 CD 21 B4 00 CD 21 0D 0A 0D 0A 00 01 02
e 112 03 04 05 06 07 08 09 20 0B 0C 20 0E 0F 10 11 12 13 14
e 124 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 20 25 26
e 136 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38
e 148 39 3A 3B 3C 3D 3E 3F 0D 0A 0D 0A 40 41 42 43 44 45 46
e 15a 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58
e 16c 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A
e 17e 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C
e 190 7D 7E 7F 0D 0A 0D 0A 80 81 82 83 84 85 86 87 88 89 8A
e 1a2 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C
e 1b4 9D 9E 9F a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aA aB aC aD aE
e 1c6 aF b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 bA bB bC bD bE bF 0D
e 1d8 0A 0D 0A c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 cA cB cC cD cE
e 1ea cF d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 dA dB dC dD dE dF e0
e 1fc e1 e2 e3 e4 e5 e6 e7 e8 e9 eA eB eC eD eE eF f0 f1 f2
e 20e f3 f4 f5 f6 f7 f8 f9 fA fB fC fD fE fF 0D 0A 0D 0A 24

The bytes 0Dh and 0Ah produce a Carriage Return and Linefeed on the display, so we replaced them in the listing above by 20h; a SPACE byte. The 24h byte was moved to the end of the program with another 20h taking its place. The bytes shown above in blue (0D 0A 0D 0A) form blank lines at the beginning of the output and after every 64 bytes for a nice formatted display.

Therefore, when the program is run, we should see four separate lines of 64 characters each (a few of those being blank spaces as mentioned above), right? Well, let's find out: Start DEBUG in a DOS-Window, copy and paste the lines above into DEBUG at its prompt symbol, then enter the following command:

     g =100    ( 'g' followed by a SPACE, then '=100')

This will immediately run (see Go command) the program, displaying the output lines followed by: "Program terminated normally" [ Do not exit DEBUG, just leave the window open. We're going to show you how to 'patch' this code and save the results as a nice little console program ].

Were you surprised to find more than four spaces on the first line; starting with the very first byte? What about the fact we appear to have missing characters at the end of that line? We'll briefly explain why the characters appeared this way on your screen, but in order to create programs of your own, you'll need to study about control characters, Interrupts and what effect different BIOS and DOS video functions have on the way ASCII characters are displayed. OK, here's what happened:

First, the Zero byte also displays as a blank space here. The 07 byte may make a beep or ding sound (but does not display anything), 08 performs a BACKSPACE (erasing the 06 byte character) and 09 is a TAB -- which may jump up to eight columns to the right before reaching the next 'Tab Stop.' But since it just happens to begin in column seven, it only moves one column to the right where our program places the space we substituted for 0Ah. Lastly, for some reason, when using Function 09 of INT 21h ("Display a string of characters until a '$' sign is encountered"), the ESC character (1Bh; 27 decimal) doesn't display or do anything. So, after reaching the end of the first line, it only appeared as if many of the characters we expected to see were never displayed. In reality, the last three characters are there. It's because of the bytes 07h (displayed nothing), 08h (only backspaced over 06h), 09h (displayed nothing, but moved cursor forward one byte) and 1Bh (displayed nothing) that we saw what we did.

Enter the following two lines into DEBUG (which contain more blank-space substitutions), run the program again, and you'll see all the displayable characters output on the first line in their correct positions:

e 10F 00 01 02 03 04 05 06 20 20 20 20 0B 0C 20
e 11D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 20
All four rows will display evenly in size, including the last one. But the last character, FFh (255 decimal), just like the first, also displays as a blank space here! You can prove this by inserting another byte such as 2Eh (a period '.') after FFh. We've created the following patch which effectively moves up the remainder of the program after the FFh by one:

e 21b 2e 0d 0a 0d 0a 24

After patching and running it again, the program output should look like this:


If you want to, you can save this code as an executable program by first giving it a path and filename (such as, C:\TEMP\ASCIIDSP.COM; see Name command) and writing the bytes (see Write command) to a file like this:
   -n c:\temp\asciidsp.com
-rcx
CX0000
:121 [ Program Length = 220h - 100h + 1 = 121h ]
-w

If you check the file properties of ASCIIDSP.COM, its size should be 289 bytes.

Back to TOC



Go: G [=address] [addresses]

Go is used to run a program and set breakpoints in the program's code.

As we saw in an Example for the ENTER command, the '=address' option is used to tell DEBUG where to start executing code. If you use 'g' all by itself, execution will begin at whatever location is pointed to by the CS:IP registers. Optional breakpoints (meaning the program will HALT before executing the code at any of these locations) of up to any ten addresses may be set by simply listing them on the command line.

Requirements: Breakpoints can only be set at an address containing the first byte of a valid 8088/8086 Opcode. So don't be surprised if picking some arbitrary address never halts the program; especially if you're trying to DEBUG a program containing opcodes DEBUG can't understand (that's any instruction which requires a CPU above an 8088/8086)!

Using "Go" after setting breakpoints at instructions you're sure DEBUG understands is one way you can get more use out of this program; for example, when debugging the "real" code of a Master Boot Record written for 80386 or above processors. DEBUG won't be able to disassemble nor single step through such code, but it can still pass the instructions to the CPU for execution then stop for breakpoints you've set at any instruction it does understand.

CAUTION: DEBUG replaces the original instructions of any listed breakpoint addresses with CCh (an INT 3). The instructions at these locations are restored to their original bytes ONLY if one of the breakpoints is encountered. If DEBUG does not HALT on any breakpoint, then all your breakpoints are still enabled! So, don't ever save the code unless you're sure you've cleared all your breakpoints! (Saving to a backup copy before ever using a breakpoint is often the best way.)



Back to TOC



Assemble: A [address]

Creates machine executable code in memory beginning at CS:0100 (or the specified address) from the 8086/8088 (and 8087) Assembly Language instructions which are entered. Although no Macro instructions nor labels are recognized, you can use the pseudo-instructions 'DB' and 'DW' (so you can use the DB opcode to enter ASCII data like this: DB 'This is a string',0D,0A ); spaces after the commas would make it clearer but aren't necessary.

The 'A' command remembers the last location where any data was assembled, so successive 'A' commands (when no address is specified) will always begin at the next address in the chain of assembled instructions. This aspect of the command is similar to the Dump command which remembers the location of its last dump (if no new address is specified).

The assembly process will stop after you ENTER an empty line.

Examples:

Using the character string from our E (Enter) command above:

      -a 200
xxxx:0200 db 'An "ASCII-Z string" is always followed by '
xxxx:022A db "a zero-byte ('00h').", 0
xxxx:023F
-d 200 23e
xxxx:0200 41 6E 20 22 41 53 43 49-49 2D 5A 20 73 74 72 69 An "ASCII-Z stri
xxxx:0210 6E 67 22 20 69 73 20 61-6C 77 61 79 73 20 66 6F ng" is always fo
xxxx:0220 6C 6C 6F 77 65 64 20 62-79 20 61 20 7A 65 72 6F llowed by a zero
xxxx:0230 2D 62 79 74 65 20 28 27-30 30 68 27 29 2E 00 -byte ('00h')..

ENTER the characters in bold type; you do not need to enter the comments after the semi-colon ( ; ) symbols:

      -a 100
xxxx:0100 jmp 126 ; Jump over data that follows:
xxxx:0102 db 0d,0a,"This is my first DEBUG program!"
xxxx:0123 db 0d,0a,"$"
xxxx:0126 mov ah,9 ; Function 09 of Int 21h:
xxxx:0128 mov dx,102 ; DS:DX -> $-terminated string.
xxxx:012B int 21 ; Write String to STD Output.
xxxx:012D mov ah,0 ; Function 00 of Int 21h:
xxxx:012F int 21 ; Terminate Program.
xxxx:0131
-g =100

This is my first DEBUG program!

Program terminated normally
-

NOTE: You can pipe simple 8086/8088 Assembly Language "scripts" into DEBUG (You can even include a semi-colon ';' followed by comments on most of its lines. For some odd reason though, these comments do not appear to be allowed on DB/DW lines!). For example, you can copy and paste the following into DEBUG (after entering an initial "a" command) and obtain the same results as above:

jmp 126      ; Jump over data that follows:
db 0d,0a,"This is my first DEBUG program!"
db 0d,0a,"$"
; End of string marker above: "$"=24h
mov ah,9 ; Function 09 of Int 21h:
mov dx,102 ; DS:DX -> $-terminated string.
int 21 ; Write String to STD Output.
mov ah,0 ; Function 00 of Int 21h:
int 21 ; Terminate Program.
  • DEBUG uses the convention of enclosing operands which refer to Memory locations in square brackets '[ ]' (as opposed to an immediate value as an operand).


  • DEBUG may require you to explicitly tell it whether or not an operand refers to a word or byte in Memory! In such cases, the data type must be stated using the prefixes 'WORD PTR' or 'BYTE PTR'


  • For all 8087 opcodes, the WAIT or FWAIT prefix must be explicitly specified.
Back to TOC



Unassemble: U [range]
Disassembles machine instructions into 8086 Assembly code. Without the optional [range], it uses Offset 100 as its starting point, disassembles about 32 bytes and then remembers the next byte it should start with if the command is used again. ( The word 'about' was used above, because it may be necessary to finish with an odd-number of bytes greater than 32, depending upon the last type of instruction DEBUG has to disassemble. )

NOTE: The user must decide whether the bytes that DEBUG disassembles are all 8086 instructions, just data or any newer x86 instructions (those for the 80286, 80386 on up to the lastest CPU from Intel; which are all beyond the ability of DEBUG to understand)!
   Example:

-u 126 12F
xxxx:0126 B409 MOV AH,09
xxxx:0128 BA0201 MOV DX,0102
xxxx:012B CD21 INT 21
xxxx:012D B400 MOV AH,00
xxxx:012F CD21 INT 21
-



Back to TOC



Input: I port

The use of I/O commands while running Windows™9x/Me is just plain unreliable! This is especially true when trying to directly access hard disks! Under Win NT/2000/XP, the I/O commands are only an emulation; so don't trust them. Though the example below still works under Win2000/XP, it's most likely using some WinAPI code to show what's in the Windows clock area; not directly from an RTC chip.

Long ago (when DOS was the only OS for PCs), there were dozens of BASIC programs that used I/O commands for handling tasks through parallel and serial ports (e.g., to change the font used by a printer or values in a modem's control registers). Under real DOS, they can still be used for direct communications with keyboards or a floppy drive's control chips along with many other hardware devices.
Here's an example of how to read the hours and minutes from a computer's "real time clock" (RTC):

    -o 70 04  <-- Check the hours.
-i 71
18 <----- 18 hours (or 6 p.m.)
-o 70 02 <-- Check the minutes.
-i 71
52 <----- 52 minutes
The first space isn't necessary under most versions of DEBUG; so you can try to get away with just "o70" and "i71" instead. Here's a page of more complex examples dealing with hard drives and the ATA commands for reading info directly from a disk controller!
Back to TOC



Output: O port byte

See comments under the Input command.

Back to TOC



Load:
L [address] [drive] [firstsector] [number]
or program! (See the N command for more on this)

This command will LOAD the selected number of sectors from any disk's Logical Drive under the control of MS-DOS or Windows into Memory. The address is the location in Memory the data will be copied to (use only 4 hex digits to keep it within the memory allocated to DEBUG), the drive number is mapped as: 0=A:, 1=B:, 2=C:, etc., firstsector counts from ZERO to the largest sector in the volume and finally number specifies in hexadecimal the total number of sectors that will be copied into Memory (so a floppy disk with 0 through 2,879 sectors would be: 0 through B3F in Hex).

The terms 'Volume' or 'Logical Drive' used in the definition above mean you cannot use the 'L' command to load or examine the MBR, or any other sectors outside the Primary Volumes or Logical Drive Letters assigned by DOS or Windows! For example (under Windows™ 9x/ME), if you enter the command: L 100 2 0 1 in DEBUG, instead of seeing the very first sector on that hard disk (the MBR), you'll see the first sector of the Boot Record for the Logical drive C: instead (the first partition that can accessed by a compatible MS-DOS or Windows OS). This and the following comments about diskettes, show that DEBUG has always been quite limited compared to a good disk editor or the UNIX 'dd' program.

Load can still be useful in examining Floppy Disks even under Windows™ 2000/XP, but (unfortunately), only if the disk can be read by MS-DOS or Windows. Once again, this shows how limited DEBUG is compared to any utility that can view the raw data on either a hard drive or diskette. (For those of you who wish to examine the actual contents of a hard disk under Windows™ XP, there are free disk editors, such as HxD, which allow you to do so.)

Unlike hard disks, the very first sector on a floppy disk is an OS Boot sector. Here's what you might see from a Logical disk sector and some dumps from a couple floppy disks.

Examples:

-l 100 2 0 1   [ the C: drive. ]
-d 100 10f
xxxx:0100 EB 58 90 4D 53 57 49 4E-34 2E 31 00 02 08 20 00 .X.MSWIN4.1... .
-d 280 2ff
xxxx:0280 01 27 0D 0A 49 6E 76 61-6C 69 64 20 73 79 73 74 .'..Invalid syst
xxxx:0290 65 6D 20 64 69 73 6B FF-0D 0A 44 69 73 6B 20 49 em disk...Disk I
xxxx:02A0 2F 4F 20 65 72 72 6F 72-FF 0D 0A 52 65 70 6C 61 /O error...Repla
xxxx:02B0 63 65 20 74 68 65 20 64-69 73 6B 2C 20 61 6E 64 ce the disk, and
xxxx:02C0 20 74 68 65 6E 20 70 72-65 73 73 20 61 6E 79 20 then press any
xxxx:02D0 6B 65 79 0D 0A 00 00 00-49 4F 20 20 20 20 20 20 key.....IO
xxxx:02E0 53 59 53 4D 53 44 4F 53-20 20 20 53 59 53 7E 01 SYSMSDOS SYS~.
xxxx:02F0 00 57 49 4E 42 4F 4F 54-20 53 59 53 00 00 55 AA .WINBOOT SYS..U.
-
-l 100 0 0 1 [ a floppy in the A: drive. ]
-d 100 13d
xxxx:0100 EB 3C 90 29 47 38 71 33-49 48 43 00 02 01 01 00 .<.)G8q3IHC.....
xxxx:0110 02 E0 00 40 0B F0 09 00-12 00 02 00 00 00 00 00 ...@............
xxxx:0120 00 00 00 00 00 00 29 40-16 D8 13 4E 4F 20 4E 41 ......)@...NO NA
xxxx:0130 4D 45 20 20 20 20 46 41-54 31 32 20 20 20 ME FAT12
-
-l 100 0 0 1 [ a different floppy in the A: drive. ]
-d 100 13d
xxxx:0100 EB 3C 90 53 59 53 4C 49-4E 55 58 00 02 01 01 00 .<.SYSLINUX..... xxxx:0110 02 E0 00 40 0B F0 09 00-12 00 02 00 00 00 00 00 ...@............ xxxx:0120 00 00 00 00 00 00 29 7E-CF 55 3C 20 20 20 20 20 ......)~.Ud 2d0 2ff
xxxx:02D0 42 3B 16 1A 7C 72 03 40-31 D2 29 F1 EB A7 42 6F B;..|r.@1.)...Bo
xxxx:02E0 6F 74 20 66 61 69 6C 65-64 0D 0A 00 00 00 00 4C ot failed......L
xxxx:02F0 44 4C 49 4E 55 58 20 53-59 53 F4 3C 82 3A 55 AA DLINUX SYS.<.:U.

The Linux Boot disk above (note the word: SYSLINUX) is the kind formatted as an MS-DOS diskette and not with a true Linux file system (such as ext2 or ext3). If it had been formatted with some other kind of file system, or had a faulty boot sector, then MS-DEBUG would not be able to read it! Instead you'd see that old "General failure reading drive A / Abort, Retry, Fail?" error message! And when you had finally cleared away that error message, you'd be greeted by DEBUG's "Disk error reading drive A" error message. This makes DEBUG almost worthless as far as trying to fix an error in a floppy disk's boot sector! However, if you keep a binary copy of a good floppy disk Boot Sector somewhere, you could use DEBUG to overwrite whatever's on a faulty floppy disk's first sector (see Write command). But if you really need to see what's in such a Boot sector (i.e., what is keeping DEBUG from recognizing it as valid), you'll need to use a disk editor such as Symantec's Norton DiskEdit (in Physical disk Mode only).

NOTE: Just because a floppy disk can't be read by DOS or opened in DEBUG does NOT necessarily mean it's defective. It might simply have been formatted with a file system it cannot recognize (such as Linux's ext2) and could easily boot-up on its own; a very good reason for labeling your disks! (CAUTION: Never try booting your system with a disk you're not 100% sure of; unless you disconnect all hard disks and don't have any flash BIOS, since it might contain a nasty boot virus! )
[ Many floppy disks have the letters IHC in their OEM ID field. What kind of OEM Name is that? None. Someone at Microsoft decided that this was where they'd place a new pseudo-random type of identification to make sure that any information cached by 'Windows 9x' from one disk wouldn't be mixed up with info from a different one if you swapped disks. The whole string begins with five pseudo-random hex bytes, and always ends with the characters IHC. All floppy diskettes that are not write-protected will have any original OEM ID overwritten. Once Windows has written this string, it will remain the same for any future disk reads or writes. However, performing even a quick format under Windows, will change the five hex bytes every time.
Some have concluded the characters 'IHC' are the first three letters of the word "Chicago" in reverse order, since Chicago was the 'code name' for Windows 95 before it was ever released (it would have appeared as ' OGACIHC' on the hypothetical disk). Although certainly a possibility, I have no proof of that. Due to our interest in some very old Greek Manuscripts, we still can't help but see the 3 characters 'IHC' as an Iota, Eta and old style Sigma since this combination of letters was often used as an abbreviation for the Greek word "IHSUS" (Jesus). Just another of many coincidences in our lives.
REMEMBER: If you really want to preserve all of the contents of an important diskette, you can't even perform a simple Directory read under a Windows OS, UNLESS it is 'write-protected' and you know the drive's write-protect system is functioning correctly!
]

Back to TOC



Move: M range address
This command could be called COPY (not Move), since it only copies all the bytes from within the specified range to a new address.

Examples:
  1)   -m 7c00 7dff 600
Copies all 512 (200h) of the bytes between Offsets 7C00 and 7DFF (inclusive) to Offset 0600 and following.
  2)   -m 100 2ff 200

This second example shows it's very easy to overwrite much of the same source area you're copying from when using the Move command. However, DEBUG must store all the source bytes in Memory before writing them; otherwise, this example would cause a problem when overwriting an area it hadn't copied data from yet, if it were copying only one byte at a time from that source area! The example above copies all 512 bytes of offsets 100h through 2FFh (inclusive) to Offsets 0200h and following; overwriting the last 256 (2FF-200+1 hex) bytes of the source in the process. This is also true under real 16-bit DOS.

Note: If your Move command produces a situation where offset FFFFh has already been written to yet there's still more data to write, you may experience unexpected results! Remember, DEBUG is technically assigned to only one 64 KiB Segment. So, the data will wrap around to the beginning of the Segment, possibly overwriting some of the source bytes you told it to copy from! But other symptoms may occur as well, since the first area to be overwritten after wrapping around (00h through FFh) is sometimes used by DEBUG to keep track of itself. So do the math whenever copying bytes to a higher location in Memory, to be sure you don't run out of room at the end of the Segment.

Copying bytes to a lower location in the Segment is quite simple though; DEBUG could actually copy just one byte at a time in that direction and never overwrite a source byte before it was already copied.

Back to TOC



Name: N [pathname] [arglist]

This command can be used to load files into DEBUG's Memory after you have started the program, but it's main function is to create a new file under control of the Operating System which DEBUG can WRITE data to.
Normally, when you want to 'debug' a file, you'd start DEBUG with a command like this: C:\WINDOWS>debug test.com . But it's also possible to load a file into DEBUG's Memory from within DEBUG itself by using the 'N' command and then the 'L' command (with no parameters) like this:
-n c:\temp\test.com
-l

which will load the file test.com into DEBUG's Memory starting at location CS:0100 (you cannot specify any other location when using the L command like this!).

The 'N' command makes it quite easy to save data or an Assembly program created in DEBUG to a file on your hard drive!
For example, these commands (in bold; along with DEBUG's reponses):

    -n c:\temp\doswinok.com
    -a 100
    cs:0100 jmp 138
    cs:0102 db 0d,0a,"It's OK to run this "
    cs:0118 db "program under DOS or Windows!"
    cs:0135 db 0d,0a,24
    cs:0138 mov dx,102
    cs:013B mov ah,9
    cs:013D int 21
    cs:013F mov ax,4c01
    cs:0142 int 21
    cs:0144
    -rcx
    CX 0000
    :44
    -w
    Writing 00044 bytes [ 68 bytes in decimal ]
    -q

will create a 68-byte file called DOSWINOK.COM in the C:\TEMP folder; even when running DEBUG in a DOS-window. The file names, however, are still limited to DOS's eight characters plus three for the extension (an 8.3 filename as it's often called)!

Note: Unlike the other programs listed on this page, this one uses Function 4Ch instead of Function 00 of Interrupt 21h to terminate its execution. This is the preferred termination function for most DOS programs, because it can not only send a "Return Code" (an ERRORLEVEL value; of whatever is in the AL register), but will also close all open files and free all memory belonging to the process. When you use this function to terminate a program running under DEBUG though, it has a tendency to also terminate DEBUG itself; thus our reason for rarely using it here!

Homework: Follow the steps above to Assemble and save this program under DEBUG, then use DEBUG to debug it! Use the P(roceed) command to step through most of the instructions, since this will keep you from accidentally stepping into an INT(errupt) instruction! If you ever do use the T(race) command on an INT, you'll end up inside nests of BIOS routines which often crashes DEBUG!

Back to TOC



Register: R [register]
Entering ' r ' all by itself will display all of the 8086 register's contents and the next instruction which the IP register points to in both machine code and an unassembled (Assembly Language) form. For example, if you start DEBUG in a Windows 95B DOS-box with the command line:
>debug c:\windows\command\choice.com
and then enter an ' r ' at the first DEBUG prompt, DEBUG will display someting similar to this:
AX=0000  BX=0000  CX=1437  DX=0000  SP=FFFE  BP=0000  SI=0000  DI=0000
DS=0ED8 ES=0ED8 SS=0ED8 CS=0ED8 IP=0100 NV UP EI PL NZ NA PO NC
0ED8:0100 E90E01 JMP 0211
For an explanation of the names of the registers (AX, BX, CX, etc. and the Flag symbols: NV UP EI PL NZ NA PO NC), see the Appendix (The 8086 CPU Registers). The last line shows the next CPU instruction (actually the first in this case) to be executed, begins at memory location 100 hex (the offset) in Segment ED8 hex (0ED8:0100) and the Hex bytes E90E01 represent the actual binary machine code of the CPU instruction (JMP 0211 in Assembly language) that would be executed by DEBUG if you entered a Trace (t) or Proceed (p) command.

If you enter the ' r ' followed by the abbreviation for an 8086 register, such as: ' rcx ', then DEBUG will display only the contents of that register followed by a line with a colon symbol (:) on which you can enter a hex number to change the contents of that register. If you simply press the ENTER key, the contents remain the same. For example:

-rcx
CX 0100
:273


means the Register command was used to change the contents of the CX register from 0100 to 0273. The command rcx could be used again to verify the change had indeed taken place. If you type the letter f after an r: rf, this commands DEBUG to display all the FLAG register bits with a prompt on the same line which allows you to change any or none of the individual flag bits. For example, here's how you would display the flags and change just the Zero Flag bit from being cleared (a 0 bit) to being set (a 1 bit):

-rf
NV UP EI PL NZ NA PO NC -zr
-rf
NV UP EI PL ZR NA PO NC -
-

As you can see above the Zero Flag was changed from NZ (cleared) to ZR (set). See our Appendix: The FLAGS Register for an explanation of all the Flag abbreviations.
Back to TOC



Trace: T [=address] [number]
The T command is used to trace (step through) CPU instructions one at a time. If you enter the T command by itself, it will normally step through only ONE instruction beginning at the location specified by your CS:IP registers, halt program execution and then display all the CPU registers plus an unassembled version of the next instruction to be executed; this is the 'default' mode of the TRACE command. Say, however, you wanted DEBUG to trace and execute seven instructions beginning at address CS:0205; to do so, you would enter:

-t =205 7

Remember that the value for the number of instructions to execute must be given in hexadecimal just as all other values used in DEUBG. (Since the T command uses the "hardware trace mode" of the CPU, it's possible to step through instructions in a ROM - Read Only Memory - chip; or step into BIOS code which has been shadowed in read-only portions of Memory for decades now.) NOTE: If you find yourself stuck inside a long LOOP or REPeat string instruction, enter a P (Proceed) command and it will complete the operation and move to the next instruction.


Back to TOC



Proceed: P [=address] [number]
Proceed acts exactly the same as Debug's T (Trace) command for most instruction types; with these notable exceptions: Proceed will immediately execute all instructions (rather than stepping through each one) inside any subroutine CALL, LOOP, REPeat string instruction or any software INTerrupt. You can still step into an INT or execute all the code contained in a subroutine if you need to, but with the Proceed (P) command you are not required to do so.
This means Proceed will probably be the command of choice for most of debugging tasks, with Trace only being used to step through an unfamiliar subroutine or check the logic of the first few iterations of long REP string instructions or LOOPs. And it's a must use command when it comes to Interrupts!
Back to TOC



Write:
W [address] [drive] [firstsector] [number]

W A R N I N G
Do NOT experiment with the W - write command in DEBUG. It can be used effectively to create new files on your hard drive, but only if you use it properly. Trying to write directly to a sector on a hard disk would very RARELY be considered proper use of this command!
    Trying to write directly to a hard disk using sector numbers will most likely result in loss of data or even a non-booting system!(Although Windows XP and later prevent direct Sector writes to a hard disk, they are still allowed to floppy drive media, i.e., drive letters A: or B:)
The WRITE (W) command is often used to save a program to your hard disk from within DEBUG. But the only safe way to do so, especially under Windows, is by allowing the OS to decide where to physically create that file on the disk. This is done by first using the Name (N) command to set up an optional path and filename for the new file (or to overwrite one that already exists). DEBUG will automatically begin saving program or data bytes from Offset 0100 of the 64 KiB Segment the OS allocated for it. The only other requirement is to set the size of the file you wish to write by placing the total number of bytes in the combined BX and CX registers* before executing the WRITE command. The Register command is used to change the value in the CX register in the following example from our MyMBR Batch/Debug Script Program.

EXAMPLE:
After creating and running a small program inside of DEBUG which copies the Master Boot Record (MBR) to Offset 0000h through 01FFh, these DEBUG commands save the MBR to a file on the hard disk:
   -n mymbr.bin
-rcx
CX 0001
:200
-w 0
Writing 00200 bytes [ 512 bytes in decimal ]
-

The BX register had already been set to zero by a previous instruction, so the CX register was simply set to 200 and the WRITE command executed with an address of 0 (if no address is used, the Write command starts saving bytes at Offset 100).

The WRITE command can, however, be used in a relatively safe manner with Floppy disks. For example, you could use the Load (L) command:
l 7c00 0 0 1
to load the first sector of an MS-DOS or Windows floppy disk into DEBUG's memory at location 7C00, change some of the code and/or messages (if you know how to do so) and then use the 'W' command:
w 7c00 0 0 1
to write the changes back to the floppy disk's first sector.

___________________
*Although the BX and CX registers are often referenced in books on Assembly as BX:CX when they discuss this write command, note that these registers are not being used like Segment:Offset pairs in this case! They are a true combination of higher and lower bytes which form a 'double word' for a theoretical total of about 4 GB (FFFF FFFFh = 4,294,967,295 bytes) that could be written to a file! Whether or not this is true of all versions of DEBUG, under DOS 7.1, we've been able to load image files of several hundred KiB and write the whole file to a new location!
For example, if you load a 360 KiB image file into DEBUG at a DOS prompt, then check the registers, BX will equal 0005 and CX will contain A000. The major problem here though is the fact DEBUG uses CONVENTIONAL MEMORY, so trying to load a file greater than about 400 KiB is bound to elicit an "Insufficient Memory" error!
[
click here to go back to text above ]

Back to TOC


N O T E :
You may have missed this page on: The Windows™ DOS Stub Program. And there are more examples of many DEBUG commands on these x86 tutorial pages:
FIRE.com: Some MS-DEBUG Tricks and The EICAR Standard AV Test Program .





Updated: September 28, 2007. (28.09.2007)
Last Update: August 23, 2009. (23.08.2009)


A Guide to DEBUG (Page One)

The Starman's Realm Assembly Page