1. Trang chủ >
  2. Công Nghệ Thông Tin >
  3. Kỹ thuật lập trình >

Chương 15:KHẢ NĂNG LIÊN TÁC MÃ LỆNH KHÔNG-ĐƯỢC-QUẢN-LÝ

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (7.15 MB, 706 trang )


585



586

Chương 15: Khả năng liên tác mã lệnh không-được-quản-lý



M



icrosoft .NET Framework là một nền cực kỳ cao vọng, là sự kết hợp của một ngôn

ngữ mới (C#), một bộ thực thi được-quản-lý (CLR), một nền cho các ứng dụng

Web (Microsoft ASP.NET), và một thư viện lớp rất lớn để xây dựng tất cả các kiểu

ứng dụng. Tuy nhiên, .NET Framework không lặp lại các tính năng có trong mã lệnh khôngđược-quản-lý. Hiện thời, .NET Framework không bao gồm mọi hàm có trong Win32 API,

trong khi nhiều doanh nghiệp đang sử dụng các giải pháp phức tạp được xây dựng với các

ngôn ngữ dựa-trên-COM như Microsoft Visual Basic 6 và Microsoft Visual C++ 6. May mắn

là Microsoft không có ý để những doanh nghiệp đó bỏ đi nền tảng mã lệnh mà họ đã xây dựng

khi chuyển sang nền .NET. Thay vào đó, .NET Framework được trang bị với các tính năng

interoperability (khả năng liên tác), cho phép bạn sử dụng lại mã lệnh cũ (legacy code) trong

các ứng dụng .NET Framework và truy xuất các assembly .NET như thể chúng là các thành

phần COM. Chương này sẽ thảo luận các vấn đề sau:













1.



Cách gọi các hàm thuộc DLL không-được-quản-lý (mục 15.1 đến 15.5).

Cách sử dụng thành phần COM trong ứng dụng .NET Framework (mục 15.6 đến 15.8).

Cách sử dụng điều kiểm ActiveX trong ứng dụng .NET Framework (mục 15.9).

Cách tạo một thành phần .NET sao cho một COM-client có thể sử dụng nó (mục 15.10).



Gọi một hàm trong một DLL không-được-quản-lý







Bạn cần gọi một hàm C trong một DLL. Đây có thể là một hàm của Win32 API

hoặc do bạn viết.







Khai báo một phương thức trong mã C# mà bạn sẽ sử dụng để truy xuất hàm

không-được-quản-lý. Khai báo phương thức này là static và extern, áp dụng

đặc tính System.Runtime.InteropServices.DllImportAttribute để chỉ định file

DLL và tên của hàm cần dùng.



Để sử dụng một hàm C từ một thư viện ngoài, bạn chỉ cần khai báo nó một cách thích hợp.

CRL sẽ tự động đảm trách phần việc còn lại, bao gồm việc tải DLL vào bộ nhớ khi hàm được

gọi và chuyển các thông số từ kiểu dữ liệu .NET thành kiểu dữ liệu C.

Dịch vụ .NET hỗ trợ việc thực thi xuyên-nền này có tên là PInvoke (Platform Invoke), và quá

trình này thường là trong suốt đối với người sử dụng. Thỉnh thoảng, bạn sẽ cần thực hiện thêm

một số việc, chẳng hạn cần hỗ trợ cấu trúc trong-bộ-nhớ (in-memory structure), callback, hay

chuỗi có thể thay đổi (mutable string).

PInvoke thường được sử dụng để truy xuất các hàm Win32 API, đặc biệt là các tính năng

không có trong các lớp được-quản-lý thuộc .NET Framework. Các ví dụ được trình bày trong

chương này sẽ sử dụng PInvoke theo cách này. Có ba thư viện chính trong Win32 API:





kernel32.dll gồm các hàm đặc-trưng-hệ-điều-hành như nạp tiến trình, chuyển ngữ

cảnh, nhập/xuất file và bộ nhớ.







user32.dll gồm các hàm dùng để thao tác cửa sổ, trình đơn, hộp thoại, biểu tượng,…



587

Chương 15: Khả năng liên tác mã lệnh không-được-quản-lý







gdi32.dll gồm các hàm đồ họa dùng để để vẽ trực tiếp lên cửa sổ, trình đơn, bề mặt

điều kiểm, cũng như để in ấn.



Ví dụ, xét các hàm Win32 API dùng để đọc và ghi các file INI, chẳng hạn

GetPrivateProfileString và WritePrivateProfileString trong kernell32.dll. .NET

Framework không có lớp nào bọc lấy chức năng này. Tuy nhiên, có thể nhập các hàm này

bằng đặc tính DllImportAttribute như sau:

[DllImport("kernel32.DLL", EntryPoint="WritePrivateProfileString")]

private static extern bool WritePrivateProfileString(string lpAppName,

string lpKeyName, string lpString, string lpFileName);



Các đối số trong phương thức WritePrivateProfileString phải tương thích với hàm trong

DLL, nếu không sẽ có lỗi khi gọi nó. Vì phương thức WritePrivateProfileString được khai

báo để tham chiếu đến một hàm trong DLL nên bạn không được viết mã cho nó. Phần

EntryPoint trong đặc tính DllImportAttribute trong ví dụ này là tùy chọn, vì tên phương

thức được khai báo đã trùng với tên của hàm trong thư viện ngoài.

Trong ví dụ sau, lớp IniFileWrapper khai báo các phương thức riêng tham chiếu tới các hàm

Win32 API, sau đó gọi chúng từ các phương thức công khai khác dựa trên file được chỉ định:

using System;

using System.Text;

using System.Runtime.InteropServices;

using System.Windows.Forms;



public class IniFileWrapper {



private string filename;



public string Filename {

get {return filename;}

}

public IniFileWrapper(string filename) {

this.filename = filename;

}

[DllImport("kernel32.dll", EntryPoint="GetPrivateProfileString")]

private static extern int GetPrivateProfileString(string lpAppName,

string lpKeyName, string lpDefault, StringBuilder lpReturnedString,

int nSize, string lpFileName);

[DllImport("kernel32.dll", EntryPoint="WritePrivateProfileString")]



588

Chương 15: Khả năng liên tác mã lệnh không-được-quản-lý



private static extern bool WritePrivateProfileString(

string lpAppName, string lpKeyName,

string lpString, string lpFileName);

// Bốn hàm sau không được sử dụng trong ví dụ này,

// nhưng được khai báo cho đầy đủ.

[DllImport("kernel32.dll", EntryPoint="WritePrivateProfileInt")]

private static extern int GetPrivateProfileInt(string lpAppName,

string lpKeyName, int iDefault, string lpFileName) ;

[DllImport("kernel32.dll", EntryPoint="GetPrivateProfileSection")]

private static extern int GetPrivateProfileSection(

string lpAppName, byte[] lpReturnedString,

int nSize, string lpFileName);

[DllImport("kernel32.dll", EntryPoint="WritePrivateProfileSection")]

private static extern bool WritePrivateProfileSection(

string lpAppName, byte[] data, string lpFileName);

[DllImport("kernel32.dll",

EntryPoint="GetPrivateProfileSectionNames")]

private static extern int GetPrivateProfileSectionNames(

byte[] lpReturnedString, int nSize, string lpFileName);



public string GetIniValue(string section, string key) {



StringBuilder buffer = new StringBuilder();

string sDefault = "";

if (GetPrivateProfileString(section, key, sDefault,

buffer, buffer.Capacity, filename) != 0) {

return buffer.ToString();

} else {

return null;

}

}



589

Chương 15: Khả năng liên tác mã lệnh không-được-quản-lý



public bool WriteIniValue(string section, string key, string value) {

return WritePrivateProfileString(section, key, value, filename);

}

}







Phương thức GetPrivateProfileString có một thông số thuộc kiểu StringBuilder

(lpReturnedString). Đó là vì chuỗi này phải là khả đổi—khi lời gọi hàm hoàn tất,

nó sẽ chứa thông tin của file INI. Bất cứ khi nào cần chuỗi khả đổi, bạn phải sử

dụng StringBuilder thay cho String. Thông thường, bạn cần tạo StringBuilder

với một bộ đệm ký tự có kích thước xác định, rồi truyền kích thước này ( nSize)

cho phương thức. Bạn có thể chỉ định số lượng ký tự trong phương thức khởi

dựng của StringBuilder (xem mục 2.1 để có thêm thông tin về StringBuilder).



Để thử nghiệm lớp IniFileWrapper, bạn hãy tạo một file INI chứa thông tin sau:

[SampleSection]

Key1=Value1

Key2=Value2

Key3=Value3



Và thực thi đoạn mã sau để đọc và ghi một giá trị trong file INI.

public class IniTest {

private static void Main() {



IniFileWrapper ini = new IniFileWrapper(

Application.StartupPath + "\\initest.ini");

string val = ini.GetIniValue("SampleSection", "Key1");

Console.WriteLine("Value of Key1 in [SampleSection] is: " + val);

ini.WriteIniValue("SampleSection", "Key1", "New Value");

val = ini.GetIniValue("SampleSection", "Key1");

Console.WriteLine("Value of Key1 in [SampleSection] is now: " +

val);

ini.WriteIniValue("SampleSection", "Key1", "Value1");

Console.ReadLine();

}

}



590

Chương 15: Khả năng liên tác mã lệnh không-được-quản-lý



Lấy handle của một điều kiểm, cửa sổ, hoặc file



2.





Bạn cần gọi một hàm không-được-quản-lý, và hàm này cần handle của một điều

kiểm, cửa sổ, hoặc file.







Nhiều lớp, bao gồm lớp FileStream và tất cả lớp dẫn xuất từ Control, trả về

handle (thuộc cấu trúc IntPtr) thông qua thuộc tính Handle. Cũng có lớp trả về

thông tin tương tự; ví dụ, lớp System.Diagnostics.Process có thêm thuộc tính

Process.MainWindowHandle ngoài thuộc tính Handle.



.NET Framework không che dấu các chi tiết nằm dưới, chẳng hạn handle dùng cho cửa sổ và

điều kiểm. Mặc dù không thường sử dụng thông tin này, bạn có thể lấy nó khi cần gọi một

hàm không-được-quản-lý và hàm này cần đến nó.

Xét ứng dụng dưới đây, form chính luôn hiển thị trên tất cả các cửa sổ khác bất kể nó có focus

hay không (có được chức năng này bằng cách thiết lập thuộc tính Form.TopMost là true).

Form còn có một Timer định kỳ gọi các hàm không-được-quản-lý GetForegroundWindow và

GetWindowText để lấy thông tin của cửa sổ hiện đang có focus. Ngoài ra, handle của form

chính được lấy thông qua thuộc tính Form.Handle, rồi được so sánh với handle của form hiện

đang tích cực để kiểm tra form chính đang có focus hay không.



Hình 15.1 Thông tin về cửa sổ đang tích cực

using System;

using System.Windows.Forms;

using System.Runtime.InteropServices;

using System.Text;

public class ActiveWindowInfo : System.Windows.Forms.Form {

// (Bỏ qua phần mã designer.)

private System.Windows.Forms.Timer tmrRefresh;

private System.Windows.Forms.Label lblCurrent;

private System.Windows.Forms.Label lblHandle;



591

Chương 15: Khả năng liên tác mã lệnh không-được-quản-lý

private System.Windows.Forms.Label lblCaption;

[DllImport("user32.dll")]

private static extern int GetForegroundWindow();

[DllImport("user32.dll")]

private static extern int GetWindowText(int hWnd, StringBuilder text,

int count);

private void tmrRefresh_Tick(object sender, System.EventArgs e) {

int chars = 256;

StringBuilder buff = new StringBuilder(chars);

int handle = GetForegroundWindow();



if (GetWindowText(handle, buff, chars) > 0) {



lblCaption.Text = buff.ToString();

lblHandle.Text = handle.ToString();

if (new IntPtr(handle) == this.Handle) {

lblCurrent.Text = "True";

} else {

lblCurrent.Text = "False";

}

}

}

}







Handle của form được quản lý một cách trong suốt đối với người dùng. Thay đổi

thuộc tính nào đó của form có thể khiến cho CRL tạo một handle mới. Do đó,

bạn nên luôn truy xuất handle ngay trước khi sử dụng nó (không nên giữ nó

trong một biến để sử dụng trong một thời gian dài).



3.



Gọi một hàm không-được-quản-lý có sử dụng cấu trúc









Bạn cần gọi một hàm không-được-quản-lý có thông số là một cấu trúc.

Định



nghĩa



cấu



trúc



trong







C#.



Sử



dụng



đặc



tính



System.Runtime.InteropServices.StructLayoutAttribute để cấu hình việc cấp bộ



nhớ



cho



cấu



trúc.



Sử



dụng



phương



thức



tĩnh



SizeOf



của



lớp



592

Chương 15: Khả năng liên tác mã lệnh không-được-quản-lý



System.Runtime.Interop.Marshal nếu muốn xác định kích thước của cấu trúc



theo byte.

Trong mã C# thuần túy, bạn không có khả năng trực tiếp kiểm soát việc cấp bộ nhớ. Thay vào

đó, CRL sẽ quyết định khi nào cần đưa dữ liệu vào bộ nhớ để tối ưu hóa hoạt động. Điều này

gây rắc rối khi làm việc với các hàm C, vì cấu trúc phải được trữ liên tục trong bộ nhớ. May

mắn là .NET đã giải quyết vấn đề này bằng đặc tính StructLayoutAttribute, cho phép bạn chỉ

định các thành viên của một lớp hay một cấu trúc cho trước sẽ được sắp xếp trong bộ nhớ như

thế nào.

Ví dụ, xét hàm GetVersionEx trong thư viện kernel32.dll. Hàm này nhận một con trỏ chỉ tới

cấu trúc OSVERSIONINFO và sử dụng nó để trả về thông tin phiên bản của hệ điều hành. Để sử

dụng cấu trúc OSVERSIONINFO trong mã C#, bạn phải định nghĩa nó với đặc tính

StructLayoutAttribute như sau:

[StructLayout(LayoutKind.Sequential)]

public class OSVersionInfo {



public int dwOSVersionInfoSize;

public int dwMajorVersion;

public int dwMinorVersion;

public int dwBuildNumber;

public int dwPlatformId;

[MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]

public String szCSDVersion;

}



Chú ý rằng, cấu trúc này cũng sử dụng đặc tính System.Runtime.InteropServices.

MarshalAsAttribute (cần cho các chuỗi có kích thước không đổi). Ở đây,

MarshalAsAttribute chỉ định chuỗi sẽ được truyền bằng trị và sẽ chứa một bộ đệm gồm 128

ký tự được chỉ định trong cấu trúc OSVersionInfo. Trong ví dụ này, LayoutKind.Sequential

được sử dụng, nghĩa là các kiểu dữ liệu trong cấu trúc được bố trí theo thứ tự mà chúng được

liệt kê trong cấu trúc hoặc lớp.

Ngoài LayoutKind.Sequential, bạn có thể sử dụng LayoutKind.Explicit. Trong trường hợp

này, bạn phải sử dụng FieldOffsetAttribute để định nghĩa độ dời của các trường. Cách này

hữu ích khi bạn muốn lưu trữ các trường một cách linh động hơn, hoặc bạn muốn bỏ qua

(không sử dụng) trường nào đó. Ví dụ sau định nghĩa lớp OSVersionInfo với

LayoutKind.Explicit.

[StructLayout(LayoutKind.Explicit)]

public class OSVersionInfo {

[FieldOffset(0)] public int dwOSVersionInfoSize;



593

Chương 15: Khả năng liên tác mã lệnh không-được-quản-lý

[FieldOffset(4)] public int dwMajorVersion;

[FieldOffset(8)] public int dwMinorVersion;

[FieldOffset(12)] public int dwBuildNumber;

[FieldOffset(16)] public int dwPlatformId;

[MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]

[FieldOffset(20)] public String szCSDVersion;

}



Sau khi đã định nghĩa cấu trúc được sử dụng bởi hàm GetVersionEx, bạn có thể khai báo hàm

này và sử dụng nó. Ứng dụng dưới đây sẽ trình bày toàn bộ mã lệnh cần thiết. Chú ý,

InAttribute và OutAttribute được áp dụng cho OSVersionInfo để biết rằng marshalling sẽ

được thực hiện trên cấu trúc này khi nó được truyền cho hàm và khi nó được trả về từ hàm.

Ngoài ra, phương thức Marshal.SizeOf được sử dụng để tính kích thước của cấu trúc trong bộ

nhớ.

using System;

using System.Runtime.InteropServices;

public class CallWithStructure {

// (Bỏ qua lớp OSVersionInfo.)

[DllImport("kernel32.dll")]

public static extern bool GetVersionEx([In, Out] OSVersionInfo osvi);

private static void Main() {

OSVersionInfo osvi = new OSVersionInfo();

osvi.dwOSVersionInfoSize = Marshal.SizeOf(osvi);

GetVersionEx(osvi);

Console.WriteLine("Class size: " + osvi.dwOSVersionInfoSize);

Console.WriteLine("Major Version: " + osvi.dwMajorVersion);

Console.WriteLine("Minor Version: " + osvi.dwMinorVersion);

Console.WriteLine("Build Number: " + osvi.dwBuildNumber);

Console.WriteLine("Platform Id: " + osvi.dwPlatformId);

Console.WriteLine("CSD Version: " + osvi.szCSDVersion);

Console.WriteLine("Platform: " + Environment.OSVersion.Platform);

Console.WriteLine( "Version: " + Environment.OSVersion.Version);

Console.ReadLine();

}



594

Chương 15: Khả năng liên tác mã lệnh không-được-quản-lý



}



Nếu chạy ứng dụng này trên hệ thống Windows XP, bạn sẽ thấy thông tin như sau:

Class size: 148

Major Version: 5

Minor Version: 1

Build Number: 2600

Platform Id: 2

CSD Version: Service Pack 1

Platform: Win32NT

Version: 5.1.2600.0



Gọi một hàm không-được-quản-lý có sử dụng callback



4.







Bạn cần gọi một hàm không-được-quản-lý và cho phép nó gọi một hàm khác.

Tạo một ủy nhiệm cho callback. Sử dụng ủy nhiệm này khi định nghĩa và sử

dụng hàm không-được-quản-lý.



Nhiều hàm của Win32 API sử dụng callback. Ví dụ, nếu muốn lấy tên của tất cả các cửa sổ

đang mở, bạn có thể sử dụng hàm EnumWindows trong thư viện user32.dll . Khi gọi

EnumWindows, bạn cần truyền cho nó một con trỏ chỉ đến một hàm khác trong mã lệnh của bạn.

Hệ điều hành Windows sau đó sẽ gọi hàm này mỗi khi tìm thấy một cửa sổ đang mở, và truyền

handle của cửa sổ cho nó.

.NET Framework cho phép bạn quản lý việc sử dụng callback mà không cần các con trỏ và

các khối mã không an toàn. Thay vào đó, bạn có thể định nghĩa và sử dụng một ủy nhiệm chỉ

đến hàm callback. Khi bạn truyền ủy nhiệm cho hàm EnumWindows, CLR sẽ tự động marshal ủy

nhiệm thành con trỏ hàm không-được-quản-lý như mong muốn.

Ví dụ dưới đây sử dụng EnumWindows cùng với một callback để hiển thị tên của tất cả các cửa

sổ đang mở.

using System;

using System.Text;

using System.Runtime.InteropServices;

public class GetWindows {

// Chữ ký cho hàm callback.

public delegate bool CallBack(int hwnd, int lParam);

// Hàm không-được-quản-lý sẽ kích hoạt callback



595

Chương 15: Khả năng liên tác mã lệnh không-được-quản-lý

// khi duyệt qua các cửa sổ đang mở.

[DllImport("user32.dll")]

public static extern int EnumWindows(CallBack callback, int param);

[DllImport("user32.dll")]

public static extern int GetWindowText(int hWnd,

StringBuilder lpString, int nMaxCount);

private static void Main() {

CallBack callBack = new CallBack(DisplayWindowInfo);

// Yêu cầu hệ điều hành duyệt qua các cửa sổ đang mở,

// kích hoạt callback với handle của mỗi cửa sổ.

EnumWindows(callBack, 0);

Console.ReadLine();

}

// Hàm sẽ nhận callback. Thông số thứ hai

// không được sử dụng nhưng phải được khai báo để

// tương thích với chữ ký của callback.

public static bool DisplayWindowInfo(int hWnd, int lParam) {

int chars = 100;

StringBuilder buf = new StringBuilder(chars);

if (GetWindowText(hWnd, buf, chars) != 0) {

Console.WriteLine(buf);

}

return true;

}

}



5.



Lấy thông tin lỗi không-được-quản-lý







Bạn cần truy xuất thông tin lỗi (mã lỗi hoặc thông điệp mô tả lỗi) giải thích tại

sao một lời gọi Win32 API thất bại.







Trong phần khai báo của hàm không-được-quản-lý, thiết lập trường

SetLastError của đặc tính DllImportAttribute là true. Nếu có lỗi khi thực thi, gọi



Xem Thêm
Tải bản đầy đủ (.pdf) (706 trang)

×