如何:使用 P/Invoke 封送数组

通过使用 .NET Framework 平台调用 (P/Invoke) 支持,可以使用 CLR 字符串类型 String 调用可接受 C 样式字符串的本机函数。 建议尽可能使用 C++ 互操作功能,而不是 P/Invoke。 P/Invoke 几乎不提供编译时错误报告,不是类型安全的,并且实现起来很繁琐。 如果未托管的 API 打包为 DLL,并且源代码不可用,则 P/Invoke 是唯一选项。 否则,请参阅使用 C++ 互操作(隐式 P/Invoke)

示例

由于本机数组和托管数组在内存中的布局不同,要想成功地跨托管/非托管边界传递它们,需要进行转换,或者说封送。 本文演示了如何从托管代码将一个简单 (blitable) 项的数组传递给本机函数。

与一般的托管/非托管数据封送一样,DllImportAttribute 特性用于为使用的每个本机函数创建一个托管入口点。 在采用数组作为自变量的函数中,必须使用 MarshalAsAttribute 特性来指定如何封送数据。 在以下示例中,UnmanagedType 枚举用于指示托管数组是作为 C 样式的数组封送的。

以下代码由一个非托管模块和一个托管模块组成。 非托管模块是一个 DLL,用于定义接受整数数组的函数。 第二个模块是一个托管命令行应用程序,它导入了此函数,但用托管数组来定义它。 它使用 MarshalAsAttribute 特性指定在调用时应将数组转换为本机数组。

// TraditionalDll4.cpp
// compile with: /LD /EHsc
#include <iostream>

#define TRADITIONALDLL_EXPORTS
#ifdef TRADITIONALDLL_EXPORTS
#define TRADITIONALDLL_API __declspec(dllexport)
#else
#define TRADITIONALDLL_API __declspec(dllimport)
#endif

extern "C" {
   TRADITIONALDLL_API void TakesAnArray(int len, int[]);
}

void TakesAnArray(int len, int a[]) {
   printf_s("[unmanaged]\n");
   for (int i=0; i<len; i++)
      printf("%d = %d\n", i, a[i]);
}

托管模块是使用 /clr 编译的。

// MarshalBlitArray.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;

value struct TraditionalDLL {
   [DllImport("TraditionalDLL4.dll")]
   static public void TakesAnArray(
   int len,[MarshalAs(UnmanagedType::LPArray)]array<int>^);
};

int main() {
   array<int>^ b = gcnew array<int>(3);
   b[0] = 11;
   b[1] = 33;
   b[2] = 55;
   TraditionalDLL::TakesAnArray(3, b);

   Console::WriteLine("[managed]");
   for (int i=0; i<3; i++)
      Console::WriteLine("{0} = {1}", i, b[i]);
}

DLL 的任何部分都不会通过传统的 #include 指令向托管代码公开。 事实上,由于 DLL 仅在运行时被访问,因此使用 DllImportAttribute 导入的函数出现的问题无法在编译时检测到。

另请参阅

在 C++ 中使用显式 P/Invoke(DllImport 特性)