将指针数组转换为IntPtr数组

时间:2021-07-08 01:31:17

I'm stuck on a seemingly trivial task and need your help.

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

I need to write a method with the following signature:

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

System.Array ToIntPtrArray(System.Array a)

where an actual argument can be an array of any pointer type (e.g. int*[], long**[], void*[,]) and returning an array of the same shape with elements of type System.IntPtr having the same numeric values as elements of an input array. The problem is that I do not understand how to extract numeric values of pointers if I do not know their types beforehand.

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


For example, if I knew beforehand that my argument is always of type void*[], I could write the method as follows:

例如,如果我事先知道我的参数总是类型为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;
}

But the problem is it could be not void*[], but void**[] or anything else, and the method should be able to handle all cases.

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

3 个解决方案

#1


3  

The short answer is, this cannot be done directly. The reasons are that if you pass your conversion function any of the conventional index-capable containers (System.Array, Collections.IList, ArrayList, etc.) performing the index operations will attempt to cast the result to System.Object. Pointers in C# do not derive from Object, so this will result in an SystemNotSupported or similar exception.

简而言之,这是不能直接做到的。原因是,如果您将转换函数传递给任何传统的可索引容器(系统)。数组、集合。执行索引操作时,将尝试将结果转换为System.Object。c#中的指针不是从对象派生的,因此这会导致系统不支持或类似的异常。

There are two reasonable workarounds:

有两个合理的解决办法:

  1. Convert the pointer arrays to void pointer arrays before calling the method.
  2. 在调用方法之前,将指针数组转换为空指针数组。
  3. Convert the pointer arrays to void pointer pointers before calling the method.
  4. 在调用方法之前,将指针数组转换为空指针指针指针指针。

The first one is rather cumbersome, as it requires duplicating the entire contents of the array with a for loop. The second option requires passing in the length of the array as it is no longer wrapped with a managed System.Array object.

第一个非常麻烦,因为它需要用for循环复制数组的全部内容。第二个选项需要传递数组的长度,因为数组不再被托管系统包装。数组对象。

Sample Code

示例代码

Method:

方法:

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

Sample Usage (integer pointer array):

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

int*[] intPtrArray;

// Code that initializes the values of intPtrArray

fixed(int** ptr = &intPtrArray[0])
{
   Array result = ToIntPtrArray((void**)ptr, intPtrArray.Length);
}

Sample Usage (void pointer pointer array):

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

void**[] voidPtrPtrArray;

// Code that initializes the values of voidPtrPtrArray

fixed(void*** ptr = &voidPtrPtrArray[0])
{
    Array result = ToIntPtrArray((void**)ptr, voidPtrPtrArray.Length);
}

Sample Usage (multidimensional int pointer array):

示例用法(多维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);
}

Where TotalSize and ReshapeArray are helper functions that are written to deal with multi-dimensional arrays. For tips on how to accomplish this see: Programatically Declare Array of Arbitrary Rank.

其中TotalSize和ReshapeArray是用于处理多维数组的辅助函数。有关如何实现此目标的提示,请参见:编程地声明任意等级的数组。

#2


2  

This is a rather difficult problem. Creating an array of the proper shape isn't too bad.

这是一个相当困难的问题。创建一个适当形状的数组并不太糟糕。

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

That creates an array of the right type and shape (dimensions and length), but it has a problem. The GetValue method, which you use to get an item from the array, returns an object. And you can't cast a pointer type to an object. No way, no how. So you can't get the value from the array! If you call GetValue on an array of long*, for example, you'll get "type not supported."

它创建了一个正确类型和形状的数组(尺寸和长度),但是它有一个问题。GetValue方法(用于从数组中获取项)返回一个对象。不能将指针类型转换为对象。没有办法,没有如何。所以你不能从数组中获得值!例如,如果在一个长*的数组上调用GetValue,就会得到“不支持类型”。

I think you need some way to copy that oddly-shaped array to a one-dimensional array of int* (or any other pointer type). Then you could directly index the temporary array and get the values to populate your IntPtr array.

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

It's an interesting chicken-and-egg problem. If you pass it as a System.Array, then you can't get items from it because there's no conversion path from object to int* (or any other pointer type). But if you pass it as a pointer type (i.e. int**), then you can't get the shape of the thing.

