2016-03-17 25 views
14

Tôi cần sắp xếp một số cấu trúc lồng nhau trong C# 4.0 thành các đốm màu nhị phân để chuyển sang khung công tác C++.C# interop: tương tác xấu giữa cố định và MarshalAs

Tôi cho đến nay đã có rất nhiều thành công khi sử dụng unsafe/fixed để xử lý các mảng độ dài cố định của các loại nguyên thủy. Bây giờ tôi cần xử lý một cấu trúc chứa các mảng chiều dài cố định lồng nhau của các cấu trúc khác.

Tôi đã sử dụng cách giải quyết phức tạp làm phẳng cấu trúc nhưng sau đó tôi đã xem qua một ví dụ về thuộc tính MarshalAs trông giống như nó có thể giúp tôi tiết kiệm rất nhiều vấn đề.

Thật không may trong khi nó cho tôi số tiền chính xác dữ liệu dường như cũng ngăn không cho các mảng fixed được sắp xếp đúng cách, do đầu ra của chương trình này thể hiện. Bạn có thể xác nhận lỗi bằng cách đặt điểm ngắt trên dòng cuối cùng và kiểm tra bộ nhớ tại mỗi con trỏ.

using System; 
using System.Threading; 
using System.Runtime.InteropServices; 

namespace MarshalNested 
{ 
    public unsafe struct a_struct_test1 
    { 
    public fixed sbyte a_string[3]; 
    public fixed sbyte some_data[12]; 
    } 

    public struct a_struct_test2 
    { 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] 
    public sbyte[] a_string; 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] 
    public a_nested[] some_data; 
    } 

    public unsafe struct a_struct_test3 
    { 
    public fixed sbyte a_string[3]; 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] 
    public a_nested[] some_data; 
    } 


    public unsafe struct a_nested 
    { 
    public fixed sbyte a_notherstring[3]; 
    } 

    class Program 
    { 
    static unsafe void Main(string[] args) 
    { 
     a_struct_test1 lStruct1 = new a_struct_test1(); 
     lStruct1.a_string[0] = (sbyte)'a'; 
     lStruct1.a_string[1] = (sbyte)'b'; 
     lStruct1.a_string[2] = (sbyte)'c'; 

     a_struct_test2 lStruct2 = new a_struct_test2(); 
     lStruct2.a_string = new sbyte[3]; 
     lStruct2.a_string[0] = (sbyte)'a'; 
     lStruct2.a_string[1] = (sbyte)'b'; 
     lStruct2.a_string[2] = (sbyte)'c'; 

     a_struct_test3 lStruct3 = new a_struct_test3(); 
     lStruct3.a_string[0] = (sbyte)'a'; 
     lStruct3.a_string[1] = (sbyte)'b'; 
     lStruct3.a_string[2] = (sbyte)'c'; 

     IntPtr lPtr1 = Marshal.AllocHGlobal(15); 
     Marshal.StructureToPtr(lStruct1, lPtr1, false); 

     IntPtr lPtr2 = Marshal.AllocHGlobal(15); 
     Marshal.StructureToPtr(lStruct2, lPtr2, false); 

     IntPtr lPtr3 = Marshal.AllocHGlobal(15); 
     Marshal.StructureToPtr(lStruct3, lPtr3, false); 

     string s1 = ""; 
     string s2 = ""; 
     string s3 = ""; 
     for (int x = 0; x < 3; x++) 
     { 
     s1 += (char) Marshal.ReadByte(lPtr1+x); 
     s2 += (char) Marshal.ReadByte(lPtr2+x); 
     s3 += (char) Marshal.ReadByte(lPtr3+x); 
     } 

     Console.WriteLine("Ptr1 (size " + Marshal.SizeOf(lStruct1) + ") says " + s1); 
     Console.WriteLine("Ptr2 (size " + Marshal.SizeOf(lStruct2) + ") says " + s2); 
     Console.WriteLine("Ptr3 (size " + Marshal.SizeOf(lStruct3) + ") says " + s3); 

     Thread.Sleep(10000); 
    } 
    } 
} 

