1. Trang chủ
  2. » Công Nghệ Thông Tin

Các giải pháp lập trình CSharp- P71 pptx

10 322 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 10
Dung lượng 2,63 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

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ư

Trang 1

[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-đ G i m t hàm không-đ ọ ộ ọ ộ ượ ượ c-qu n-lý có s d ng c u trúc 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 mã 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

Trang 2

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;

Trang 3

[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();

}

Trang 4

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

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

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

Trang 5

[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-đ L y thông tin l i không-đ ấ ấ ỗ ỗ ượ ượ c-qu n-lý 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 DllImportAttributetrue Nếu có lỗi khi thực thi, gọi

Trang 6

phương thức tĩnh Marshal.GetLastWin32Error để truy xuất mã lỗi Để lấy thông điệp mô tả một mã lỗi cụ thể, sử dụng hàm không-được-quản-lý FormatMessage.

Bạn không thể trực tiếp lấy thông tin lỗi bằng hàm không-được-quản-lý GetLastError Vấn đề

là, mã lỗi do GetLastError trả về có thể không phản ánh lỗi do hàm không-được-quản-lý gây

ra Thay vào đó, nó có thể được thiết lập bởi các lớp NET Framework khác hoặc CLR Bạn có

thể lấy thông tin lỗi một cách an toàn bằng phương thức tĩnh Marshal.GetLastWin32Error Phương thức này cần được gọi ngay sau lời gọi hàm không-được-quản-lý, và nó sẽ trả về thông tin lỗi chỉ một lần (các lần gọi GetLastWin32Error sau đó sẽ trả về mã lỗi 127) Ngoài

ra, bạn phải thiết lập trường SetLastError của đặc tính DllImportAttribute là true, cho biết những lỗi do hàm này sinh ra sẽ được ghi nhận.

[DllImport("user32.dll", SetLastError=true)]

Sau đó, bạn có thể sử dụng hàm FormatMessage trong thư viện kernel32.dll để lấy thông điệp

mô tả lỗi từ mã lỗi Win32.

Ví dụ, ứng dụng dưới đây muốn hiển thị một MessageBox, nhưng lại sử dụng handle không đúng Mã lỗi được lấy bằng Marshal.GetLastWin32Error, và thông điệp mô tả lỗi được lấy bằng FormatMessage.

using System;

using System.Runtime.InteropServices;

public class TestError {

[DllImport("kernel32.dll")]

private unsafe static extern int FormatMessage(int dwFlags,

int lpSource, int dwMessageId, int dwLanguageId,

ref String lpBuffer, int nSize, int Arguments);

[DllImport("user32.dll", SetLastError=true)]

public static extern int MessageBox(int hWnd, string pText,

string pCaption, int uType);

private static void Main() {

int badWindowHandle = 453;

MessageBox(badWindowHandle, "Message", "Caption", 0);

int errorCode = Marshal.GetLastWin32Error();

Console.WriteLine(errorCode);

Console.WriteLine(GetErrorMessage(errorCode));

Trang 7

Console.ReadLine();

}

// GetErrorMessage trả về thông điệp lỗi

// tương ứng với mã lỗi errorCode

public static string GetErrorMessage(int errorCode) {

int FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;

int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;

int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;

int messageSize = 255;

string lpMsgBuf = "";

int dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER |

FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;

int retVal = FormatMessage(dwFlags, 0, errorCode, 0,

ref lpMsgBuf, messageSize, 0);

if (0 == retVal) {

return null;

} else {

return lpMsgBuf;

}

}

}

Sau đây là kết xuất của ứng dụng:

1400

Invalid window handle

6. S d ng thành ph n COM trong NET-client S d ng thành ph n COM trong NET-client ử ụ ử ụ ầ ầ

Bạn cần sử dụng thành phần COM trong một NET-client.

Sử dụng một Primary Interop Assembly, nếu có Nếu không, tạo một Runtime

Callable Wrapper bằng Type Library Importer (Tlbimp.exe), hoặc tính năng Add Reference trong Visual Studio NET.

Trang 8

.NET Framework hỗ trợ khả năng liên tác với COM Để cho phép NET-client tương tác với một thành phần COM, NET sử dụng một Runtime Callable Wrapper (RCW)—một lớp proxy đặc biệt đóng vai trò trung gian giữa mã NET và thành phần COM RCW sẽ xử lý tất cả chi tiết, bao gồm việc marshal các kiểu dữ liệu, sử dụng các giao diện COM truyền thống, và xử

lý các sự kiện COM.

Bạn có ba tùy chọn khi sử dụng một RCW:

Lấy RCW từ tác giả của thành phần COM Trong trường hợp này, RCW được gọi là một

Primary Interop Assembly (PIA).

Tạo RCW bằng tiện ích dòng lệnh Tlbimp.exe hoặc bằng Visual Studio NET.

Tự tạo RCW thông qua các kiểu trong không gian tên System.Runtime InteropServices (cách này cực kỳ dài dòng và phức tạp).

Nếu muốn sử dụng Visual Studio NET để tạo RCW, bạn chỉ cần chọn Project | Add Reference

và sau đó chọn thành phần thích hợp trong thẻ COM Interop Assembly sẽ được tạo ra và được thêm vào các tham chiếu của dự án Sau đó, bạn có thể sử dụng Object Browser để khảo sát

các không gian tên và các lớp có trong thành phần này.

Hình 15.2 Sử dụng Object Browser

để xem các không gian tên và các lớp có trong thành phần COM

Nếu không sử dụng Visual Studio NET, bạn có thể tạo một Wrapper Assembly bằng công cụ

Tlbimp.exe, chỉ cần truyền cho nó tên file chứa thành phần COM Ví dụ, dòng lệnh sau đây sẽ

tạo một RCW với tên file và không gian tên mặc định, giả sử file MyCOMComponent.dll đang

nằm trong thư mục hiện hành.

tlbimp MyCOMComponent.dll

Giả sử MyCOMComponent chứa một thư viện kiểu có tên là MyClasses, file RCW sẽ có tên là

MyClasses.dll và sẽ trưng các lớp của nó thông qua một không gian tên có tên là MyClasses

Bạn có thể cấu hình các tùy chọn này bằng các đối số dòng lệnh (được mô tả chi tiết trong tài

Trang 9

/namespace:[Namespace] để chỉ định không gian tên Bạn cũng có thể sử dụng

/keyfile[keyfilename] để tạo tên mạnh cho assembly, và đặt nó vào Global Assembly Cache (GAC) Sử dụng /primary để tạo một PIA.

Nếu có thể, bạn nên sử dụng PIA thay vì tạo RCW PIA hoạt động đáng tin cậy hơn vì được

tạo ra bởi nhà phát hành thành phần, và cũng có thể chứa một số tính năng nâng cao Nếu một

PIA được đăng ký với hệ thống cho một thành phần COM thì Visual Studio NET sẽ tự động

sử dụng PIA đó khi bạn thêm một tham chiếu đến thành phần COM Ví dụ, adodb.dll (có trong NET Framework) cho phép bạn sử dụng các đối tượng ADO truyền thống Nếu bạn thêm một tham chiếu đến thành phần Microsoft ActiveX Data Objects, Interop Assembly này

sẽ tự động được sử dụng, mà không sinh ra một RCW mới Tương tự, Microsoft Office XP cung cấp một PIA để nâng cao sự hỗ trợ NET cho Office Automation (bạn có thể tải PIA này

tại địa chỉ [http://msdn.microsoft.com/downloads/list/office.asp]).

Ví dụ sau minh họa cách sử dụng COM Interop để truy xuất các đối tượng ADO truyền thống trong ứng dụng NET Framework.

using System;

public class ADOClassic {

private static void Main() {

ADODB.Connection con = new ADODB.Connection();

string connectionString = "Provider=SQLOLEDB.1;" +

"Data Source=localhost;" +

"Initial Catalog=Northwind;Integrated Security=SSPI";

con.Open(connectionString, null, null, 0);

object recordsAffected;

ADODB.Recordset rs = con.Execute("SELECT * From Customers",

out recordsAffected, 0);

while (rs.EOF != true) {

Console.WriteLine(rs.Fields["CustomerID"].Value);

rs.MoveNext();

}

Console.ReadLine();

}

}

Trang 10

7. Gi i phóng nhanh thành ph n COM Gi i phóng nhanh thành ph n COM ả ả ầ ầ

Bạn cần bảo đảm một thành phần COM được xóa khỏi bộ nhớ ngay tức thì,

không phải chờ bộ thu gom rác (Garbage Collector) làm việc Hoặc bạn muốn bảo đảm các đối tượng COM được giải phóng theo một thứ tự xác định

Sử dụng phương thức tĩnh Marshal.ReleaseComObject và truyền RCW thích hợp

để giải phóng tham chiếu đến đối tượng COM nằm dưới.

COM sẽ đếm các tham chiếu đến đối tượng để xác định khi nào đối tượng sẽ được giải phóng

Khi bạn sử dụng một RCW, tham chiếu sẽ được giữ cả khi biến đối tượng vượt khỏi tầm vực Tham chiếu này chỉ được giải phóng khi bộ thu gom rác giải phóng đối tượng RCW Do đó, bạn không thể kiểm soát việc các đối tượng COM sẽ được giải phóng khi nào hoặc theo thứ tự

nào.

Để vượt qua hạn chế này, bạn có thể sử dụng phương thức Marshal.ReleaseComObject Trong

ví dụ ở mục 15.6, bạn hãy thêm hai dòng sau vào cuối mã lệnh để giải phóng đối tượng

Recordset và Connection nằm dưới.

Marshal.ReleaseComObject(rs);

Marshal.ReleaseComObject(con);

Về mặt kỹ thuật, phương thức ReleaseComObject không thực sự giải phóng đối

tượng COM, nó chỉ giảm số lượng tham chiếu đến đối tượng Nếu số tham chiếu

là 0, đối tượng COM sẽ được giải phóng Tuy nhiên, nếu cùng thể hiện của một đối tượng COM được sử dụng tại nhiều mẩu mã lệnh, nó phải được giải phóng ở

các nơi đó trước khi được giải phóng khỏi bộ nhớ.

8. S d ng thông s tùy ch n S d ng thông s tùy ch n ử ụ ử ụ ố ố ọ ọ

Bạn cần gọi một phương thức trong thành phần COM mà không phải truyền tất

cả các thông số cần thiết.

Sử dụng trường Type.Missing.

Hầu hết các phương thức trong NET Framework đều được nạp chồng nhiều lần để bạn có thể gọi phiên bản yêu cầu chỉ những thông số do bạn cung cấp Mặt khác, COM không hỗ trợ việc nạp chồng phương thức Thay vào đó, các thành phần COM thường sử dụng các phương thức với một danh sách dài các thông số tùy chọn Không may là, C# không hỗ trợ thông số tùy

chọn, nghĩa là người phát triển phải cung cấp thêm các giá trị không cần thiết khi truy xuất

một thành phần COM Và vì các thông số COM thường được truyền bằng tham chiếu nên mã

lệnh của bạn không thể truyền một tham chiếu null, mà phải khai báo một biến đối tượng và rồi truyền biến đó.

Bạn có thể giảm nhẹ vấn đề đến một chừng mực nào đó bằng cách cung cấp trường

Type.Missing bất cứ khi nào muốn bỏ qua một thông số tùy chọn Nếu cần truyền một thông

Ngày đăng: 08/07/2014, 17:20

HÌNH ẢNH LIÊN QUAN

Hình 15.2 Sử dụng Object Browser - Các giải pháp lập trình CSharp- P71 pptx
Hình 15.2 Sử dụng Object Browser (Trang 8)

TỪ KHÓA LIÊN QUAN