在 C# 中传递 IntPtr 指针后,在非托管 C++ 代码中分配数组,编组类型

Posted

技术标签:

【中文标题】在 C# 中传递 IntPtr 指针后,在非托管 C++ 代码中分配数组,编组类型【英文标题】:Allocating array in unmanaged C++ code after passing IntPtr pointer in C#, Marshaling Types 【发布时间】:2013-03-05 07:17:15 【问题描述】:

我正在尝试实施一些涉及在托管 C# 和非托管 C++ 代码之间编组数组的项目。我面临一个问题,我在网上找到的解决方案似乎都不起作用。对于这方面的任何 cmet,我将不胜感激。

我没有提供完整的代码,而是显示问题的非常简化的部分。虽然它看起来像一个大块 - 它非常简单 - 只是概念性的。只是想提供尽可能多的全貌。

C++部分:

Object.h

class cObject

public:
    //...constructor, destructor...
    int Method_Known_Size(double* array, int size);
    int Method_Unknown_Size(double* array);
    ...
    void FreeArray(double* p);

Object.cpp

int Method_Known_Size(double* array, int size)

    //modify array somehow..
    for(int i=0; i<size; i++) array[i] = i;



int method_Unknown_Size(double* array)

    int size = 9;
    array = new double[size];
    for(int i=0; i<size; i++) array[i] = i;

(跳过 Caller.h) Caller.cpp

//...callers for constructor, destructor, for releasing unmanaged memory...
extern "C" int __stdcall Run_Known_Size(cObject* pObject, double* array, int size)

     return cObject->Method_Known_Size(array, size);


extern "C" int __stdcall Run_Unknown_Size(cObject* pObject, double* array)

     return cObject->Method_Unknown_Size(array);


extern "C" void __stdcall Release(cObject* cObject, double* array)

     if(cObject != NULL) cObject->FreeArray(array);

所以,基本上Run_Known_Size 方法只是修改已经由 C# 分配的内存,Run_Unknown_Size 创建数组并修改它。

C#部分

public class DllWrapper: IDisposable
       
    /* Creating an object, disposing,...
    [DllImport("cObject.dll")]
    CreateObject();...DisposeObject(IntPtr pObject);
    ...CallFreeArray(IntPtr pArray);*/

    [DllImport("cObject.dll")]
    private static extern int CallRun_Known_Size(IntPtr pObject, 
           [Out] double [] arr_allocated, int size);

    [DllImport("cObject.dll")]
    private static extern int CallRun_Unknown_Size(IntPtr pObject, 
           [Out] IntPtr arr_not_allocated);                         

    private IntPtr m_pNativeObject;

    public DllWrapper()
    
        this.m_pNativeObject = CreateObject();
    

    public void Dispose()
    
        Dispose(true);
    

    protected virtual void Dispose(bool bDisposing)
    
        if (this.m_pNativeObject != IntPtr.Zero)
        
            DisposeObject(this.m_pNativeObject);
            this.m_pNativeObject = IntPtr.Zero;
        

        if (bDisposing)
        
            GC.SuppressFinalize(this);
        
    

    ~DllWrapper()
    
        Dispose(false);
    

    public void ReleaseUnmanAraray(IntPtr pArr)
    
        CallFreeArray(pArr);
    

    public int Run_Known_Size(double[] arr_allocated, int size)
    
        return CallRun_Known_Size(this.m_pNativeObject, arr_allocated, size);
    

    public int Run_Unknown_Size(IntPtr arr_not_allocated)
    
        return CallRun_Known_Size(this.m_pNativeObject, arr_not_allocated);
    


static void Main(string[] args)

    double[] alloc_arr = new double[]  1, 5, 3, 3, 5, 5, 8, 9,1 ;
    int size = 9;            


    double[] Arr_for_Copy = new double[size];

    IntPtr pArr = new IntPtr();

    DllWrapper wrapper = new DllWrapper();

    int res1 = Run_Known_Size(alloc_arr, size);
    int res2 = Run_Unknown_size(pArr);

    if (pArr != IntPtr.Zero) // pArr IS ZERO ALWAYS!!!!!!
    
        Marshal.Copy(pArr, Arr_for_Copy, 0, size);
    
    else
    
        Console.WriteLine("Pointer was zero again");
    

    wrapper.ReleaseUnmanAraray(pScores);
    wrapper.Dispose();

    Console.ReadLine();

