c# - sobrecarga - ¿Qué podría causar que los argumentos de P/Invoke estén fuera de servicio cuando se pasan?
ejemplos de metodos en c# (1)
El problema que presenté sobre GH ha estado allí durante bastante tiempo. Creo que este comportamiento es simplemente un error y no es necesario dedicar más tiempo a investigarlo.
Este es un problema que ocurre específicamente en el ARM, no en x86 o x64. Un usuario me informó de este problema y pude reproducirlo con UWP en Raspberry Pi 2 a través de Windows IoT. He visto este tipo de problema antes con convenciones de llamadas no coincidentes, pero estoy especificando Cdecl en la declaración P / Invoke e intenté agregar explícitamente __cdecl en el lado nativo con los mismos resultados. Aquí hay alguna información:
P / Invocar declaración ( reference ):
[DllImport(Constants.DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern FLSliceResult FLEncoder_Finish(FLEncoder* encoder, FLError* outError);
Las estructuras de C # ( reference ):
internal unsafe partial struct FLSliceResult
{
public void* buf;
private UIntPtr _size;
public ulong size
{
get {
return _size.ToUInt64();
}
set {
_size = (UIntPtr)value;
}
}
}
internal enum FLError
{
NoError = 0,
MemoryError,
OutOfRange,
InvalidData,
EncodeError,
JSONError,
UnknownValue,
InternalError,
NotFound,
SharedKeysStateError,
}
internal unsafe struct FLEncoder
{
}
La función en el encabezado C ( reference )
FLSliceResult FLEncoder_Finish(FLEncoder, FLError*);
¿FLSliceResult puede estar causando algunos problemas porque se devuelve por valor y tiene algunas cosas de C ++ en el lado nativo?
Las estructuras en el lado nativo tienen información real, pero para la API de C, FLEncoder se define como un puntero opaco . Al llamar al método anterior en x86 y x64, las cosas funcionan sin problemas, pero en el ARM, observo lo siguiente. La dirección del primer argumento es la dirección del SEGUNDO argumento, y el segundo argumento es nulo (por ejemplo, cuando registro las direcciones en el lado C # obtengo, por ejemplo, 0x054f59b8 y 0x0583f3bc, pero luego en el lado nativo los argumentos son 0x0583f3bc y 0x00000000). ¿Qué podría causar este tipo de problema fuera de servicio? ¿Alguien tiene alguna idea, porque estoy perplejo ...
Aquí está el código que ejecuto para reproducir:
unsafe {
var enc = Native.FLEncoder_New();
Native.FLEncoder_BeginDict(enc, 1);
Native.FLEncoder_WriteKey(enc, "answer");
Native.FLEncoder_WriteInt(enc, 42);
Native.FLEncoder_EndDict(enc);
FLError err;
NativeRaw.FLEncoder_Finish(enc, &err);
Native.FLEncoder_Free(enc);
}
Ejecutar una aplicación C ++ con lo siguiente funciona bien:
auto enc = FLEncoder_New();
FLEncoder_BeginDict(enc, 1);
FLEncoder_WriteKey(enc, FLSTR("answer"));
FLEncoder_WriteInt(enc, 42);
FLEncoder_EndDict(enc);
FLError err;
auto result = FLEncoder_Finish(enc, &err);
FLEncoder_Free(enc);
Esta lógica puede desencadenar el bloqueo con la última compilación del desarrollador, pero desafortunadamente aún no he descubierto cómo ser capaz de proporcionar de manera confiable símbolos de depuración nativos a través de Nuget para que se pueda avanzar (solo construir todo desde la fuente parece hacer eso ... .) por lo que la depuración es un poco incómoda porque es necesario construir componentes nativos y administrados. Sin embargo, estoy abierto a sugerencias sobre cómo hacer esto más fácil si alguien quiere intentarlo. Pero si alguien ha experimentado esto antes o tiene alguna idea sobre por qué sucede esto, agregue una respuesta, ¡gracias! Por supuesto, si alguien quiere un estuche de reproducción (ya sea uno fácil de construir que no proporcione pasos de origen o uno difícil de construir que sí lo haga), entonces deje un comentario pero no quiero pasar por el proceso de hacer uno si nadie lo va a usar (no estoy seguro de qué tan popular es ejecutar cosas de Windows en ARM real)
EDITAR Actualización interesante: si "falsifico" la firma en C # y elimino el segundo parámetro, el primero aparece en Aceptar.
EDITAR 2
Segunda actualización interesante: si cambio la definición de tamaño C # FLSliceResult de
UIntPtr
a
ulong
entonces los argumentos entran correctamente ... lo que no tiene sentido ya que
size_t
en ARM no debe estar firmado int.
EDITAR 3
Agregar
[StructLayout(LayoutKind.Sequential, Size = 12)]
a la definición en C # también hace que esto funcione, pero ¿POR QUÉ?
sizeof (FLSliceResult) en C / C ++ para esta arquitectura devuelve 8 como debería.
Establecer el mismo tamaño en C # provoca un bloqueo, pero establecerlo en 12 lo hace funcionar.
EDIT 4 minimicé el caso de prueba para poder escribir también un caso de prueba de C ++. En C # UWP falla, pero en C ++ UWP tiene éxito.
EDITAR 5 Here están las instrucciones desensambladas para C ++ y C # para la comparación (aunque C # no estoy seguro de cuánto tomar, así que cometí un error al tomar demasiado)
EDITAR 6 Un análisis adicional muestra que durante la ejecución "buena" cuando miento y digo que la estructura es de 12 bytes en C #, el valor de retorno se pasa al registro r0, y los otros dos argumentos entran por r1, r2. Sin embargo, en la mala ejecución, esto se desplaza para que los dos argumentos entren por r0, r1 y el valor de retorno esté en otro lugar (¿puntero de pila?)
EDITAR 7 Consulté el Estándar de Llamada de Procedimiento para la Arquitectura ARM . Encontré esta cita: "Un tipo compuesto de más de 4 bytes, o cuyo tamaño no puede ser determinado estáticamente por la persona que llama y la persona que llama, se almacena en la memoria en una dirección pasada como argumento adicional cuando se llamó a la función (§5.5, regla A .4). La memoria que se utilizará para el resultado puede modificarse en cualquier momento durante la llamada a la función ". Esto implica que pasar a r0 es el comportamiento correcto, ya que el argumento adicional implica el primero (ya que la convención de llamada C no tiene una forma de especificar el número de argumentos). Me pregunto si el CLR está confundiendo esto con otra regla sobre los tipos de datos fundamentales de 64 bits: "Un tipo de datos fundamentales de doble palabra (por ejemplo, vectores contenedorizados largos, dobles y de 64 bits) se devuelve en r0 y r1".
EDITAR 8 Ok, hay muchas pruebas que indican que el CLR está haciendo algo incorrecto aquí, así que presenté un informe de error . Espero que alguien lo note entre todos los bots automatizados que publican problemas en ese repositorio: -S.