2015-08-20 13 views
6

SzenarioC# Rangierung Bool

Dies sollte eine einfache Aufgabe sein, aber aus irgendeinem Grund kann ich es nicht wie beabsichtigt gehen. Ich muss ein grundlegendes C++ struct während eines reversed-P/Invoke-Aufrufs (nicht verwalteten Aufruf verwalteten Codes) marshalieren.

Das Problem tritt nur bei der Verwendung von bool innerhalb der Struktur, so dass ich schneide nur die Seite ++ C auf:

struct Foo { 
    bool b; 
}; 

Da .NET Marschälle booleans als 4-Byte-Felder standardmäßig Marschall ich die native boolean explizit als 1-Byte-Längenfeld:

public struct Foo { 
    [MarshalAs(UnmanagedType.I1)] public bool b; 
} 

wenn ich rufe eine exportierte verwaltet statische Methode mit der folgenden Signatur und Körper:

public static void Bar(Foo foo) { 
    Console.WriteLine("{0}", foo.b); 
} 

Ich bekomme die korrekte boolesche Alpha-Darstellung gedruckt. Wenn ich die Struktur mit mehr Feldern erweitern, ist die Ausrichtung korrekt und die Daten sind nach dem Marshalling nicht beschädigt.

Problem

Aus irgendeinem Grund, wenn ich diese struct als Argument vermarshallten nicht passieren, sondern als Rückgabetyp von Wert:

public static Foo Bar() { 
    var foo = new Foo { b = true }; 
    return foo; 
} 

stürzt die Anwendung mit der folgenden Fehlermeldung :

enter image description here

Wenn ich Ändern Sie den verwalteten Struktur eine byte anstelle eines bool

public struct Foo { 
    [MarshalAs(UnmanagedType.I1)] public byte b; 
} 

public static Foo Bar() { 
    var foo = new Foo { b = 1 }; 
    return foo; 
} 

der Rückgabewert zu halten, richtig, ohne einen Fehler zu einem nicht verwalteten Bool vermarshallten.

ich nicht unterstand zwei Dinge hier:

  1. Warum ein Paramter mit bool vermarshallten wie oben beschrieben Arbeit, sondern als Rückgabewert einen Fehler geben?
  2. Warum funktioniert ein byte marshalled als UnmanagedType.I1 für Rückgaben, aber ein bool auch mit UnmanagedType.I1 marshalled nicht?

Ich hoffe meine Beschreibung macht Sinn - wenn nicht, lass es mich wissen, damit ich den Wortlaut ändern kann.

EDIT: Meine aktuelle Problemumgehung ist eine verwaltete Struktur wie:

public struct Foo { 
    private byte b; 
    public bool B { 
     get { return b != 0; } 
     set { b = value ? (byte)1 : (byte)0; } 
} 

, die ehrlich gesagt, ich ziemlich lächerlich ...

EDIT2: Hier eine fast MCVE ist.Die verwaltete Assembly wurde mit korrekten Symbolexporten neu kompiliert (unter Verwendung der Attribute .export und .vtentry im IL-Code), aber sollte keinen Unterschied zu C++/CLI-Aufrufen darstellen. So wird dieser Code nicht funktioniert "wie sie ist", ohne dass die Exporte manuell zu tun:

C++ (native.dll):

#include <Windows.h> 

struct Foo { 
    bool b; 
}; 

typedef void (__stdcall *Pt2PassFoo)(Foo foo); 
typedef Foo (__stdcall *Pt2GetFoo)(void); 

int main(int argc, char** argv) { 
    HMODULE mod = LoadLibraryA("managed.dll"); 
    Pt2PassFoo passFoo = (Pt2PassFoo)GetProcAddress(mod, "PassFoo"); 
    Pt2GetFoo getFoo = (Pt2GetFoo)GetProcAddress(mod, "GetFoo"); 

    // Try to pass foo (THIS WORKS) 
    Foo f1; 
    f1.b = true; 
    passFoo(f1); 

    // Try to get foo (THIS FAILS WITH ERROR ABOVE) 
    // Note that the managed method is indeed called; the error 
    // occurs upon return. If 'b' is not a 'bool' but an 'int' 
    // it also works, so there must be something wrong with it 
    // being 'bool'. 
    Foo f2 = getFoo(); 

    return 0; 
} 

C# (managed.dll):

using System; 
using System.Runtime.InteropServices; 

public struct Foo { 
    [MarshalAs(UnmanagedType.I1)] public bool b; 
    // When changing the above line to this, everything works fine! 
    // public byte b; 
} 

/* 
    .vtfixup [1] int32 fromunmanaged at VT_01 
    .vtfixup [1] int32 fromunmanaged at VT_02 
    .data VT_01 = int32(0) 
    .data VT_02 = int32(0) 
*/ 

public static class ExportedFunctions { 
    public static void PassFoo(Foo foo) { 
     /* 
      .vtentry 1:1 
      .export [1] as PassFoo 
     */    

     // This prints the correct value, and the 
     // method returns without error. 
     Console.WriteLine(foo.b); 
    } 

