2013-03-21 16 views
9

Tôi có struct này và mã này:lỗi Non-blittable trên một loại blittable

[StructLayout(LayoutKind.Sequential, Pack = 8)] 
private class xvid_image_t 
{ 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] 
    public int[] stride; 

    // [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] 
    // public IntPtr[] plane; 
} 

public int decore() 
{ 
    xvid_image_t myStruct = new xvid_image_t(); 
    myStruct.stride = new int[4]; // can be commented out - same result 
    GCHandle.Alloc(myStruct, GCHandleType.Pinned); 

    // ... 
} 

Khi tôi cố gắng chạy nó tôi nhận được một ArgumentException nói:

Object chứa phi nguyên thủy hoặc dữ liệu không blittable

Sau khi đọc this MSDN page nói

Các loại phức tạp sau đây cũng là loại blittable:

mảng
  • Một chiều các loại blittable, chẳng hạn như một mảng các số nguyên. Tuy nhiên, một loại có chứa một mảng biến của các loại blittable không phải là chính nó blittable.

  • Loại giá trị được định dạng chỉ chứa các loại blittable (và các lớp nếu chúng được so khớp dưới dạng các loại được định dạng). Để biết thêm thông tin về các loại giá trị được định dạng, hãy xem Marsharsh mặc định cho các loại giá trị.

Tôi không hiểu mình đang làm gì sai. Tôi không chỉ muốn sử dụng Marshal mà còn phải hiểu điều này.

Vì vậy, những gì tôi thực sự muốn là phải biết:

  1. Tại sao?
  2. Tôi làm cách nào để giải quyết vấn đề này?
  3. Giải pháp bạn cung cấp cũng có hoạt động với dòng nhận xét trong cấu trúc không?

Tôi đang sử dụng .Net 4.5 nhưng cũng cần có giải pháp cho .Net 2.0.

Trả lời

9

Object chứa phi nguyên thủy hoặc không blittable dữ liệu

Đó là thông điệp ngoại lệ bạn nhận được. Bạn đang tập trung vào phần "không thể blittable" của thông điệp, nhưng đó không phải là vấn đề. Đó là phần "không nguyên thủy" là vấn đề. Một mảng là một kiểu dữ liệu không nguyên thủy.

CLR đang cố gắng giúp bạn không gặp khó khăn ở đây. Bạn có thể ghim đối tượng nhưng sau đó bạn vẫn còn có sự cố, mảng sẽ không được ghim. Một đối tượng không thực sự được ghim khi nó có các trường cần được ghim.

Và bạn có vấn đề lớn hơn với UnmanagedType.ByValArray, yêu cầu chuyển đổi cấu trúc. Nói cách khác, bố cục mà bạn cần hoàn toàn khác với bố cục của đối tượng lớp được quản lý. Chỉ có kẻ khắc phục pinvoke mới có thể thực hiện chuyển đổi này.

Bạn có thể nhận được những gì bạn muốn mà không cần sử dụng trình khắc phục sự cố bằng cách sử dụng bộ đệm kích thước cố định, sử dụng từ khóa cố định. Điều này yêu cầu sử dụng từ khóa không an toàn.Làm cho nó trông như thế này:

[StructLayout(LayoutKind.Sequential)] 
    unsafe private struct xvid_image_t { 
     public fixed int stride[4]; 
    } 

Lưu ý rằng bạn phải thay đổi việc kê khai để một struct loại. Nó bây giờ là một loại giá trị, bạn không còn cần phải sử dụng GCHandle để ghim giá trị khi bạn biến nó trở thành một biến cục bộ. Đảm bảo rằng bất kỳ mã không được quản lý nào có giá trị cấu trúc, thường là do tham chiếu, không không lưu con trỏ vào cấu trúc. Điều đó sẽ thổi mạnh và hoàn toàn không thể đoán trước. Từ khóa không an toàn phù hợp tại đây. Nếu nó lưu trữ con trỏ thì bạn thực sự phải byte bullet và sử dụng Marshal.AllocHGlobal() và Marshal.StructureToPtr() để đảm bảo con trỏ vẫn hợp lệ trong khi mã không được quản lý đang sử dụng nó.

