Pergunta

Eu encontrei recentemente uma situação onde eu preciso para criar um método genérico para ler um tipo de dados a partir de uma matriz de bytes.

Eu criei a seguinte classe:


public class DataStream
{
    public int Offset { get; set; }

    public byte[] Data { get; set; }

    public T Read<T>() where T : struct
    {
        unsafe
        {
            int dataLen = Marshal.SizeOf( typeof( T ) );
            IntPtr dataBlock = Marshal.AllocHGlobal( dataLen );


            Marshal.Copy( Data, Offset, dataBlock, dataLen );


            T type = *( ( T* )dataBlock.ToPointer() );

            Marshal.FreeHGlobal( dataBlock );

            Offset += dataLen;

            return type;
        }
    }
}

Agora, as questões de alocação de parte, este código não compila com esta mensagem:

Cannot take the address of, get the size of, or declare a pointer to a managed type ('T')

O que, parece estranho, porque você deve ser capaz de fazer as operações acima com base na restrição where T : struct sobre o método.

Se este código é terrivelmente incorreto, existe alguma maneira simples de tomar uma série de bytes e os lançou em um tipo de 'T'?

Obrigado!

Foi útil?

Solução

Em vez de tentar fazer isso através de manipulação de ponteiros, você deve mudar seu código para uso Mashal.PtrToStructure . Este método é projetado especificamente para este cenário.

Outras dicas

Desde resposta já foi dada, deixe-me explicar por que seu código original não funcionou para você:

O que, parece estranho, porque você deve ser capaz de fazer as operações acima com base na qual T:. Restrição struct no método

Não é verdade. Você pode ter ponteiros crus para tipos não gerenciados . Esta é definida como segue na especificação da linguagem C # (18.2):

referências Ao contrário (valores de tipos de referência), ponteiros não são rastreadas pelo coletor de lixo - o coletor de lixo não tem conhecimento de ponteiros e os dados aos quais eles apontam. Por isso, o ponteiro não é permitido para apontar para uma referência ou para uma estrutura que contém referências, eo tipo referente de um ponteiro deve ser um não gerenciado-type . Um do tipo não gerenciado é qualquer tipo que não é um tipo de referência e não contém campos de tipo de referência a qualquer nível de aninhamento. Em outras palavras, um do tipo não gerenciado é um dos seguintes:

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, ou bool.
  • Qualquer enum do tipo .
  • Qualquer ponteiro do tipo .
  • Qualquer definida pelo usuário struct do tipo que contém campos de não gerenciados tipos somente.

Portanto, há muito poucas restrições, e por um método genérico, T:struct pode ou não estar de acordo com eles para qualquer instanciação particular, assim como construção T* é ilegal. Seria bom ter uma restrição de parâmetro de tipo genérico especial para cobrir tipos não gerenciados, mas como está, não há um no CLR.

Em um ponto que eu escrevi este artigo explicando como fazer exatamente isso , mas muitas vezes mais rápido do que o Marshal.PtrToStructure. O exemplo de código usa a geração de código dinâmico para copiar o tipo genérico T de / para o fluxo de bits.

Assumindo:

  • A Sequential ou explícita Estrutura (de outra maneira muito má ideia)
  • tamanhos de dados correto (você deve pré-check & lance)

unsafe TStruct BytesToStructure<TStruct>(byte[] data) where TStruct : struct
{
    fixed (byte* dataPtr = data)
        return (TStruct)Marshal.PtrToStructure(new IntPtr(dataPtr), typeof(TStruct));
}

unsafe byte[] StructureToBytes<TStruct>(TStruct st) where TStruct : struct
{
    var bytes = new byte[Marshal.SizeOf(st)];
    fixed (byte* ptr = bytes) Marshal.StructureToPtr(st, new IntPtr(ptr), true);
    return bytes;
}

Eu escrevi este um tempo atrás para fazer a mesma coisa: http://www.codeproject.com/Articles/33713/Generic-BinaryReader-and-BinaryWriter-Extensions

Você vai ter que adicionar um ++ projeto C / CLI para a sua solução Visual Studio embora.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top