2009-06-12 431 views
2

我想一個byte*轉換爲byte[],但我也希望有一個可重複使用的功能來做到這一點:C#:通用指針轉換成數組

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; 
} 

不幸的是我得到一個編譯錯誤,因爲牛逼可能一個「.NET託管類型」,我們不能指向那些。更令人沮喪的是,沒有可以將T限制爲「非託管類型」的泛型類型約束。有沒有一個內置的.NET函數來做到這一點?有任何想法嗎?

回答

5

的方法,可以匹配你正在試圖做的是Marshal.Copy,但它不採取適當的參數來制定一個通用的方法。

雖然在那裏不可能編寫一個泛型約束的通用方法來描述什麼是可能的,但並不是每個類型都可以使用「不安全」方式進行復制。有一些例外;類是其中之一。

下面是一個示例代碼:

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); 
     } 
    } 

方法可以是通用的,但它不能採取一般類型的指針。這不是問題,因爲指針協變是有幫助的,但是這具有阻止泛型參數類型的隱式解析的不良效果。然後您必須明確指定MakeArray。

我已經爲結構添加了一個特殊情況,最好是指定struct layout的類型。這可能不是問題,但如果指針數據來自本機C或C++代碼,指定佈局類型很重要(CLR可能會選擇重新排列字段以獲得更好的內存對齊)。

但是,如果指針完全來自託管代碼生成的數據,那麼您可以刪除該檢查。另外,如果性能是一個問題,那麼複製數據的算法比逐字節地執行要好。 (請參閱memcpy的無數實現以供參考)

0

我不知道如果任何以下的工作,但它可能(至少它編譯:):

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; 
} 

的關鍵是使用Marshal.PtrToStructure轉換爲正確的類型。

+0

這不起作用,因爲T可能不是結構體 - 字節,int,long等。 – wj32 2009-06-12 09:04:06

+0

看起來很接近但我看不到指針正在遞增 - 它將始終使用第一個字節。另外,是否有無效的原因*? – 2009-06-12 09:05:40

1

似乎問題變成:如何指定泛型類型爲簡單類型。

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

給出了錯誤:
不能走的地址,獲取的大小,或宣佈一個指向託管類型(「T」)

1

這個怎麼樣?

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; 
} 

我們不能使用sizeof(T)在這裏,但主叫方可以這樣做

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