这是一个有趣的先有鸡还是先有蛋的问题。如果你把它作为一个系统传递。数组,那么您就不能从它获取项,因为没有从对象到int*(或任何其他指针类型)的转换路径。但是如果您将它作为指针类型(例如int**)传递,那么您就无法得到它的形状。

I suppose you could write it as:

我想你可以这样写:

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

You then have the System.Array metadata and the actual data in a form that you can use.

然后就有了系统。数组元数据和您可以使用的表单中的实际数据。

#3


0  

Even though the question has been answered well, I feel the need to leave a note/warning to future readers.

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

The CLR is designed to keep you safe, or at least as safe as possible. It accomplishes this with (among other things) type safety and abstracting memory operations. While you can turn some of these protections off with the unsafe block, some protections are hardcoded into the compiler/runtime. In order to circumvent this additional hurdle, one must resort to some hackey, and possibly slow, code. While these methods work, experience has taught me that doing such things leads to problems down the road, whether it be a change in the runtime, a future programmer needing to modify that segment of code, or you yourself needing to do something else with those pointers later on in the code.

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

At this point, I would seriously consider a helper dll written in Managed C++ to handle the "raw pointer" side of things. My reasoning is that by using Unsafe, you're already throwing out many protections the CLR offers. You may find it easier to work unencumbered by any additional, baked in protections. Not to mention you can use pointers to their full extent, and then cast them back to intptr when you're done. If nothing else, you may want to look at implementing ToIntPtrArray in C++. Still pass it pointers on the C# side, but escape the CLR's oversight.

此时,我将认真考虑使用托管c++编写的助手dll来处理“原始指针”方面的内容。我的推理是,通过使用不安全,您已经放弃了CLR提供的许多保护。你可能会发现不受任何额外保护的工作更容易。更不用提你可以充分利用指针,然后在你完成时将它们转换回intptr。如果没有其他内容,您可能需要考虑在c++中实现ToIntPtrArray。仍然在c#端传递它的指针,但是避免CLR的疏忽。

Note that I'm not saying that every time you use "unsafe" you should bust out the C++. Quite contrary - unsafe will allow you to do quite a bit - but in this instance, a C++ helper is probably something to consider.

注意,我并不是说每次您使用“不安全”时都应该使用c++。相反——不安全将允许您做很多事情——但是在本例中,可能需要考虑使用c++助手。

Disclaimer: I have not done a whole lot with managed C++. It could be that I am totally wrong and the CLR would still monitor the pointers. If some more experienced soul could comment and tell me either way, It'd be much appreciated.

免责声明:我还没有对托管c++做很多工作。可能是我完全错了,CLR仍然会监控指针。如果有更有经验的人能评论并告诉我这两种方式,我将不胜感激。

#1


3  

The short answer is, this cannot be done directly. The reasons are that if you pass your conversion function any of the conventional index-capable containers (System.Array, Collections.IList, ArrayList, etc.) performing the index operations will attempt to cast the result to System.Object. Pointers in C# do not derive from Object, so this will result in an SystemNotSupported or similar exception.

简而言之,这是不能直接做到的。原因是,如果您将转换函数传递给任何传统的可索引容器(系统)。数组、集合。执行索引操作时,将尝试将结果转换为System.Object。c#中的指针不是从对象派生的,因此这会导致系统不支持或类似的异常。

There are two reasonable workarounds:

有两个合理的解决办法:

  1. Convert the pointer arrays to void pointer arrays before calling the method.
  2. 在调用方法之前,将指针数组转换为空指针数组。
  3. Convert the pointer arrays to void pointer pointers before calling the method.
  4. 在调用方法之前,将指针数组转换为空指针指针指针指针。

The first one is rather cumbersome, as it requires duplicating the entire contents of the array with a for loop. The second option requires passing in the length of the array as it is no longer wrapped with a managed System.Array object.

第一个非常麻烦,因为它需要用for循环复制数组的全部内容。第二个选项需要传递数组的长度,因为数组不再被托管系统包装。数组对象。

Sample Code

示例代码

Method:

方法:

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

Sample Usage (integer pointer array):

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

int*[] intPtrArray;

// Code that initializes the values of intPtrArray

fixed(int** ptr = &intPtrArray[0])
{
   Array result = ToIntPtrArray((void**)ptr, intPtrArray.Length);
}

Sample Usage (void pointer pointer array):

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

void**[] voidPtrPtrArray;

// Code that initializes the values of voidPtrPtrArray

fixed(void*** ptr = &voidPtrPtrArray[0])
{
    Array result = ToIntPtrArray((void**)ptr, voidPtrPtrArray.Length);
}

