ScenarioC# Marshalling bool
Questo dovrebbe essere un compito facile, ma per qualche motivo non riesco a farlo andare come previsto. Devo eseguire il marshalling di base su C++ struct
durante una chiamata P/Invoke inversa (codice gestito chiamata non gestita).
Il problema si pone solo quando si utilizza bool
all'interno della struct, quindi ho solo rivestimento laterale C++ fino a:
struct Foo {
bool b;
};
Dal marescialli .NET booleani come campi a 4 byte per impostazione predefinita, ho maresciallo il booleano nativo esplicitamente come campo 1 byte di lunghezza:
public struct Foo {
[MarshalAs(UnmanagedType.I1)] public bool b;
}
Quando chiamo un metodo statico gestito esportato con la seguente firma e del corpo:
public static void Bar(Foo foo) {
Console.WriteLine("{0}", foo.b);
}
Ottengo la corretta rappresentazione alfa booleana stampata. Se estendo la struttura con più campi, l'allineamento è corretto e i dati non sono corrotti dopo il marshalling.
Problema
Per qualche ragione, se non supero questo Marshalled struct
come argomento, ma piuttosto come un tipo di ritorno per valore:
public static Foo Bar() {
var foo = new Foo { b = true };
return foo;
}
l'applicazione si blocca con il seguente messaggio di errore :
Se io modificare la struttura riuscito a tenere un byte
invece di un valore bool
public struct Foo {
[MarshalAs(UnmanagedType.I1)] public byte b;
}
public static Foo Bar() {
var foo = new Foo { b = 1 };
return foo;
}
alla dichiarazione schierato correttamente senza un errore da un bool non gestito.
non lo faccio Unterstand due cose qui:
- Perché un paramter schierò con
bool
come descritto in precedenza il lavoro, ma come valore di ritorno dà un errore? - Perché uno eseguito come
UnmanagedType.I1
funziona come restituisce, ma unbool
ha anche il marshalling conUnmanagedType.I1
no?
Spero che la mia descrizione abbia un senso - in caso contrario, fatemelo sapere, così posso cambiare il testo.
EDIT: mia soluzione attuale è una struct gestito come:
public struct Foo {
private byte b;
public bool B {
get { return b != 0; }
set { b = value ? (byte)1 : (byte)0; }
}
che onestamente, trovo abbastanza ridicolo ...
EDIT2: Ecco un quasi-MCVE.L'assembly gestito è stato ricompilato con le esportazioni di simboli corrette (utilizzando gli attributi e .vtentry
nel codice IL), ma lo dovrebbe essere non fare alcuna differenza per le chiamate C++/CLI. Quindi, questo codice non funziona "così com'è", senza fare le esportazioni manualmente:
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;
}
}
Possiamo avere un MCVE? Quindi possiamo vedere anche le chiamate di funzione. –
@DavidHeffernan Ho aggiunto un MCVE come meglio potevo. Poiché utilizzo meccanismi di inversione P/Invoke e manipolo direttamente il codice IL, esso non funzionerà out-of-the-box senza modifiche manuali all'assieme. – PuerNoctis
Manipola l'IL? Hmm. Sicuramente sarà rilevante !! –