Output:

Ptr1 (size 15) says abc 
Ptr2 (size 15) says abc 
Ptr3 (size 15) says a 

Vì vậy, đối với một số lý do nó chỉ marshalling ký tự đầu tiên của chuỗi fixed ANSI của tôi. Có cách nào xung quanh điều này không, hay tôi đã làm điều gì đó ngu ngốc không liên quan đến sự đầm lầy?

+0

Ngăn xếp ngăn xếp: Nhận danh tiếng để chỉnh sửa bài đăng của mọi người và tài liệu MSDN đáng chú ý. Đặt một câu hỏi khó? [Dế] –

+1

Bạn ít nhất có được một huy hiệu tumbleweed;) Và bây giờ với tiền thưởng, bạn sẽ nhận được sự chú ý của dev tốt hơn * nhưng không cần thái độ *. –

+0

Bạn đã từng sử dụng trang web này trước đây chưa? Nó chạy trên thái độ xấu. :-P –

Trả lời

14

Đây là trường hợp chẩn đoán bị thiếu. Ai đó nên đã lên tiếng và cho bạn biết rằng tuyên bố của bạn không được hỗ trợ. Trường hợp ai đó là trình biên dịch C#, tạo ra một lỗi biên dịch, hoặc trình biên dịch trường CLR, tạo ra một ngoại lệ thời gian chạy.

Không giống như bạn không thể chẩn đoán. Bạn chắc chắn sẽ nhận được một khi bạn thực sự bắt đầu sử dụng các cấu trúc như dự định: "Bạn không thể sử dụng bộ đệm kích thước cố định chứa trong biểu thức không cố định Hãy thử sử dụng câu lệnh cố định"

a_struct_test3 lStruct3 = new a_struct_test3(); 
    lStruct3.some_data = new a_nested[4]; 
    lStruct3.some_data[0] = new a_nested(); 
    lStruct3.some_data[0].a_notherstring[0] = (sbyte)'a'; // Eek! 

Những gợi CS1666,. Không phải là lời khuyên "thử này" là tất cả những gì hữu ích:

fixed (sbyte* p = &lStruct3.some_data[0].a_notherstring[0]) // Eek! 
    { 
     *p = (sbyte)'a'; 
    } 

Lỗi chính xác tương tự CS1666. Điều tiếp theo bạn muốn thử được đặt một thuộc tính trên đệm cố định:

public unsafe struct a_struct_test3 { 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] 
    public fixed sbyte a_string[3]; 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] 
    public a_nested[] some_data; 
} 
//... 

    a_struct_test3 lStruct3 = new a_struct_test3(); 
    lStruct3.some_data = new a_nested[4]; 
    IntPtr lPtr3 = Marshal.AllocHGlobal(15); 
    Marshal.StructureToPtr(lStruct3, lPtr3, false); // Eek! 

Giữ các biên dịch C# hạnh phúc nhưng bây giờ CLR nói lên và bạn sẽ có được một TypeLoadException khi chạy: "Thông tin thêm: Can lĩnh vực không soái 'a_string 'of type' MarshalNested.a_struct_test3 ': Kết hợp kiểu không được quản lý/không được quản lý (loại giá trị này phải được ghép nối với Struct). "

Vì vậy, tóm lại, bạn đã nhận được CS1666 hoặc TypeLoadException về nỗ lực ban đầu của bạn. Điều đó không xảy ra vì trình biên dịch C# không bị buộc phải nhìn vào phần xấu, nó chỉ tạo ra CS1666 trên một câu lệnh truy cập mảng đó. Và nó đã không xảy ra trong thời gian chạy vì marshaller trường trong CLR đã không cố gắng để sắp xếp các mảng vì nó là null. Bạn có thể gửi báo cáo phản hồi lỗi tại connect.microsoft.com nhưng tôi sẽ rất ngạc nhiên nếu họ không đóng nó bằng "thiết kế".


