C# – sizeof vs Marshal.SizeOf

Depois do PySide – ainda no INDT como já falei em outros posts antigos – e dos bindings JS para o EFL, no meu trabalho atual estamos fazendo bindings para C#, mais especificamente para o Mono.

A princípio é relativamente simples usar código C a partir de C#. De forma resumida, basta declarar uma função em C# dizendo de que biblioteca ele deve importar a função nativa – pense em dlopen/dlsym – e então invocar a função. O Mono cuida de converter os tipos entre o código gerenciado e a função nativa, tanto os parâmetros como o retorno da função. Por exemplo, direto do guia do Mono para interoperabilidade:

[DllImport ("libc.so")]
private static extern int getpid ();

Lógico que isso é apenas o caso mais simples. Dependendo das peculiaridades do tipo a ser convertido, você pode precisar colocar mais informações para orientar o Mono nessa conversão, como o layout das estruturas, o formato de conversão de strings, ou mesmo uma conversão customizada.

Numa dessas customizações tive problemas durante a chamada de algumas funções, onde misteriosamente a pilha de chamada estava sendo corrompida. Depois de alguns testes, vi que as funções que corrompiam os dados envolviam uma estrutura que era passada por valor como argumento. De forma análoga às funções, onde a assinatura que você declara em C# é uma cópia da assinatura nativa e representa o “layout” daquela função na memória, com as estruturas você também faz o mesmo em C#. No caso, essa estrutura era declarada manualmente em C# da seguinte forma:

struct FooBar {
  IntPtr obj;
  bool something;
  bool another_thing;
  int size;
}

Enquanto que em C a estrutura tinha o seguinte formato:

struct Foo_Bar {
  Obj *obj;
  byte something : 1; // Na pratica é um typedef p/ byte
  byte another_thing : 1;
  int size;
}

A princípio tudo parece correto, já que bool no C# é armazenado no espaço de 1 byte, e apesar do bit field em C, cada field “byte” no C ocupava também 1 byte no final, devido ao packing da estrutura.

Ao realizar mais testes, inicialmente usando sizeof no C# e no C, o tamanho e os offsets dos campos estavam iguais entre o C# e C. Foi então que entrou em cena do Marshal.SizeOf. Marshal é uma classe do C# responsável por cuidar da conversão (marshalling) de tipos entre o código gerenciado e o código nativo.

O problema com sizeof era que ele media o uso de memória gerenciada dos tipos. E, curiosamente, o tipo booleano de C# por padrão difere no espaço utilizado entre a memória gerenciada (1) e memória nativa (4), este último corretamente informado pelo Marshal.SizeOf. A solução então foi indicar para o compilador para usar apenas 1 byte ao converter os campos booleanos, da seguinte forma:

struct FooBar {
  IntPtr obj;
  [MarshalAsAttribute(UnmanagedType.U1)] bool something;
  [MarshalAsAttribute(UnmanagedType.U1)] bool another_thing;
 int size;
}

Feito isso, o problema foi corrigido e todos viveram felizes até o próximo bug. 🙂