在 C# 中分配的数组一切正常——它们是从 C++ 修改而来的,没有错误。但是如果我不知道数组的大小,因此无法预分配数组,我找到的唯一解决方案是传递 [Out] IntPtr 并让 C++ 管理内存,分配和修改数组。然后返回的 IntPtr 可以编组到 C# 的 double[] 数组,因为我们已经知道大小(为简化起见,我只是将数字 4 作为大小,但我通过 int* size 来确定大小)。

在传递 IntPtr 并基于此指针在 C++ 中创建数组后,我所有的试验都以零指针(没有错误)结束。

我见过涉及 COM 对象的解决方案,但由于可移植性问题,我不得不避免使用它。

提前致谢。

【问题讨论】:

【参考方案1】:

Method_Unknown_Size 的参数是double*,而您正在更改参数本身。如果要更改调用者发送的原始值,则应将参数定义为指向数组的指针,这意味着指向双精度指针的指针引用指向双精度指针

你还应该以某种方式告诉调用者数组的大小(我猜你已经做到了)。

C++:

int method_Unknown_Size(double *&array)

    int size = 9;
    array = new double[size];
    for(int i=0; i<size; i++) array[i] = i;
    return size;


void FreeArray(double *&p)

    delete[] p;
    p = NULL;


extern "C" int __stdcall Run_Unknown_Size(cObject *pObject, double *&array)

     return cObject->Method_Unknown_Size(array);


extern "C" void __stdcall Release(cObject *cObject, double *&array)

     if(cObject != NULL) cObject->FreeArray(array);

C#:

[DllImport("cObject.dll")]
private static extern int Run_Unknown_Size(IntPtr pObject, 
       out IntPtr arr_not_allocated);

[DllImport("cObject.dll")]
private static extern int Release(IntPtr pObject,
       ref IntPtr arr_not_allocated);


// to allocate the array:
IntPtr pArr;
int res2 = Run_Unknown_size(m_pNativeObject, out pArr);

// to free the array:
Release(m_pNativeObject, ref pArr);

这绝对有效!如果没有,请告诉我!

【讨论】:

非常感谢您的快速回复。我的工作机器上有这个代码,所以明天我一定会检查出来。我试过双指针,但方式略有不同。我希望这会奏效!只是一个简单的问题:在 C# 部分中,您将属性“[Out] out”。坐是什么意思? 不客气。我猜这里不需要[Out],但我可以肯定地说。它告诉编组器将更改放回变量,但它仅适用于对象和数组的内容。如果对象自身发生变化,[Out] 将无济于事!这里是out 参数修饰符的用途。它将参数作为reference(或pointer)传递。请参阅here 了解更多信息。 这很有趣。这是我第一个涉及托管/非托管代码的项目,你肯定让我看到了一些在网络或教科书中不容易找到的东西。再次感谢! 有效!非常感谢你。但现在我面临不同的问题 - 我无法释放为这个数组分配的内存......它给出了断言错误。你能帮忙吗 太棒了!非常非常感谢你。现在它正在发挥应有的作用,更重要的是,您的解决方案非常精确、清晰且非常有意义!非常感谢您的时间和帮助!

以上是关于在 C# 中传递 IntPtr 指针后,在非托管 C++ 代码中分配数组,编组类型的主要内容,如果未能解决你的问题,请参考以下文章

在 C# 中将 CodeElements 编组到 IntPtr

C#调用Win32 api时的内存操作

如何在 C# 中获取 IntPtr 指针的属性

C# 句柄指针的 JFrame 等效项

在非托管 C++ DLL 和托管 C# UI 之间发送信息

创建 COM 接口,返回一个在 C# 中编组为 IntPtr 的指针