Tổng kết về việc gọi DLL viết bằng C++ từ C#

Gần đây, tôi đã phát triển một DLL bằng C++ để được gọi từ ứng dụng web, được xây dựng bằng C#. Việc tạo DLL bằng C++ khá đơn giản, đồng thời tôi cũng hoàn thành một EXE bằng C++ để gọi và gỡ lỗi DLL. Mọi thứ đều ổn! Sau đó, khi chuyển DLL đến nơi thực hiện C# để kiểm thử, tôi nhận thấy hoặc là việc gọi thất bại, hoặc là không nhận được dữ liệu. Việc C# gọi DLL bằng C++ có thực sự phức tạp đến vậy không?

DLL bằng C++ cung cấp một chức năng, chuyển đổi một chuỗi ký tự và tạo ra một chuỗi ký tự khác, sau đó hiển thị chuỗi đã chuyển đổi này trên trang web. Ban đầu, giao diện C++ được thiết kế như sau:

1 /*
2  * Chức năng: 
3  * Tham số: (in) duLieuVao Chuỗi ký tự do người dùng nhập
4  * (in) doDaiVao Độ dài của chuỗi ký tự đầu vào
5  * (out) duLieuRa Chuỗi ký tự đầu ra
6  * (in)  kichThuocLonNhat Độ dài tối đa của chuỗi đầu ra
7 */
8 BOOL XuLyChuoi(unsigned char *duLieuVao,int doDaiVao,unsigned char *duLieuRa,int kichThuocLonNhat);

Hàm này chỉ có một giá trị trả về, thông qua tham số thứ ba: duLieuRa, các tham số còn lại đều là đầu vào.

C# gọi giao diện này như sau:

1 public static extern char[] XuLyChuoi(ref char[] duLieuVao, ref int doDaiVao,ref char[] duLieuRa,ref int kichThuocLonNhat);

Biên dịch không gặp vấn đề, nhưng khi chạy, tôi phát hiện ra rằng các giá trị được truyền qua iInLen và iLen không chính xác. Bởi vì hàm trong DLL đã thực hiện kiểm tra tính hợp lệ của tham số, nếu độ dài không hợp lệ sẽ trả về ngay lập tức. Để kiểm tra, tôi đã thêm MessageBox để hiển thị hai giá trị Len này, và thông báo cho thấy hai tham số này giống như các số ngẫu nhiên, mỗi lần chạy giá trị lại khác nhau, nhưng dữ liệu đầu vào luôn là cố định. Nếu không sử dụng ref cho các tham số khác ngoài tham số thứ ba, các giá trị Len được truyền vào là bình thường. Nhưng khi chạy lại gây ra ngoại lệ! Tôi đã tìm kiếm lâu nhưng không tìm ra nguyên nhân, cuối cùng đành phải sửa đổi giao diện của DLL: không dùng tham số để truyền giá trị, thay vào đó sử dụng giá trị trả về.

Giao diện C++ trong DLL được sửa đổi thành:

1 char *XuLyChuoi(unsigned char *duLieuVao,int doDaiVao);

C# gọi giao diện này với hai phương thức thử nghiệm sau:

1 (1) public static extern char[] XuLyChuoi(char[] duLieuVao, int doDaiVao);
2 (2) public static extern string XuLyChuoi(char[] duLieuVao, int doDaiVao);

Cả hai đều gây ra ngoại lệ khi chạy đến giao diện gọi DLL. Tôi đã viết một chương trình kiểm thử C# gọi DLL, cũng gặp ngoại lệ tương tự, thông báo ngoại lệ là: Additional information: Cannot marshal 'return value': Invalid managed/unmanaged type combination.

Cuối cùng, tôi đã sửa đổi phương thức gọi thành:

1 public static extern StringBuilder XuLyChuoi(char[] duLieuVao, int doDaiVao);

Với cách gọi này, khi gọi giao diện DLL không còn gây ra ngoại lệ, nhưng giá trị trả về luôn là rỗng. Tôi nghi ngờ là giao diện trong DLL không trả về giá trị, vì vậy tôi đã thêm một MessageBox trước khi trả về dữ liệu trong giao diện C++ của DLL. Sau khi chạy, tôi thấy rằng giao diện C++ trong DLL được gọi từ C# đã hiển thị dữ liệu bình thường. Dữ liệu này giống hệt khi gọi DLL từ EXE bằng C++. Nhưng EXE bằng C++ có thể nhận được dữ liệu, trong khi chương trình web bằng C# chỉ nhận được một giá trị NULL.

Cuối cùng, tôi nghi ngờ rằng không gian chạy của chương trình C++ và C# khác nhau, dẫn đến vấn đề này. Bởi vì trong giao diện C++ của DLL, giá trị trả về được định nghĩa trong hàm giao diện, là một mảng; tôi nghi ngờ rằng mảng này đã bị giải phóng khi C# gọi và trả về. Tôi đã di chuyển định nghĩa mảng này ra ngoài giao diện, định nghĩa thành một biến toàn cục, các phần khác không thay đổi. Lúc này, tôi phát hiện rằng chương trình web bằng C# gọi giao diện trong DLL bằng C++ cũng có thể nhận được dữ liệu chính xác.

Tổng kết: Khi C# gọi DLL bằng C++, cần lưu ý hai vấn đề: (1) Chuyển đổi giữa các kiểu dữ liệu, đặc biệt là con trỏ; (2) Phạm vi chạy. Tôi vẫn chưa giải quyết được một vấn đề, đó là việc truyền giá trị bằng ref. Tại sao khi truyền int bằng ref đến DLL bằng C++, dữ liệu lại thay đổi? Tôi gần như không sử dụng C# bao giờ, nên cũng không thể giải quyết được.

Giải pháp cuối cùng: Khôi phục lại giao diện ban đầu với bốn tham số, khai báo giao diện trong C# như sau:

1 public static extern bool XuLyChuoi(char[] duLieuVao, int doDaiVao, [Out,MarshalAs(UnmanagedType.LPArray)]char[] duLieuRa, int kichThuocLonNhat);

Thẻ: C# C++ dll P/Invoke marshaling

Đăng vào ngày 9 tháng 6 lúc 01:01