+2

+1 - tuyệt vời giải thích, Hans! – JerKimball

3

Giới hạn gây phiền nhiễu của .NET là những thứ duy nhất mà nó nhận ra là một đối tượng độc lập System.ArraySystem.String, cả hai đều là loại tham chiếu. Có thể mã được viết bằng C# để sử dụng mảng fixed (như được chú thích bởi Hans Passant), nhưng loại đó không được nhận dạng bởi chính .NET. Và mã sử dụng mảng cố định không thể xác minh được. Ngoài ra, các mảng cố định được giới hạn để giữ nguyên bản, và không thể được truy cập bởi các ngôn ngữ khác như vb.net.

Hai giải pháp thay thế cho việc sử dụng một mảng cố định là để

  • thay thế các mảng cố định với một số sự kết hợp của các lĩnh vực mà cùng nhau tổng kích thước thích hợp (sử dụng các biến N trong hầu hết các trường hợp, nhưng có lẽ thay thế ví dụ như một char[4] với a UInt32 hoặc char[8] với số UInt64). Nếu mảng không quá lớn, người ta có thể định nghĩa (hoặc thông qua cắt/dán hoặc phản xạ) một tập hợp các phương thức tĩnh lấy cấu trúc bằng ref và đọc/ghi phần tử thích hợp, sau đó tạo một mảng các đại biểu để gọi các phương thức như vậy .

  • thay thế toàn bộ cấu trúc bằng một mảng, sau đó chuyển phần tử đầu tiên của mảng đó làm tham số ref. Điều này thậm chí có thể còn nguy hiểm hơn việc sử dụng một mảng fixed trong một cấu trúc, nhưng là cách duy nhất tôi biết trong vb.net để có được ngữ nghĩa "pass-by-ref" với cấu trúc chứa một thứ thực sự cần được truy cập dưới dạng mảng.

Trong khi tôi có thể hiểu rằng các mảng giá trị kiểu có thể đã bị coi là "khó hiểu" (đặc biệt là nếu họ đã tự động đóng hộp) có những nơi họ đã có được cách tiếp cận ngữ nghĩa-chính xác cho lưu trữ mảng, cả hai từ quan điểm cho phép ngữ nghĩa truyền qua cho COM interop và cũng từ quan điểm của các phương thức được cho là trả lại một số lượng nhỏ các giá trị. Ví dụ: trong System.Drawing2d, có phương thức trả về biến đổi đồ họa hiện tại dưới dạng float[6]; khác với thử nghiệm, sẽ không có cách nào rõ ràng để biết liệu các thay đổi đối với mảng đó sau khi nó được trả lại sẽ ảnh hưởng, có thể ảnh hưởng, hoặc được đảm bảo không ảnh hưởng đến bất kỳ điều gì khác. Nếu phương thức trả về một mảng kiểu giá trị, nó sẽ rõ ràng là các thay đổi đối với mảng được trả về không thể ảnh hưởng đến bất cứ thứ gì khác. Tuy nhiên, có hay không các mảng kiểu giá trị sẽ là một phần hữu ích của Khung, thực tế vẫn là cho dù lý do tốt hay xấu không có điều đó tồn tại.

0

tôi lấy câu trả lời dưới đây từ liên kết này (here)

SItuLongEmailMsg msg = newSItuLongEmailMsg(); 
// set members 
msg.text = new byte[2048]; 
// assign to msg.text 
int msgSize = Marshal.SizeOf(msg); 
IntPtr ptr = Marshal.AllocHGlobal(msgSize); 
Marshal.StructureToPtr(msg, ptr, true); 
byte[] dataOut = new byte[msgSize]; 
Marshal.Copy(ptr, dataOut, 0, msgSize); 
Các vấn đề liên quan