10

我被困在一个看似微不足道的任务上,需要你的帮助。

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

System.Array ToIntPtrArray(System.Array a)

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


例如,如果我事先知道我的参数始终是 type 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*[], butvoid**[]或其他任何东西,并且该方法应该能够处理所有情况。

4

3 回答 3

3

简短的回答是,这不能直接完成。原因是,如果您传递您的转换函数,任何执行索引操作的常规索引容器(System.Array、、、Collections.IListArrayList)都将尝试将结果转换为System.Object. C# 中的指针不是从 派生的Object,因此这将导致一个SystemNotSupported或类似的异常。

有两种合理的解决方法:

  1. 在调用方法之前将指针数组转换为 void 指针数组。
  2. 在调用方法之前将指针数组转换为 void 指针指针。

第一个相当麻烦,因为它需要使用 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**[] 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);
}

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

于 2013-07-23T19:33:10.717 回答
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数组。

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

我想你可以写成:

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

然后System.Array,您可以以可以使用的形式获得元数据和实际数据。

于 2013-07-23T21:10:11.527 回答
0

尽管这个问题已经得到了很好的回答,但我觉得有必要给未来的读者留言/警告。

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

在这一点上,我会认真考虑用托管 C++ 编写的帮助程序 dll 来处理“原始指针”方面的事情。我的理由是,通过使用 Unsafe,您已经放弃了 CLR 提供的许多保护。您可能会发现在不受任何额外保护的情况下工作起来更容易。更不用说您可以完全使用指针,然后在完成后将它们转换回 intptr。如果不出意外,您可能想看看在 C++ 中实现 ToIntPtrArray。仍然在 C# 端传递它的指针,但避开 CLR 的监督。

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

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

于 2013-07-23T23:01:54.737 回答