调用引用指针的非托管库函数

本文关键字:库函数 引用 指针 调用 | 更新日期: 2023-09-27 17:58:46

假设我们有一个C++函数,其原型如下:

int myFunction(int someNumber, int &arraySize, signed char *&array)
// Extra function to free allocated memory:
int freePointer(void* myPointer)

这个函数取一些数字,并根据这个数字创建一个数组。所以我们传递一个数字,得到一个数组。在C#中最好的方法是什么?

我的第一个方法:

[DllImport(...)]
internal static int myFunction(int someNumber, out int arraySize, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] out byte[] array)
// ...
byte[] myArray;
int discard;
int myNumber = 100;
myFunction(myNumber, discard, myArray);

但问题是,我需要释放非托管库分配的内存,而且我不知道如何将myArray的地址传递给freePointer()。而且,我不知道myArray的地址是否等于非托管库创建的数组的地址。

然后我的第二种方法是:

[DllImport(...)]
internal static int myFunction(int someNumber, out int arraySize, out IntPtr array)
// ...
byte[] myArray;
int myArraySize
IntPtr pointerToArray;
int myNumber = 100;
myFunction(myNumber, myArraySize, pointerToArray);
Marshal.Copy(pointerToArray, myArray, 0, myArraySize);
FreePointerHandler(pointerToArray.ToPointer());

但同样,我不确定使用方法.ToPointer()发送IntPtr的地址是否正确。

我不想介绍内存泄漏,你建议怎么做?(不使用不安全代码。)

编辑:是否应该使用ref而不是out,因为参数是对指针的引用?

调用引用指针的非托管库函数

你的第二种方法对我来说很合适

FreePointerHandler(pointerToArray.ToPointer());

我想你可以:

FreePointerHandler(pointerToArray);

我正在写一个测试,并将发布一个编辑。

refout

refout而言,这并不能完全映射到C++引用。当C#调用C#时,被调用函数需要设置一个out参数。对于interop调用,这是无法强制执行的,对于文档价值,在这种情况下,我更喜欢out

真正的问题

真正的问题是,如果你没有使用.Net pinvoke的经验,那么在调用没有任何直接可见副作用的函数时,你很难有信心。

@Hans Passant建议"通过调用它一百万次来测试它",以使任何内存泄漏都变得明显。虽然这种方法在这种情况下可能是最简单的,但它不会推广到没有关联内存资源的情况,如果失败,它不会提供任何额外信息。如果我有.dll的源代码,我更喜欢通过pinvoke调用进行调试;如果我没有,我更愿意编写模拟API实现。这样,当你尝试一个新的pinvoke功能时,你可以准确地看到发生了什么。如果出现问题,调试器中的跟踪可以帮助你发现哪里出了问题。

一个完整的pinvoke示例

以下是基于原始问题的完整示例。返回值没有记录,myFunction的详细信息也没有记录。该示例只是用(0,1,2)填充已分配的长度为3的数组。

这是DLL的头文件:

// testlib.h
#ifdef TESTLIB_EXPORTS
#define TESTLIB_API __declspec(dllexport)
#else
#define TESTLIB_API __declspec(dllimport)
#endif
extern "C" {
    TESTLIB_API int myFunction(int someNumber, int &arraySize, signed char *&array);
    TESTLIB_API int freePointer(void* myPointer);
}

.DLL的.cpp源文件:

// testlib.cpp : Defines the exported functions for the DLL application.
//
#include "stdafx.h"
#include "testlib.h"
#include <iostream>
using namespace std;

TESTLIB_API int myFunction(int someNumber, int &arraySize, signed char *&array)
{
    #ifdef _DEBUG
        cout << "myFunction enter " << someNumber << ", " << arraySize << ", 0x"  << &array << endl;
    #endif
    arraySize = 3;
    array = (signed char*)malloc(arraySize);
    #ifdef _DEBUG
        cout << "myFunction array: 0x" << (void *)array << endl;
    #endif
    for (int i = 0; i < arraySize; ++i)
    {
        array[i] = i;
    }
    #ifdef _DEBUG
        cout << "myFunction exit " << someNumber << ", " << arraySize << ", 0x"  << &array << endl;
    #endif
    return 0;
}
TESTLIB_API int freePointer(void* myPointer)
{
    #ifdef _DEBUG
        cout << "freePointer: 0x" << myPointer << endl;
    #endif
    free(myPointer);
    return 0;
}

C#来源:

using System;
using System.Runtime.InteropServices;
namespace InteropTest
{
    class InteropTest
    {
        [DllImport("testlib.dll")]
        private static extern int myFunction(int someNumber, out int arraySize, out IntPtr array);
        [DllImport("testlib.dll")]
        public static extern int freePointer(IntPtr pointer);
        public static byte[] myFunction(int myNumber)
        {
            int myArraySize;
            IntPtr pointerToArray;
            int iRet = myFunction(myNumber, out myArraySize, out pointerToArray);
            // should check iRet and if needed throw exception
            #if (DEBUG)
                Console.WriteLine();
                Console.WriteLine("InteropTest.myFunction myArraySize: {0}", myArraySize);
                Console.WriteLine();
            #endif
            byte[] myArray = new byte[myArraySize];
            Marshal.Copy(pointerToArray, myArray, 0, myArraySize);
            freePointer(pointerToArray);
            #if (DEBUG)
               Console.WriteLine();
            #endif
            return myArray;
        }
        static void Main(string[] args)
        {
            #if (DEBUG)
                Console.WriteLine("Start InteropTest");
                Console.WriteLine();
            #endif
            int myNumber = 123;
            byte[] myArray = myFunction(myNumber);
            for (int i = 0; i < myArray.Length; ++i)
            {
                Console.WriteLine("InteropTest myArray[{0}] = {1}", i, myArray[i]);
            }
        }
    }
}

调试生成将打印状态消息。它们显示myFunctionarray值是传递给freePointer的值。

我还为myFunction创建了一个C#接口函数。当pinvoke调用不是琐碎的时,您通常希望一次性计算出细节,并将其封装到一个函数中以供重用。