【问题标题】:Convert an array of pointers to an array of IntPtr将指针数组转换为 IntPtr 数组
【发布时间】:2013-07-22 21:58:42
【问题描述】:

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

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

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**[]或其他任何东西,并且该方法应该能够处理所有情况。

【问题讨论】:

  • void*的数值是多少?
  • @CédricBignon 我不太明白你的问题。 void* 类型的变量可以具有与平台相关的地址范围内的任何数值,并且无论如何它都适合该平台上的 IntPtr。我添加了一个示例,说明它在这种情况下应该如何工作。
  • @TheJoker 是的,如果我在编译时知道指针的确切类型,我会使用显式转换。但我不知道,你建议使用什么转换?您期望什么样的未定义行为?
  • @nicholas 该方法应该能够处理 .NET 运行时中可能存在的所有类型的指针。我不知道可能的调用者可能会选择作为参数传递。我可以使用一组重载,只要它们覆盖所有可能的输入。
  • 需要注意的事情(来自 MSDN 文档(:“不支持在本机代码中使用指针数组对象,并且会为多种方法抛出 NotSupportedException。”GetValue 就是这样一种方法。

标签: c# .net arrays pointers intptr


【解决方案1】:

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

有两种合理的解决方法:

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

第一个比较麻烦,因为它需要使用 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);
}

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

【讨论】:

  • 很好,但它不能正确处理具有相同形状的 OP 的 void*[,] 情况。它将数组扁平化为一维,但我相信,如果不使用反射,这是不可避免的。
  • @Xcelled194,我会挑战即使没有问题的指针约束,创建与原始数组具有相同等级和维度的任意数组也是一个具有挑战性的问题。对于锯齿状数组,这可以通过递归来完成,对于矩形多维数组,我不确定它是否可以完成。
  • 这必须通过反射和 SetValue() 来完成,我几乎可以肯定。但我的直觉告诉我这是可以做到的。
  • 是的,我刚刚编写了创建矩形多维数组的代码。设置值需要使用 SetValue 方法,但可以做到。 ` var a = 新字节[10, 3]; int[] lens = new int[a.Rank], lbs = new int[a.Rank]; for (int i = 0; i
  • 多维数组是线性存储的,所以main方法IntToPtrArray还是可以的。为简单起见,我将 ReshapeArray 函数作为外部助手;但是,它也可以很好地在 IntToPtrArray 函数中实现。锯齿状数组会更难。
【解决方案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。没办法,没办法。所以你无法从数组中获取值!例如,如果您在 long* 的数组上调用 GetValue,您将得到“类型不受支持”。

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

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

我想你可以写成:

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

然后,您将拥有System.Array 元数据和您可以使用的表单中的实际数据。

【讨论】:

  • 您的倒数第二个建议是真正可行的建议。仅传递指向数组的指针的最终建议不会,因为不允许您获取 System.Array 对象的地址(除非您想进行编组)。使用fixed 关键字将数组固定为void**,并将原始数组传递给元数据,这是用一块石头杀死所有OP 请求的唯一方法。
  • @nicholas:谢谢。你说得对。我做了更多的探索。查看我的更新。
【解决方案3】:

即使问题已经得到很好的回答,我还是觉得有必要给未来的读者留言/警告。

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

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

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

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

【讨论】:

    猜你喜欢
    • 2015-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-07
    相关资源
    最近更新 更多