Nói chung, một chi tiết khó hiểu vấn đề một thỏa thuận tuyệt vời để các marshaller trường trong CLR, các đoạn mã có thể chuyển đổi các giá trị struct và các đối tượng lớp từ bố cục quản lý của họ để bố trí không được quản lý của họ.Đó là tài liệu kém, Microsoft không muốn để móng tay xuống các chi tiết thực hiện chính xác. Chủ yếu là vì chúng phụ thuộc quá nhiều vào kiến ​​trúc đích.

Điều quan trọng là có hay không một giá trị hoặc đối tượng là blittable. Nó là blittable khi bố trí quản lý và unmanaged là giống hệt nhau. Điều này chỉ xảy ra khi mọi thành viên thuộc loại có chính xác cùng với căn chỉnh ở cả hai bố cục. Điều đó thường chỉ xảy ra khi các trường có một loại giá trị rất đơn giản (như byte hoặc int) hoặc cấu trúc tự nó là blittable. Notoriously không phải khi nó là bool, có quá nhiều loại bool không được quản lý xung đột. Một trường của một kiểu mảng là không bao giờ blittable, mảng được quản lý không nhìn bất cứ điều gì giống như mảng C kể từ khi họ có một tiêu đề đối tượng và một thành viên Length.

Có giá trị blittable hoặc đối tượng là rất mong muốn, nó tránh các marshaller lĩnh vực từ việc phải tạo một bản sao. Mã nguồn gốc nhận được một con trỏ đơn giản để quản lý bộ nhớ và tất cả những gì cần thiết là để ghim bộ nhớ. Rất nhanh. Nó cũng rất nguy hiểm, nếu khai báo không khớp thì mã nguồn gốc có thể dễ dàng tô màu bên ngoài các dòng và làm hỏng khung hình đống hoặc ngăn xếp GC. Một lý do rất phổ biến cho một chương trình sử dụng pinvoke để ném bom ngẫu nhiên với ExecutionEngineException, quá khó chẩn đoán. Tuyên bố như vậy thực sự xứng đáng với từ khóa không an toàn nhưng trình biên dịch C# không nhấn mạnh vào nó. Cũng không thể, trình biên dịch không được phép đưa ra bất kỳ giả định nào về bố cục đối tượng được quản lý. Bạn giữ nó an toàn bằng cách sử dụng Debug.Assert() trên giá trị trả về của Marshal.SizeOf<T>, nó phải là một kết hợp chính xác với giá trị sizeof(T) trong chương trình C.

Như đã lưu ý, mảng là một chướng ngại vật để nhận được giá trị hoặc vật thể dễ cháy. Từ khóa fixed được thiết kế để giải quyết vấn đề này. CLR xử lý nó như một loại giá trị mờ đục không có thành viên, chỉ là một blob byte. Không có tiêu đề đối tượng và không có thành viên Độ dài, gần như bạn có thể truy cập vào mảng C. Và được sử dụng trong mã C# như bạn muốn sử dụng một mảng trong một chương trình C, bạn phải sử dụng một con trỏ để giải quyết các phần tử mảng và kiểm tra ba lần mà bạn không tô màu bên ngoài các dòng. Đôi khi bạn phải sử dụng một mảng cố định, xảy ra khi bạn khai báo một liên kết (các trường chồng chéo) và bạn chồng chéo một mảng với một giá trị. Poison vào bộ thu gom rác, nó không thể tìm ra nếu trường lưu trữ một đối tượng gốc. Không được phát hiện bởi trình biên dịch C# nhưng đáng tin cậy các chuyến đi một TypeLoadException khi chạy.


câu chuyện dài ngắn, sử dụng fixedchỉ cho một loại blittable. Trộn các trường của loại bộ đệm có kích thước cố định với các trường phải được sắp xếp không thể hoạt động. Và không hữu ích, đối tượng hoặc giá trị được sao chép, vì vậy bạn cũng có thể sử dụng loại mảng thân thiện.

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