Điều khiển việc gọi hàm từ DLL không quản lý trong C# qua DllImport

Khi tích hợp thư viện bên ngoài (DLL không quản lý) vào ứng dụng C#, thuộc tính [DllImport] đóng vai trò then chốt trong việc khai báo và cấu hình cách gọi hàm gốc. Dưới đây là phân tích chi tiết ba tham số quan trọng thường bị hiểu sai: CharSet, CallingConvention, và SetLastError.

1. Tham số CharSet

Xác định cách biểu diễn chuỗi khi truyền giữa mã quản lý và không quản lý:

  • CharSet.Ansi: Chuỗi được chuyển thành byte theo bảng mã ANSI (mỗi ký tự 1 byte), phù hợp với các API cũ trên Windows.
  • CharSet.Unicode: Sử dụng UTF-16 (mỗi ký tự 2 byte), tương thích với đa số API hiện đại của Windows.
  • CharSet.Auto: Để CLR tự chọn — không khuyến khích vì hành vi phụ thuộc vào nền tảng và có thể gây lỗi không nhất quán.

2. Tham số CallingConvention

Quy định giao thức dọn dẹp ngăn xếp và thứ tự đẩy tham số — phải khớp chính xác với cách hàm trong DLL được biên dịch:

Giá trị Mô tả Ứng dụng điển hình
StdCall Hàm đích chịu trách nhiệm dọn ngăn xếp sau khi trả về. API hệ thống Windows (ví dụ: kernel32.dll, user32.dll)
Cdecl Người gọi (C#) dọn ngăn xếp — hỗ trợ hàm có số lượng tham số biến đổi. Hàm như printf trong msvcrt.dll
ThisCall Tham số this được truyền qua thanh ghi ECX; phần còn lại đẩy vào ngăn xếp. Phương thức thành viên của lớp C++ không export dưới dạng C-style
Winapi Biểu tượng viết tắt: ánh xạ tới StdCall trên Windows desktop, Cdecl trên Windows CE. Dùng khi cần khả năng tương thích chéo nền tảng

Ví dụ minh họa đúng chuẩn:

[DllImport("Sdtapi.dll", 
    CallingConvention = CallingConvention.StdCall,
    CharSet = CharSet.Ansi)]
public static extern int InitializeDevice(int portNumber);

[DllImport("msvcrt.dll", 
    CallingConvention = CallingConvention.Cdecl,
    CharSet = CharSet.Ansi)]
public static extern int printf(string format, params object[] args);

3. Tham số SetLastError

Khi đặt giá trị là true, Common Language Runtime (CLR) sẽ lưu giữ giá trị lỗi Win32 ngay sau khi hàm không quản lý thực thi xong — trước khi bất kỳ mã CLR nào khác có cơ hội ghi đè lên nó.

Nếu bỏ qua hoặc đặt false, giá trị trả về từ Marshal.GetLastWin32Error() có thể không phản ánh lỗi của hàm bạn vừa gọi, do các hàm nội bộ của .NET (ví dụ: kiểm tra null, marshaling chuỗi) cũng sử dụng SetLastError làm cơ chế báo lỗi.

Cách sử dụng an toàn:

[DllImport("Sdtapi.dll", SetLastError = true)]
public static extern int OpenReader(string devicePath);

int result = OpenReader("\\\\.\\COM3");
if (result == -1)
{
    int winErr = Marshal.GetLastWin32Error();
    throw new Win32Exception(winErr);
}

Thẻ: csharp dllimport interop win32-api pinvoke

Đăng vào ngày 4 tháng 6 lúc 00:37