将指针数组转换为IntPtr数组

时间:2013-07-23 17:19:08

标签: c# .net arrays pointers intptr

我陷入了一项看似微不足道的任务,需要你的帮助。

我需要编写一个带有以下签名的方法:

System.Array ToIntPtrArray(System.Array a)

其中实际参数可以是任何pointer type的数组(例如int*[]long**[]void*[,])并返回具有类型元素的相同形状的数组System.IntPtr具有与输入数组元素相同的数值。问题是,如果我事先不知道它们的类型,我不明白如何提取指针的数值。


例如,如果我事先知道我的参数总是void*[]类型,我可以按如下方式编写方法:

unsafe IntPtr[] ToIntPtrArray(void*[] a)
{
    var result = new IntPtr[a.Length];
    for (int i = 0; i < a.Length; i++)
        result[i] = (IntPtr) a[i];

    return result;
}

但问题是它可能不是void*[],而是void**[]或其他任何东西,并且该方法应该能够处理所有情况。

3 个答案:

答案 0 :(得分:3)

简短的回答是,这不能直接完成。原因是如果您传递转换函数,任何传统的具有索引功能的容器(System.ArrayCollections.IListArrayList等)执行索引操作都会尝试转换结果到System.Object。 C#中的指针不是来自Object,因此会导致SystemNotSupported或类似的异常。

有两种合理的解决方法:

  1. 在调用之前将指针数组转换为void指针数组 方法
  2. 之前将指针数组转换为void指针指针 调用方法。
  3. 第一个是相当麻烦的,因为它需要用for循环复制数组的整个内容。第二个选项需要传入数组的长度,因为它不再包含托管的System.Array对象。

    示例代码

    方法:

        unsafe Array ToIntPtrArray(void** a, int count)
        {
            IntPtr[] intPtrArray = new IntPtr[count];
    
            for (int n = 0; n < count; n++)
                intPtrArray[n] = new IntPtr(a[n]);
    
            return intPtrArray;
        }
    

    示例用法(整数指针数组):

    int*[] intPtrArray;
    
    // Code that initializes the values of intPtrArray
    
    fixed(int** ptr = &intPtrArray[0])
    {
       Array result = ToIntPtrArray((void**)ptr, intPtrArray.Length);
    }
    

    示例用法(void指针指针数组):

    void**[] voidPtrPtrArray;
    
    // Code that initializes the values of voidPtrPtrArray
    
    fixed(void*** ptr = &voidPtrPtrArray[0])
    {
        Array result = ToIntPtrArray((void**)ptr, voidPtrPtrArray.Length);
    }
    

    示例用法(多维int指针数组):

    int*[,] int2dArray;
    
    // Code that initializes the values of int2dArray
    
    fixed(int** ptr = &int2dArray[0,0])
    {
        Array result = ToIntPtrArray((void**)ptr, TotalSize(int2dArray));
        Array reshaped = ReshapeArray(result,int2dArray);
    }
    

    其中TotalSizeReshapeArray是为处理多维数组而编写的辅助函数。有关如何完成此操作的提示,请参阅:Programatically Declare Array of Arbitrary Rank

答案 1 :(得分:2)

这是一个相当困难的问题。创建一个正确形状的数组也不错。

unsafe System.Array ToIntPtrArray(System.Array a)
{
    int[] lengths = new int[a.Rank];
    int[] lowerBounds = new int[a.Rank];
    for (int i = 0; i < a.Rank; ++i)
    {
        lengths[i] = a.GetLength(i);
        lowerBounds[i] = a.GetLowerBound(i);
    }
    Array newArray = Array.CreateInstance(typeof (IntPtr), lengths, lowerBounds);

    // The hard part is iterating over the array.
    // Multiplying the lengths will give you the total number of items.
    // Then we go from 0 to n-1, and create the indexes
    // This loop could be combined with the loop above.
    int numItems = 1;
    for (int i = 0; i < a.Rank; ++i)
    {
        numItems *= lengths[i];
    }

    int[] indexes = new int[a.Rank];
    for (int i = 0; i < numItems; ++i)
    {
        int work = i;
        int inc = 1;
        for (int r = a.Rank-1; r >= 0; --r)
        {
            int ix = work%lengths[r];
            indexes[r] = lowerBounds[r] + ix;
            work -= (ix*inc);
            inc *= lengths[r];
        }

        object obj = a.GetValue(indexes);
        // somehow create an IntPtr from a boxed pointer
        var myPtr = new IntPtr((long) obj);
        newArray.SetValue(myPtr, indexes);
    }
    return newArray;
}

这会创建一个正确的类型和形状(尺寸和长度)的数组,但它有一个问题。用于从数组中获取项目的GetValue方法返回object。并且您不能将指针类型强制转换为object。没有办法,没有办法。所以你无法从数组中获取值!例如,如果您在GetValue数组上调用long*,则会获得“不支持的类型。”

我认为您需要某种方法将奇形状的数组复制到int*(或任何其他指针类型)的一维数组中。然后,您可以直接索引临时数组并获取值以填充IntPtr数组。

这是一个有趣的鸡蛋问题。如果您将其作为System.Array传递,则无法从中获取项目,因为没有从objectint*(或任何其他指针类型)的转换路径。但是如果你将它作为指针类型传递(即int**),那么你就无法得到它的形状。

我想你可以把它写成:

unsafe System.Array ToIntPtrArray(System.Array a, void** aAsPtr)

然后,您可以使用System.Array元数据和实际数据。

答案 2 :(得分:0)

即使这个问题得到了很好的解答,我觉得有必要给未来的读者留下一个注释/警告。

CLR旨在让您安全,或者至少尽可能安全。它通过(除其他外)类型安全和抽象内存操作来实现这一点。虽然您可以使用不安全的块关闭其中一些保护,但某些保护会硬编码到编译器/运行时。为了规避这个额外的障碍,必须使用一些hackey,可能还要慢一些代码。虽然这些方法有效,但经验告诉我,做这些事情会导致问题,无论是运行时的变化,未来需要修改代码段的程序员,还是你自己需要对这些代码做些什么。稍后在代码中指针。

此时,我会认真考虑使用Managed C ++编写一个帮助器dll来处理事物的“原始指针”。我的理由是,通过使用Unsafe,你已经抛出了CLR提供的许多保护。你可能会发现更容易在没有任何额外的保护措施的情况下工作。更不用说你可以使用指针到它们的完整范围,然后在你完成时将它们强制转换回intptr。如果没有别的,你可能想看看在C ++中实现ToIntPtrArray。仍然在C#端传递指针,但是逃避了CLR的疏忽。

请注意,我并不是说每次使用“不安全”时都应该破坏C ++。相反 - 不安全会让你做很多事 - 但在这种情况下,可能需要考虑C ++助手。

免责声明:我在托管C ++方面做得并不多。可能是我完全错了,CLR仍会监控指针。如果一些更有经验的灵魂可以评论并告诉我任何一种方式,那将非常感激。