Sample Usage (multidimensional int pointer array):

示例用法(多维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);
}

Where TotalSize and ReshapeArray are helper functions that are written to deal with multi-dimensional arrays. For tips on how to accomplish this see: Programatically Declare Array of Arbitrary Rank.

其中TotalSize和ReshapeArray是用于处理多维数组的辅助函数。有关如何实现此目标的提示,请参见:编程地声明任意等级的数组。

#2


2  

This is a rather difficult problem. Creating an array of the proper shape isn't too bad.

这是一个相当困难的问题。创建一个适当形状的数组并不太糟糕。

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

That creates an array of the right type and shape (dimensions and length), but it has a problem. The GetValue method, which you use to get an item from the array, returns an object. And you can't cast a pointer type to an object. No way, no how. So you can't get the value from the array! If you call GetValue on an array of long*, for example, you'll get "type not supported."

它创建了一个正确类型和形状的数组(尺寸和长度),但是它有一个问题。GetValue方法(用于从数组中获取项)返回一个对象。不能将指针类型转换为对象。没有办法,没有如何。所以你不能从数组中获得值!例如,如果在一个长*的数组上调用GetValue,就会得到“不支持类型”。

I think you need some way to copy that oddly-shaped array to a one-dimensional array of int* (or any other pointer type). Then you could directly index the temporary array and get the values to populate your IntPtr array.

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

It's an interesting chicken-and-egg problem. If you pass it as a System.Array, then you can't get items from it because there's no conversion path from object to int* (or any other pointer type). But if you pass it as a pointer type (i.e. int**), then you can't get the shape of the thing.

这是一个有趣的先有鸡还是先有蛋的问题。如果你把它作为一个系统传递。数组,那么您就不能从它获取项,因为没有从对象到int*(或任何其他指针类型)的转换路径。但是如果您将它作为指针类型(例如int**)传递,那么您就无法得到它的形状。

I suppose you could write it as:

我想你可以这样写:

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

You then have the System.Array metadata and the actual data in a form that you can use.

然后就有了系统。数组元数据和您可以使用的表单中的实际数据。

#3


0  

Even though the question has been answered well, I feel the need to leave a note/warning to future readers.

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

The CLR is designed to keep you safe, or at least as safe as possible. It accomplishes this with (among other things) type safety and abstracting memory operations. While you can turn some of these protections off with the unsafe block, some protections are hardcoded into the compiler/runtime. In order to circumvent this additional hurdle, one must resort to some hackey, and possibly slow, code. While these methods work, experience has taught me that doing such things leads to problems down the road, whether it be a change in the runtime, a future programmer needing to modify that segment of code, or you yourself needing to do something else with those pointers later on in the code.

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

At this point, I would seriously consider a helper dll written in Managed C++ to handle the "raw pointer" side of things. My reasoning is that by using Unsafe, you're already throwing out many protections the CLR offers. You may find it easier to work unencumbered by any additional, baked in protections. Not to mention you can use pointers to their full extent, and then cast them back to intptr when you're done. If nothing else, you may want to look at implementing ToIntPtrArray in C++. Still pass it pointers on the C# side, but escape the CLR's oversight.

此时,我将认真考虑使用托管c++编写的助手dll来处理“原始指针”方面的内容。我的推理是,通过使用不安全,您已经放弃了CLR提供的许多保护。你可能会发现不受任何额外保护的工作更容易。更不用提你可以充分利用指针,然后在你完成时将它们转换回intptr。如果没有其他内容,您可能需要考虑在c++中实现ToIntPtrArray。仍然在c#端传递它的指针,但是避免CLR的疏忽。

Note that I'm not saying that every time you use "unsafe" you should bust out the C++. Quite contrary - unsafe will allow you to do quite a bit - but in this instance, a C++ helper is probably something to consider.

注意,我并不是说每次您使用“不安全”时都应该使用c++。相反——不安全将允许您做很多事情——但是在本例中,可能需要考虑使用c++助手。

Disclaimer: I have not done a whole lot with managed C++. It could be that I am totally wrong and the CLR would still monitor the pointers. If some more experienced soul could comment and tell me either way, It'd be much appreciated.

免责声明:我还没有对托管c++做很多工作。可能是我完全错了,CLR仍然会监控指针。如果有更有经验的人能评论并告诉我这两种方式,我将不胜感激。