Pergunta

Eu quero converter um byte* para um byte[], mas também quero ter uma função reutilizável para fazer isso:

public unsafe static T[] Create<T>(T* ptr, int length)
{
    T[] array = new T[length];

    for (int i = 0; i < length; i++)
        array[i] = ptr[i];

    return array;
}

Infelizmente, recebo um erro do compilador porque T pode ser um ".NET gerenciado tipo" e não podemos ter indicadores para Essa. Ainda mais frustrante é que não existe uma restrição de tipo genérico que possa restringir T a "tipos não gerenciados". Existe uma função .NET embutida para fazer isso? Alguma ideia?

Foi útil?

Solução

O método que poderia corresponder ao que você está tentando fazer é Marshal.copy, mas não são necessários os parâmetros apropriados para criar um método genérico.

Embora não seja possível escrever um método genérico com restrições genéricas que poderiam descrever o que é possível, nem todo tipo pode ser copiado usando uma maneira "insegura". Existem algumas exceções; As aulas são uma delas.

Aqui está um código de amostra:

    public unsafe static T[] Create<T>(void* source, int length)
    {
        var type = typeof(T);
        var sizeInBytes =  Marshal.SizeOf(typeof(T));

        T[] output = new T[length];

        if (type.IsPrimitive)
        {
            // Make sure the array won't be moved around by the GC 
            var handle = GCHandle.Alloc(output, GCHandleType.Pinned);

            var destination = (byte*)handle.AddrOfPinnedObject().ToPointer();
            var byteLength = length * sizeInBytes;

            // There are faster ways to do this, particularly by using wider types or by 
            // handling special lengths.
            for (int i = 0; i < byteLength; i++)
                destination[i] = ((byte*)source)[i];

            handle.Free();
        }
        else if (type.IsValueType)
        {
            if (!type.IsLayoutSequential && !type.IsExplicitLayout)
            {
                throw new InvalidOperationException(string.Format("{0} does not define a StructLayout attribute", type));
            }

            IntPtr sourcePtr = new IntPtr(source);

            for (int i = 0; i < length; i++)
            {
                IntPtr p = new IntPtr((byte*)source + i * sizeInBytes);

                output[i] = (T)System.Runtime.InteropServices.Marshal.PtrToStructure(p, typeof(T));
            }
        }
        else 
        {
            throw new InvalidOperationException(string.Format("{0} is not supported", type));
        }

        return output;
    }

    unsafe static void Main(string[] args)
    {
        var arrayDouble = Enumerable.Range(1, 1024)
                                    .Select(i => (double)i)
                                    .ToArray();

        fixed (double* p = arrayDouble)
        {
            var array2 = Create<double>(p, arrayDouble.Length);

            Assert.AreEqual(arrayDouble, array2);
        }

        var arrayPoint = Enumerable.Range(1, 1024)
                                   .Select(i => new Point(i, i * 2 + 1))
                                   .ToArray();

        fixed (Point* p = arrayPoint)
        {
            var array2 = Create<Point>(p, arrayPoint.Length);

            Assert.AreEqual(arrayPoint, array2);
        }
    }

O método pode ser genérico, mas não pode tomar um ponteiro de um tipo genérico. Isso não é um problema, já que a covariância dos ponteiros está ajudando, mas isso tem o efeito infeliz de impedir uma resolução implícita do tipo de argumento genérico. Você então precisa especificar o MakeArray explicitamente.

Eu adicionei um caso especial para as estruturas, onde é melhor ter tipos que especificam um layout de estrutura. Isso pode não ser um problema no seu caso, mas se os dados do ponteiro vieram do código C ou C ++ nativo, especificar um tipo de layout é importante (o CLR pode optar por reordenar os campos para ter um melhor alinhamento de memória).

Mas se o ponteiro estiver chegando exclusivamente a partir de dados gerados pelo código gerenciado, você poderá remover a verificação.

Além disso, se o desempenho for um problema, existem algoritmos melhores para copiar os dados do que fazer byte por byte. (Veja as inúmeras implementações de memcpy para referência)

Outras dicas

Parece que a pergunta se torna: como especificar um tipo genérico para ser um tipo simples.

unsafe void Foo<T>() : where T : struct
{
   T* p;
}

Dá o erro:
Não pode levar o endereço de, obter o tamanho ou declarar um ponteiro para um tipo gerenciado ('t')

Que tal agora?

static unsafe T[] MakeArray<T>(void* t, int length, int tSizeInBytes) where T:struct
{
    T[] result = new T[length];
    for (int i = 0; i < length; i++)
    {
        IntPtr p = new IntPtr((byte*)t + (i * tSizeInBytes));
        result[i] = (T)System.Runtime.InteropServices.Marshal.PtrToStructure(p, typeof(T));
    }

    return result;
}

Não podemos usar o tamanho do (t) aqui, mas o chamador pode fazer algo como

byte[] b = MakeArray<byte>(pBytes, lenBytes, sizeof(byte));

Eu não tenho ideia de tudo se o seguinte funcionaria, mas pode (pelo menos ele compila :):

public unsafe static T[] Create<T>(void* ptr, int length) where T : struct
{
    T[] array = new T[length];

    for (int i = 0; i < length; i++)
    {
        array[i] = (T)Marshal.PtrToStructure(new IntPtr(ptr), typeof(T));
    }

    return array;
}

A chave é usar Marshal.PtrToStructure para converter para o tipo correto.

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