581 Chương 14: Mật mã // Trích khóa phiên từ dữ liệu trao đổi bằng // phương thức DeformatKeyExchange. byte[] sessionKey = DeformatKeyExchange(exchangeData, cspParams); // Hiển thị khóa phiên vừa được trích. Console.WriteLine("Session Key at Destination = {0}\n\r", BitConverter.ToString(sessionKey)); // Nhấn Enter để kết thúc. Console.ReadLine(); } } // Phương thức dùng để mật hóa và định dạng khóa phiên đối xứng. // Để mật hóa khóa phiên, chúng ta cần truy xuất PUBLIC KEY // của người nhận (trong cấu trúc RSAParameters). private static byte[] FormatKeyExchange(byte[] sessionKey, RSAParameters rsaParams) { // Tạo một giải thuật bất đối xứng RSA. using (RSACryptoServiceProvider asymAlg = new RSACryptoServiceProvider()) { // Nạp PUBLIC KEY của người nhận. asymAlg.ImportParameters(rsaParams); // Tạo một RSA OAEP formatter để định dạng dữ liệu trao đổi. RSAOAEPKeyExchangeFormatter formatter = new RSAOAEPKeyExchangeFormatter(); // Chỉ định giải thuật RSA dùng để mật hóa khóa phiên. formatter.SetKey(asymAlg); // Mật hóa và định dạng khóa phiên rồi trả về kết quả. return formatter.CreateKeyExchange(sessionKey); } 582 Chương 14: Mật mã } // Phương thức dùng để giải mật hóa dữ liệu trao đổi và trích khóa phiên // đối xứng. Để giải mật hóa dữ liệu trao đổi, chúng ta cần truy xuất // PRIVATE KEY (từ kho chứa khóa do đối số cspParams chỉ định). private static byte[] DeformatKeyExchange(byte[] exchangeData, CspParameters cspParams) { // Tạo một giải thuật bất đối xứng RSA. using (RSACryptoServiceProvider asymAlg = new RSACryptoServiceProvider(cspParams)) { // Tạo một RSA OAEP deformatter để trích khóa phiên // từ dữ liệu trao đổi. RSAOAEPKeyExchangeDeformatter deformatter = new RSAOAEPKeyExchangeDeformatter(); // Chỉ định giải thuật RSA dùng để giải mật hóa dữ liệu trao đổi. deformatter.SetKey(asymAlg); // Giải mật hóa dữ liệu trao đổi và trả về khóa phiên. return deformatter.DecryptKeyExchange(exchangeData); } } } Chạy KeyExchangeExample sẽ sinh ra kết xuất tương tự như sau: Session Key at Source = EE-5B-16-5B-AC-46-3D-72-CC-73-19-D9-0B-8A-19-E2-A6-02-13- BE-F8-CE-DF-40 Exchange Data = 60-FA-3B-63-41-25-F1-AD-08-F9-FC-67-CD-C6-FB-3E-0F-C3-62- C6-3F-5C-C0-7E-D1-60-2D-19-58-07-EE-BB-7C-53-A5-C2-FB-CA-D7-64-FF-BA-33-77-AC-52- 87-5F-75-E7-57-99-01-90-CD-70-36-1E-53-0C-82-C6-CE-B8-BC-8B-C9-39-6F-29-39-5F-6C- A6-43-E5-B0-A1-42-46-1C-9B-1C-72-EB-5E-67-06-44-C0-CE-AB-70-B8-39-8E-9F-01-E8-49- 51-36-D6-27-09-94-DA-42-CE-79-C2-72-88-4D-CE-63-B4-A0-AC-07-AF-26-A7-76-DE-21-BE- A5 Session Key at Destination = EE-5B-16-5B-AC-46-3D-72-CC-73-19-D9-0B-8A-19-E2-A6- 02-13-BE-F8-CE-DF-40 583 Chương 14: Mật mã 15 584 Chương 15:KHẢ NĂNG LIÊN TÁC MÃ LỆNH KHÔNG- ĐƯỢC-QUẢN-LÝ 585 586 Chương 15: Khả năng liên tác mã lệnh không-được-quản-lý 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: M 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). 1. 1. G i m t hàm trong m t DLL không-đ c-qu n-lýọ ộ ộ ượ ả 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ý 2. 2. L y handle c a m t đi u ki m, c a s , ho c fileấ ủ ộ ề ể ử ổ ặ L y handle c a m t đi u ki m, c a s , ho c fileấ ủ ộ ề ể ử ổ ặ 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; . 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 thảo luận các vấn đề sau: M 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. 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