    public static Foo GetFoo() { 
     /* 
      .vtentry 2:1 
      .export [2] as GetFoo 
     */ 

     // The application crashes with the shown error 
     // message upon return. 
     var foo = new Foo { b = true; } 
     return foo; 
    } 
} 
+0

Können wir einen MCVE haben? So können wir auch die Funktionsaufrufe sehen. –

+0

@DavidHeffernan Ich habe ein MCVE hinzugefügt, so gut ich konnte. Da ich umgekehrte P/Invoke-Mechanismen verwende und den IL-Code direkt manipuliere, läuft er nicht ohne manuelle Änderungen an der Assembly ab. – PuerNoctis

+0

Die IL manipulieren? Hmm. Sicherlich wird das relevant sein !! –

Antwort

5

Das zugrunde liegende Problem ist das gleiche wie bei dieser Frage - Why DllImport for C bool as UnmanagedType.I1 throws but as byte it works Die Ausnahme, die Sie bekommen, ist MarshalDirectiveException - die restlichen Informationen über die Ausnahme zu bekommen ist ein bisschen Trickie r, aber unnötig.

Kurz gesagt funktioniert das Marshalling für Rückgabewerte nur für blitable Strukturen. Wenn Sie ein boolesches Feld angeben, ist die Struktur nicht mehr blitbar (weil bool nicht blittierbar ist) und funktioniert nicht mehr für Rückgabewerte. Dies ist lediglich eine Einschränkung des Marshallers und gilt sowohl für DllImport als auch für Ihre Versuche bei "DllExport".

das entsprechende Stück Dokumentation Zitiert:

Strukturen, die von der Plattform zurückgegeben werden, rufen Sie Anrufe blitfähig Typen sein müssen. Platform-Aufrufe unterstützen nicht blitbare Strukturen als Rückgabetypen.

Es wird nicht direkt gesagt, aber dasselbe gilt auch beim Aufruf.

Die einfachste Problemumgehung besteht darin, bei Ihrem Ansatz "Byte als Hintergrundfeld, Bool als Eigenschaft" zu bleiben. Alternativ können Sie stattdessen die C BOOL verwenden, die gut funktioniert. Und natürlich gibt es immer die Möglichkeit, einen C++/CLI-Wrapper zu verwenden oder einfach nur das reale Layout der Struktur in Ihren Hilfsmethoden zu verstecken (in diesem Fall rufen Ihre Exportmethoden eine andere Methode auf, die sich mit dem echten Foo-Typ befasst) und die korrekte Konvertierung in den Foo++ Typ übernehmen).

Es ist auch möglich, ein ref Argument anstelle eines Rückgabewerts zu verwenden. Dies ist in der Tat ein gemeinsames Muster in nicht verwalteten interop:

typedef void(__stdcall *Pt2GetFoo)(Foo* foo); 

Foo f2 = Foo(); 
getFoo(&f2); 

auf der C++ Seite und

public static void GetFoo(ref Foo foo) 
{ 
    foo = new Foo { b = true }; 
} 

auf der C# Seite.

Sie können auch Ihren eigenen boolean-Typen, ein einfaches struct mit einem einzigen byte Feld mit impliziten Cast-Operatoren zu und von bool machen - es ist nicht genau wie ein echtes bool Feld zur Arbeit zu gehen, aber es sollte gut am meisten arbeiten der ganzen Zeit.

+0

Hmm, du hast das Duplikat nicht richtig gelesen. Das Verwenden von BOOL ist keine Problemumgehung, es ist nicht blitable. Es kann nicht sein, ein .NET bool ist 1 Byte und BOOL ist 4 Bytes. –

+0

@HansPassant Oh, mein Schlechter. Fest. – Luaan