2012-04-25 42 views
18

Vì vậy, đã có nhiều biến thể của câu hỏi này, và sau khi xem xét một số câu hỏi tôi vẫn không thể hiểu được.C# gọi hàm C trả về cấu trúc với mảng char có kích thước cố định

Đây là mã C:

typedef struct 
{ 
unsigned long Identifier; 
char Name[128]; 
} Frame; 

Frame GetFrame(int index); 

Đây là mã C#:

struct Frame 
{ 
    public ulong Identifier; 
    [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = 128)] 
    public char[] Name; 
} 

[DllImport("XNETDB.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 
private static extern Frame GetFrame(int index); 

Đây là nỗ lực cuối cùng tôi đã cố gắng trong C#, và nó có vẻ khá hợp lý, nhưng tôi nhận được lỗi "Chữ ký của phương thức không tương thích với PInvoke". Vì vậy, tôi là loại mất về những gì để thử tiếp theo. Bất kỳ trợ giúp được đánh giá cao.

Cảm ơn, Kevin

Cập nhậtKevin nói thêm đây là một chỉnh sửa câu trả lời của tôi

tôi thay vì phải thay đổi mã C của tôi:

void GetFrame(int index, Frame * f); 

và sử dụng thay thế cho C# :

struct Frame 
{ 
    public uint Identifier; 
    [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 128)] 
    public string Name; 
} 

[DllImport("XNETDB.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 
private static extern void GetFrame(int index, ref Frame f); 
+1

Bạn có thấy điều này http://social.msdn.microsoft.com/Forums/en-AU/csharplanguage/thread/6e0ad208-5c8b-48ac-a45e-cfaf7f52221b? – MilkyWayJoe

+0

Định nghĩa hàm tồn tại. Mã C được liệt kê chỉ từ tệp tiêu đề. –

+0

Tôi thấy rằng và cố gắng tư nhân tĩnh extern IntPtr GetFrame (int index); nhưng gọi đó là ném lỗi "Đã cố đọc hoặc ghi bộ nhớ được bảo vệ". –

Trả lời

8

Vấn đề là hàm gốc trả về kiểu không thể chuyển thành giá trị trả về.

http://msdn.microsoft.com/en-us/library/ef4c3t39.aspx

P/Invoke không thể có các loại phi blittable như một giá trị trả về.

Bạn không thể p/Gọi phương thức đó. [EDITĐó là thực sự tốt, xem JaredPar's answer]

Trở 132 byte bởi giá trị là một ý tưởng tồi. Nếu mã gốc này là của bạn, tôi sẽ sửa nó. Bạn có thể sửa nó bằng cách phân bổ 132 byte và trả về một con trỏ. Sau đó, thêm một phương thức FreeFrame để giải phóng bộ nhớ đó. Bây giờ nó có thể được p/viện dẫn.

Cách khác, bạn có thể thay đổi nó để chấp nhận một con trỏ vào bộ nhớ Khung rằng nó sẽ điền vào.

+0

+1, hoàn toàn bị quên về loại trả về phải là quy tắc có thể chuyển đổi được. Đồng ý rằng nếu bạn có quyền kiểm soát tốt nhất là cung cấp một lớp dễ dàng hơn. Nếu bạn không mặc dù tôi đã thêm một giải pháp khả thi (nhưng xấu xí). – JaredPar

+0

@ kevin.key Tôi sẽ đánh dấu câu trả lời của JaredPar là câu trả lời ngay bây giờ vì nó hoạt động trực tiếp nếu bạn không thể thay đổi mã gốc. – Tergiver

+0

Tôi đã đánh dấu nó là câu trả lời. Khóa đã chuyển cấu trúc bằng ref trong C# và thay đổi hàm C để truyền cấu trúc thông qua tham số con trỏ. –

20

Có hai vấn đề với chữ ký PInvoke mà bạn đã chọn.

Cách đầu tiên dễ sửa. Bạn có một sự dịch sai của unsigned long. Trong C, unsigned long thường chỉ có 4 byte. Bạn đã chọn loại C# long là 8 byte. Việc thay đổi mã C# để sử dụng uint sẽ khắc phục điều này.

Thứ hai khó hơn một chút. Như Tergiver chỉ ra CLR Marshaller chỉ hỗ trợ một struct ở vị trí trả về nếu nó blittable. Blittable là một cách ưa thích để nói rằng nó có cùng một biểu diễn bộ nhớ chính xác trong mã nguồn gốc và được quản lý. Định nghĩa struct bạn đã chọn không phải là blittable bởi vì nó có một mảng lồng nhau.

Điều này có thể được làm việc xung quanh mặc dù nếu bạn nhớ rằng PInvoke là một quá trình rất đơn giản. CLR Marshaller thực sự chỉ cần bạn trả lời 2 câu hỏi với chữ ký của các loại và phương thức pinvoke của bạn

  • Tôi đang sao chép bao nhiêu byte?
  • Hướng nào cần phải đi?

Trong trường hợp này, số byte là sizeof(unsigned long) + 128 == 132. Vì vậy, tất cả những gì chúng ta cần làm là xây dựng một loại được quản lý có thể ghi được và có kích thước là 132 byte. Cách dễ nhất để làm điều này là để xác định một blob để xử lý các phần mảng

[StructLayout(LayoutKind.Sequential, Size = 128)] 
struct Blob 
{ 
    // Intentionally left empty. It's just a blob 
} 

Đây là một cấu trúc không có thành viên đó sẽ xuất hiện ở bên marshaller như có kích thước 128 byte (và như là một tiền thưởng đó là blittable !). Bây giờ chúng ta có thể dễ dàng xác định cấu trúc Frame như một sự kết hợp của một uint và loại này

struct Frame 
{ 
    public int Identifier; 
    public Blob NameBlob; 
    ... 
} 

Bây giờ chúng ta có một loại blittable với một kích thước marshaller sẽ thấy như 132 byte.Điều này có nghĩa là nó sẽ hoạt động tốt với chữ ký GetFrame bạn đã xác định

Phần duy nhất còn lại là cấp cho bạn quyền truy cập vào số thực tế char[] cho tên. Đây là một chút khó khăn nhưng có thể được giải quyết với một chút phép thuật nguyên soái.

public string GetName() 
{ 
    IntPtr ptr = IntPtr.Zero; 
    try 
    { 
     ptr = Marshal.AllocHGlobal(128); 
     Marshal.StructureToPtr(NameBlob, ptr, false); 
     return Marshal.PtrToStringAnsi(ptr, 128); 
    } 
    finally 
    { 
     if (ptr != IntPtr.Zero) 
     { 
      Marshal.FreeHGlobal(ptr); 
     } 
    } 
} 

Lưu ý: Tôi không thể bình luận về phần quy ước gọi vì tôi không quen với các GetFrame API nhưng đó là một cái gì đó tôi chắc chắn sẽ kiểm tra trên.

+0

Tôi không thể thấy rằng loại sẽ tương thích nhiều hơn với P/Invoke dựa trên thay đổi này, mặc dù nó chắc chắn là một vấn đề về cổng cần được sửa chữa. –

+1

@BenVoigt nó có thể gây ra một sự khác biệt lớn. Trong chữ ký gốc, marshaller sẽ giả định 'char []' bắt đầu tại offset 8 từ đầu của struct so với giá trị thực của 4. Kết quả là marshaller thấy 4 byte không thực sự ở đó. Cả hai sẽ ghi vào 4 byte này khi marshalling thành native và đọc 4 byte này khi marshalling lại từ native. 4 byte về cơ bản là rác và do đó dẫn đến hành vi không xác định – JaredPar

+0

Tất nhiên nó ảnh hưởng đến tính chính xác. Nhưng nó sẽ không gây ra lỗi biên dịch. Cả 'ulong' và' uint' đều có thể blittable, p/invoke xử lý chúng theo cùng một kiểu (mặc dù mức độ khác nhau). –

3

Một lựa chọn khác để JaredPar là để sử dụng các tính năng C# đệm kích thước cố định. Tuy nhiên, điều này yêu cầu bạn bật cài đặt để cho phép mã không an toàn, nhưng tránh được 2 cấu trúc.

class Program 
{ 
    private const int SIZE = 128; 

    unsafe public struct Frame 
    { 
     public uint Identifier; 
     public fixed byte Name[SIZE]; 
    } 

    [DllImport("PinvokeTest2.DLL", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] 
    private static extern Frame GetFrame(int index); 

    static unsafe string GetNameFromFrame(Frame frame) 
    { 
     //Option 1: Use if the string in the buffer is always null terminated 
     //return Marshal.PtrToStringAnsi(new IntPtr(frame.Name)); 

     //Option 2: Use if the string might not be null terminated for any reason, 
     //like if were 128 non-null characters, or the buffer has corrupt data. 

     return Marshal.PtrToStringAnsi(new IntPtr(frame.Name), SIZE).Split('\0')[0]; 
    } 

    static void Main() 
    { 
     Frame a = GetFrame(0); 
     Console.WriteLine(GetNameFromFrame(a)); 
    } 
} 
+1

Đẹp nhất! Bạn có thể sử dụng quá tải 'PtrToStringAnsi' với kích thước 128 để nó sẽ không bị tràn nếu không có ký tự NULL kết thúc. – Tergiver

+0

Nếu có bất kỳ khả năng nào sẽ không có ký tự rỗng, thì bạn nên sử dụng tùy chọn thứ 2 tôi hiển thị, sử dụng quá tải đó. Thật không may AFAICT rằng quá tải sẽ luôn tạo ra một chuỗi 128 ký tự, thay vì dừng tại NULL đầu tiên. Đó là lý do tại sao tôi đã thêm phân chia vào ký tự null và chọn kết quả đầu tiên. –

+0

Bạn nói đúng, chuỗi kết quả Độ dài sẽ là kích thước bạn vượt qua. – Tergiver

Các vấn đề liên quan