2012-04-25 12 views
18

Tak więc, było wiele wariantów tego pytania, a patrząc na kilka wciąż nie mogę tego rozgryźć.C# wywoływanie funkcji C, która zwraca struct z ustalonym rozmiarem tablicy znaków

Jest to kod C:

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

Frame GetFrame(int index); 

Jest to kod 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); 

Jest to ostatnia próba Próbowałem w C# i wydaje się całkiem logiczne, ale otrzymuję error "Sygnatura metody nie jest zgodna z PInvoke." Tak, jestem trochę zagubiony w tym, co spróbować dalej. Każda pomoc jest doceniana.

Dzięki, Kevin

UpdatedKevin dodaje to jako edycja na moją odpowiedź

Powinienem zamiast zmiana kodu C:

void GetFrame(int index, Frame * f); 

i używać zamiast 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

Widziałeś ten http://social.msdn.microsoft.com/Forums/en-AU/csharplanguage/thread/6e0ad208-5c8b-48ac-a45e-cfaf7f52221b? – MilkyWayJoe

+0

Definicja funkcji istnieje. Podany kod C jest tylko z pliku nagłówkowego. –

+0

Widziałem to i próbowałem prywatnego statycznego rozszerzenia IntPtr GetFrame (indeks int); ale wywołanie, które wyrzuca błąd "Próba odczytu lub zapisu chronionej pamięci." –

Odpowiedz

8

Problem polega na tym, że natywna funkcja zwraca typ niewywołujący jako wartość zwracaną.

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

P/wywołać nie mogą mieć niekorzystny blittable typów jako wartość powrotną.

Nie można p/wywołać tej metody. [EDITTo jest rzeczywiście możliwe, patrz JaredPar's answer]

Wracając 132 bajtów przez wartość jest złym pomysłem. Jeśli ten natywny kod jest twój, naprawiłbym go. Możesz to naprawić, przydzielając 132 bajty i zwracając wskaźnik. Następnie dodaj metodę FreeFrame, aby zwolnić tę pamięć. Teraz może być p/Invoked.

Alternatywnie, można zmienić go przyjąć wskaźnik do pamięci ramki, która będzie wypełnić.

+0

+1, całkowicie zapomniałem o typie zwrotu musi być zasadą blittable. Zgadzam się, że jeśli masz kontrolę, najlepiej jest zapewnić łatwiejszą warstwę. Jeśli nie, dodałem działające (ale brzydkie) rozwiązanie. – JaredPar

+0

@ kevin.key Chciałbym zaznaczyć odpowiedź JaredPar jako odpowiedź teraz, ponieważ działa bezpośrednio, jeśli nie można zmienić natywnego kodu. – Tergiver

+0

Zrobiłem to jako odpowiedź. Kluczem było przejście przez struct przez ref w C# i zmiana funkcji C, aby przekazać strukturę za pomocą parametru wskaźnika. –

20

Istnieją dwa problemy z wybranym przez ciebie sygnaturą PInvoke.

Pierwsza jest łatwa do naprawienia. Masz błędne tłumaczenie unsigned long. W C unsigned long jest zwykle tylko 4 bajty. Wybrano typ C# long, który ma 8 bajtów. Zmiana kodu C# do użycia uint naprawi to.

Drugi jest nieco trudniejszy. Jak zauważył Tergiver, CLR Marshaller obsługuje tylko strukturę w pozycji powrotnej, jeśli jest ona podatna na blaknięcie. Blittable to fantazyjny sposób powiedzenia, że ​​ma dokładnie taką samą reprezentację pamięci w natywnym i zarządzanym kodzie. Definicja struktury, którą wybrałeś, nie jest zdolna do rozsyłania, ponieważ ma tablicę zagnieżdżoną.

Można to obejść, jeśli pamiętasz, że PInvoke to bardzo prosty proces. CLR naziemnego naprawdę potrzebuje, aby odpowiedzieć na 2 pytania z podpisem swoim rodzaju i metod pinvoke

  • Ile bajtów ja kopiowania?
  • W którym kierunku muszą iść?

W tym przypadku liczba bajtów wynosi sizeof(unsigned long) + 128 == 132. Więc wszystko, co musimy zrobić, to zbudować zarządzany typ, który jest blittable i ma rozmiar 132 bajtów. Najprostszym sposobem, aby to zrobić jest zdefiniowanie blob obsłużyć część tablicę

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

Jest to struktura bez członków, które pojawią się do naziemnego jak o wielkości 128 bajtów (i jako bonus to blittable !). Teraz możemy łatwo określić strukturę Frame jako kombinacja uint i tego typu

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

teraz mamy blittable typu o wielkości ruchu naziemnego będzie zobaczyć jak 132 bajtów.Oznacza to, że będzie on działał dobrze z sygnaturą GetFrame, którą zdefiniowałeś.

Pozostała część pozostawia ci dostęp do rzeczywistej nazwy char[]. Jest to nieco skomplikowane, ale można je rozwiązać za pomocą odrobiny magii marshmala.

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); 
     } 
    } 
} 

Uwaga: Nie mogę wypowiedzieć się na temat części konferencyjnym powołanie, bo jestem zaznajomiony z GetFrame API ale to jest coś, co na pewno sprawdzić.

+0

Nie widzę, aby typ był mniej lub bardziej kompatybilny z P/Invoke na podstawie tej zmiany, chociaż z pewnością jest to problem z portowaniem, który należy poprawić. –

+1

@BenVoigt może to spowodować dużą różnicę. W oryginalnym podpisie marshaller przyjmie, że 'char []' zaczyna się od przesunięcia 8 od początku struktury w stosunku do rzeczywistej wartości 4. W rezultacie marshaller widzi 4 bajty, których tak naprawdę tam nie ma. Oba zapisze do tych 4 bajtów podczas zestawiania do natywnego i odczyta te 4 bajty, gdy zestawi je z natywnego. 4 bajty są w zasadzie śmieciami, a zatem prowadzą do niezdefiniowanych zachowań. – JaredPar

+0

Oczywiście wpływa to na poprawność. Ale nie spowoduje to błędu kompilacji. Zarówno 'ulong', jak i' uint' są blittable, p/invoke obsługuje je w ten sam sposób (chociaż różny zakres). –

3

Inną możliwością JaredPar jest wykorzystanie funkcji ustalony rozmiar bufora C#. Wymaga to jednak włączenia tego ustawienia, aby zezwolić na niebezpieczny kod, ale unika się 2 struktur.

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

Niezły! Możesz użyć przeciążenia 'PtrToStringAnsi' z rozmiarem 128, aby nie został przekroczony, jeśli nie ma kończącego się znaku NULL. – Tergiver

+0

Jeśli jest jakaś możliwość, że nie będzie znaku pustego, powinieneś użyć drugiej opcji, którą pokazuję, która używa tego przeciążenia. Niestety AFAICT, że przeciążenie zawsze tworzy ciąg 128 znaków, zamiast zatrzymywać się przy pierwszej wartości NULL. Dlatego dodałem podział na pustym znaku i wybrałem pierwszy wynik. –

+0

Masz rację, wynikowy ciąg Długość będzie wielkością, którą zdasz. – Tergiver

Powiązane problemy