若要调用从非托管库导出的函数,.NET Framework 应用程序需要在托管代码中包含表示该非托管函数的函数原型。若要创建使平台调用能够正确封送数据的原型,您必须执行以下操作:
-
将 DllImportAttribute 属性应用于托管代码中的静态函数或方法。
-
用托管数据类型替换非托管数据类型。
可以使用随非托管函数提供的文档来构造等效的托管原型,方法是通过它的可选字段来应用属性并用托管数据类型替换非托管类型。有关如何应用 DllImportAttribute 的说明,请参见使用非托管 DLL 函数 。
本节提供了一些示例,演示如何创建托管函数原型以便向由非托管库导出的函数传递参数以及从这些导出的函数接收返回值。这些示例还演示何时使用 MarshalAsAttribute 属性和 Marshal 类显式封送数据。
平台调用数据类型
下表列出了在 Win32 API(在 Wtypes.h 中列出)和 C 样式函数中使用的数据类型。许多非托管库包含将这些数据类型作为参数传递并返回值的函数。第三列列出了在托管代码中使用的相应的 .NET Framework 内置值类型或类。某些情况下,您可以用大小相同的类型替换此表中列出的类型。
Wtypes.h 中的非托管类型 | 非托管 C 语言类型 | 托管类名 | 说明 |
---|---|---|---|
HANDLE | void* | System. . :: . IntPtr | 在 32 位 Windows 操作系统上为 32 位,在 64 位 Windows 操作系统上为 64 位。 |
BYTE | unsigned char | System. . :: . Byte | 8 位 |
SHORT | short | System. . :: . Int16 | 16 位 |
WORD | unsigned short | System. . :: . UInt16 | 16 位 |
INT | int | System. . :: . Int32 | 32 位 |
UINT | unsigned int | System. . :: . UInt32 | 32 位 |
LONG | long | System. . :: . Int32 | 32 位 |
BOOL | long | System.Int32 | 32 位 |
DWORD | unsigned long | System. . :: . UInt32 | 32 位 |
ULONG | unsigned long | System. . :: . UInt32 | 32 位 |
CHAR | char | System. . :: . Char | 用 ANSI 修饰。 |
LPSTR | char* | System. . :: . String 或者 System.Text. . :: . StringBuilder | 用 ANSI 修饰。 |
LPCSTR | Const char* | System. . :: . String 或者 System.Text. . :: . StringBuilder | 用 ANSI 修饰。 |
LPWSTR | wchar_t* | System. . :: . String 或者 System.Text. . :: . StringBuilder | 用 Unicode 修饰。 |
LPCWSTR | Const wchar_t* | System. . :: . String 或者 System.Text. . :: . StringBuilder | 用 Unicode 修饰。 |
FLOAT | Float | System. . :: . Single | 32 位 |
DOUBLE | Double | System. . :: . Double | 64 位 |
PinvokeLib.dll
以下代码定义由 Pinvoke.dll 提供的库函数。本节中描述的许多示例都调用此库。
// Interop.cpp : Defines the entry point for the DLL application.
//
#include "stdafx.h"
#include "PinvokeLib.h"
#include <strsafe.h>
#include <objbase.h>
#include <stdio.h>
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break ;
}
return TRUE;
}
// This is the constructor of a class that has been exported.
CTestClass::CTestClass()
{
m_member = 1;
}
int CTestClass::DoSomething( int i )
{
return i*i + m_member;
}
extern "C" PINVOKELIB_API CTestClass* CreateTestClass()
{
return new CTestClass();
}
extern "C" PINVOKELIB_API void DeleteTestClass( CTestClass* instance )
{
delete instance;
}
/*******************************************************************/
extern "C" PINVOKELIB_API int TestArrayOfInts( int * pArray, int size )
{
int result = 0;
for ( int i = 0; i < size; i++ )
{
result += pArray[ i ];
pArray[ i ] += 100;
}
return result;
}
/*******************************************************************/
extern "C" PINVOKELIB_API int TestRefArrayOfInts( int ** ppArray, int * pSize )
{
int result = 0;
// CoTaskMemAlloc must be used instead of the new operator
// because code on the managed side will call Marshal.FreeCoTaskMem
// to free this memory.
int * newArray = (int *)CoTaskMemAlloc( sizeof(int ) * 5 );
for ( int i = 0; i < *pSize; i++ )
{
result += (*ppArray)[ i ];
}
for ( int j = 0; j < 5; j++ )
{
newArray[ j ] = (*ppArray)[ j ] + 100;
}
CoTaskMemFree( *ppArray );
*ppArray = newArray;
*pSize = 5;
return result;
}
/*******************************************************************/
extern "C" PINVOKELIB_API int TestMatrixOfInts( int pMatrix[][COL_DIM], int row )
{
int result = 0;
for ( int i = 0; i < row; i++ )
{
for ( int j = 0; j < COL_DIM; j++ )
{
result += pMatrix[ i ][ j ];
pMatrix[ i ][ j ] += 100;
}
}
return result;
}
/*******************************************************************/
extern "C" PINVOKELIB_API int TestArrayOfStrings( char * ppStrArray[], int size )
{
int result = 0;
STRSAFE_LPWSTR temp;
for ( int i = 0; i < size; i++ )
{
result += strlen( ppStrArray[ i ] );
temp = (STRSAFE_LPWSTR)CoTaskMemAlloc( sizeof(char ) * 10 );
StringCchCopy( temp, sizeof(temp), (STRSAFE_LPWSTR)"123456789" );
// CoTaskMemFree must be used instead of delete to free memory.
CoTaskMemFree( ppStrArray[ i ] );
ppStrArray[ i ] = (char *) temp;
}
return result;
}
/*******************************************************************/
extern "C" PINVOKELIB_API int TestArrayOfStructs( MYPOINT* pPointArray, int size )
{
int result = 0;
MYPOINT* pCur = pPointArray;
for ( int i = 0; i < size; i++ )
{
result += pCur->x + pCur->y;
pCur->y = 0;
pCur++;
}
return result;
}
/*******************************************************************/
extern "C" PINVOKELIB_API int TestArrayOfStructs2( MYPERSON* pPersonArray, int size )
{
int result = 0;
MYPERSON* pCur = pPersonArray;
STRSAFE_LPWSTR temp;
for ( int i = 0; i < size; i++ )
{
result += strlen( pCur->first ) + strlen( pCur->last );
temp = (STRSAFE_LPWSTR)CoTaskMemAlloc( sizeof(char ) * ( strlen( pCur->last ) + 2 ));
StringCchCopy( temp, sizeof(temp), (STRSAFE_LPWSTR)"Mc" );
StringCbCat( temp, sizeof(temp), (STRSAFE_LPWSTR)pCur->last );
// CoTaskMemFree must be used instead of delete to free memory.
CoTaskMemFree( pCur->last );
pCur->last = (char *)temp;
pCur++;
}
return result;
}
/*******************************************************************/
extern "C" PINVOKELIB_API int TestStructInStruct( MYPERSON2* pPerson2 )
{
STRSAFE_LPWSTR temp = (STRSAFE_LPWSTR)CoTaskMemAlloc( sizeof(char ) * ( strlen( pPerson2->person->last ) + 2 ));
StringCchCopy( temp, sizeof(temp), (STRSAFE_LPWSTR)"Mc" );
StringCbCat( temp, sizeof(temp), (STRSAFE_LPWSTR)pPerson2->person->last );
CoTaskMemFree( pPerson2->person->last );
pPerson2->person->last = (char *)temp;
return pPerson2->age;
}
/*******************************************************************/
extern "C" PINVOKELIB_API void TestStructInStruct3( MYPERSON3 person3 )
{
printf( "/n/nperson passed by value:/n" );
printf( "first = %s last = %s age = %i/n/n" ,
person3.person.first, person3.person.last, person3.age );
}
/**********************************************************************/
extern "C" PINVOKELIB_API void TestUnion( MYUNION u, int type )
{
if (( type != 1 ) && ( type != 2 )) return ;
if ( type == 1 )
printf( "/n/ninteger passed: %i" , u.i );
else if ( type == 2 )
printf( "/n/ndouble passed: %f" , u.d );
}
/*******************************************************************/
extern "C" PINVOKELIB_API void TestUnion2( MYUNION2 u, int type )
{
if (( type != 1 ) && ( type != 2 )) return ;
if ( type == 1 )
printf( "/n/ninteger passed: %i" , u.i );
else if ( type == 2 )
printf( "/n/nstring passed: %s" , u.str );
}
/*******************************************************************/
extern "C" PINVOKELIB_API void TestCallBack( FPTR pf, int value )
{
printf( "/nReceived value: %i" , value );
printf( "/nPassing to callback..." );
bool res = (*pf)(value);
if ( res )
printf( "Callback returned true./n" );
else
printf( "Callback returned false./n" );
}
/*******************************************************************/
extern "C" PINVOKELIB_API void TestCallBack2( FPTR2 pf2, char * value )
{
printf( "/nReceived value: %s" , value );
printf( "/nPassing to callback..." );
bool res = (*pf2)(value);
if ( res )
printf( "Callback2 returned true./n" );
else
printf( "Callback2 returned false./n" );
}
/*******************************************************************/
extern "C" PINVOKELIB_API void TestStringInStruct( MYSTRSTRUCT* pStruct )
{
wprintf( L"/nUnicode buffer content: %s/n" , pStruct->buffer );
// Assuming that the buffer is big enough.
StringCbCat( pStruct->buffer, sizeof(pStruct->buffer), L"++" );
}
/*******************************************************************/
extern "C" PINVOKELIB_API void TestStringInStructAnsi( MYSTRSTRUCT2* pStruct )
{
printf( "/nAnsi buffer content: %s/n" , pStruct->buffer );
// Assuming that the buffer is big enough.
StringCbCat( (STRSAFE_LPWSTR) pStruct->buffer, sizeof(pStruct->buffer), (STRSAFE_LPWSTR)"++" );
}
/*******************************************************************/
extern "C" PINVOKELIB_API void TestOutArrayOfStructs( int * pSize, MYSTRSTRUCT2** ppStruct )
{
const int cArraySize = 5;
*pSize = cArraySize;
*ppStruct = (MYSTRSTRUCT2*)CoTaskMemAlloc( cArraySize * sizeof( MYSTRSTRUCT2 ));
MYSTRSTRUCT2* pCurStruct = *ppStruct;
STRSAFE_LPWSTR buffer;
for ( int i = 0; i < cArraySize; i++, pCurStruct++ )
{
pCurStruct->size = i;
buffer = (STRSAFE_LPWSTR)CoTaskMemAlloc( 4 );
StringCchCopy( buffer, sizeof(buffer), (STRSAFE_LPWSTR)"***" );
pCurStruct->buffer = (char *)buffer;
}
}
/*************************************************************************/
extern "C" PINVOKELIB_API char * TestStringAsResult()
{
STRSAFE_LPWSTR result = (STRSAFE_LPWSTR)CoTaskMemAlloc( 64 );
StringCchCopy( result, sizeof(result) , (STRSAFE_LPWSTR)"This is return value" );
return (char *) result;
}
/*************************************************************************/
extern "C" PINVOKELIB_API void SetData( DataType typ, void * object )
{
switch ( typ )
{
case DT_I2: printf( "Short %i/n" , *((short*)object) ); break ;
case DT_I4: printf( "Long %i/n" , *((long*)object) ); break ;
case DT_R4: printf( "Float %f/n" , *((float *)object) ); break ;
case DT_R8: printf( "Double %f/n" , *((double*)object) ); break ;
case DT_STR: printf( "String %s/n" , (char *)object ); break ;
default : printf( "Unknown type" ); break ;
}
}
/*************************************************************************/
extern "C" PINVOKELIB_API void TestArrayInStruct( MYARRAYSTRUCT* pStruct )
{
pStruct->flag = true ;
pStruct->vals[ 0 ] += 100;
pStruct->vals[ 1 ] += 100;
pStruct->vals[ 2 ] += 100;
}
/*************************************************************************/
// PinvokeLib.h : The header file for the DLL application.
//
// The following ifdef block is the standard way of creating macros which make exporting
// from a DLL simpler. All files within this DLL are compiled with the PINVOKELIB_EXPORTS
// symbol defined on the command line. this symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see
// PINVOKELIB_API functions as being imported from a DLL, wheras this DLL sees symbols
// defined with this macro as being exported.
#ifdef PINVOKELIB_EXPORTS
#define PINVOKELIB_API __declspec(dllexport)
#else
#define PINVOKELIB_API __declspec(dllimport)
#endif
// This class is exported from the PinvokeLib.dll
class PINVOKELIB_API CTestClass
{
public :
CTestClass( void );
int DoSomething( int i );
private :
int m_member;
};
extern "C" PINVOKELIB_API CTestClass* CreateTestClass();
extern "C" PINVOKELIB_API void DeleteTestClass( CTestClass* instance );
/**************************************************************************************/
extern "C" PINVOKELIB_API int TestArrayOfInts( int * pArray, int size );
extern "C" PINVOKELIB_API int TestRefArrayOfInts( int ** ppArray, int * pSize );
const int COL_DIM = 5;
extern "C" PINVOKELIB_API int TestMatrixOfInts( int pMatrix[][COL_DIM], int row );
extern "C" PINVOKELIB_API int TestArrayOfStrings( char * ppStrArray[], int size );
/**************************************************************************************/
typedef struct _MYPOINT
{
int x;
int y;
} MYPOINT;
extern "C" PINVOKELIB_API int TestArrayOfStructs( MYPOINT* pPointArray, int size );
/**************************************************************************************/
typedef struct _MYPERSON
{
char * first;
char * last;
} MYPERSON;
extern "C" PINVOKELIB_API int TestArrayOfStructs2( MYPERSON* pPersonArray, int size );
/**************************************************************************************/
typedef struct _MYPERSON2
{
MYPERSON* person;
int age;
} MYPERSON2;
extern "C" PINVOKELIB_API int TestStructInStruct( MYPERSON2* pPerson2 );
/**************************************************************************************/
typedef struct _MYPERSON3
{
MYPERSON person;
int age;
} MYPERSON3;
extern "C" PINVOKELIB_API void TestStructInStruct3( MYPERSON3 person3 );
/**************************************************************************************/
union MYUNION
{
int i;
double d;
};
extern "C" PINVOKELIB_API void TestUnion( MYUNION u, int type );
union MYUNION2
{
int i;
char str[128];
};
extern "C" PINVOKELIB_API void TestUnion2( MYUNION2 u, int type );
/**************************************************************************************/
typedef bool (CALLBACK *FPTR)( int i );
extern "C" PINVOKELIB_API void TestCallBack( FPTR pf, int value );
typedef bool (CALLBACK *FPTR2)( char * str );
extern "C" PINVOKELIB_API void TestCallBack2( FPTR2 pf2, char * value );
/**************************************************************************************/
typedef struct _MYSTRSTRUCT
{
wchar_t* buffer;
UINT size;
} MYSTRSTRUCT;
// buffer is an in/out param
extern "C" PINVOKELIB_API void TestStringInStruct( MYSTRSTRUCT* pStruct );
typedef struct _MYSTRSTRUCT2
{
char * buffer;
UINT size;
} MYSTRSTRUCT2;
// buffer is in/out param
extern "C" PINVOKELIB_API void TestStringInStructAnsi( MYSTRSTRUCT2* pStruct );
extern "C" PINVOKELIB_API void TestOutArrayOfStructs( int * pSize, MYSTRSTRUCT2** ppStruct );
/**************************************************************************************/
extern "C" PINVOKELIB_API char * TestStringAsResult();
/**************************************************************************************/
enum DataType
{
DT_I2 = 1,
DT_I4,
DT_R4,
DT_R8,
DT_STR
};
extern "C" PINVOKELIB_API void SetData( DataType typ, void * object );
/**************************************************************************************/
typedef struct _MYARRAYSTRUCT
{
bool flag;
int vals[ 3 ];
} MYARRAYSTRUCT;
extern "C" PINVOKELIB_API void TestArrayInStruct( MYARRAYSTRUCT* pStruct );
/**************************************************************************************/
封送字符串
平台调用复制字符串参数,并在必要时将其从 .NET Framework 格式 (Unicode) 转换为非托管格式 (ANSI)。由于托管字符串是不可变的,因此当函数返回时,平台调用不会将它们从非托管内存复制回托管内存。
下表列出了字符串的封送处理选项,描述了它们的用法,并提供了到相应的 .NET Framework 示例的链接。
字符串 | 说明 | 示例 |
---|---|---|
通过值传递。 | 将字符串作为 In 参数传递。 | MsgBox |
作为结果。 | 从非托管代码返回字符串。 | Strings |
通过引用传递。 | 使用 StringBuilder 将字符串作为 In/Out 参数传递。 | Buffers |
在结构中通过值传递。 | 在作为 In 参数的结构中传递字符串。 | 结构 |
在结构中通过引用传递 (char*) 。 | 在作为 In/Out 参数的结构中传递字符串。非托管函数需要指向字符缓冲区的指针,并且缓冲区大小是结构的成员。 | Strings |
在结构中通过引用传递 (char[]) 。 | 在作为 In/Out 参数的结构中传递字符串。非托管函数需要嵌入的字符缓冲区。 | OSInfo |
在类中通过值传递 (char*) 。 | 在作为 In/Out 参数的类中传递字符串。非托管函数需要指向字符缓冲区的指针。 | OpenFileDlg |
在类中通过值传递 (char[]) 。 | 在作为 In/Out 参数的类中传递字符串。非托管函数需要嵌入的字符缓冲区。 | OSInfo |
作为通过值传递的字符串数组。 | 创建通过值传递的字符串数组。 | 数组 |
作为包含通过值传递的字符串的结构数组。 | 创建包含字符串的结构数组,并且该数组是通过值传递的。 | 数组 |
封送类、结构和联合
类和结构在 .NET Framework 中是类似的。它们都可以具有字段、属性和事件。它们也有静态和非静态方法。一个显著区别是结构属于值类型而类属于引用类型。
下表列出类、结构和联合的封送处理选项;描述它们的用法;提供到相应的平台调用示例的链接。
类型 | 说明 | 示例 |
---|---|---|
通过值传递的类。 | 将具有整数成员的类作为 In/Out 参数传递,与托管的情形相同。 | SysTime |
通过值传递的结构。 | 将结构作为 In 参数传递。 | 结构 |
通过引用传递的结构。 | 将结构作为 In/Out 参数传递。 | OSInfo |
具有嵌套结构的结构(单一化)。 | 传递在非托管函数中表示具有嵌套结构的结构的类。该结构在托管原型中被单一化为一个大结构。 | FindFile |
具有嵌套结构的结构(未单一化)。 | 传递具有嵌入结构的结构。 | 结构 |
具有指向其他结构的指针的结构。 | 传递包含指向另一个结构的指针作为成员的结构。 | 结构 |
具有通过值传递的整数的结构数组。 | 传递只包含将整数作为 In/Out 参数的结构的数组。可以更改数组的成员。 | 数组 |
具有通过引用传递的整数和字符串的结构数组。 | 将包含整数和字符串的结构数组作为 Out 参数传递。被调用函数为该数组分配内存。 | OutArrayOfStructs |
具有值类型的联合。 | 传递具有值类型(整型和双精度型)的联合。 | Unions |
具有混合类型的联合。 | 传递具有混合类型(整型和字符串类型)的联合。 | Unions |
结构中的空值。 | 传递空引用(在 Visual Basic 中为 Nothing ),而不是对值类型的引用。 | HandleRef |
封送类型数组
在托管代码中,数组是包含一个或多个相同类型元素的引用类型。虽然数组是引用类型,但它们被作为 In 参数传递给非托管函数。此行为与将托管数组作为 In/Out 参数传递给托管对象的方式是不一致的。有关其他详细信息,请参见复制和锁定 。
下表列出数组的封送处理选项并描述它们的用法。有关相应的平台调用示例,请参见数组 。
数组 | 说明 |
---|---|
通过值传递的整数数组。 | 将整数数组作为 In 参数传递。 |
通过引用传递的整数数组。 | 将整数数组作为 In/Out 参数传递。 |
通过值传递的整数数组(二维)。 | 将整数矩阵作为 In 参数传递。 |
通过值传递的字符串数组。 | 将字符串数组作为 In 参数传递。 |
包含整数的结构数组。 | 将包含整数的结构数组作为 In 参数传递。 |
包含字符串的结构数组。 | 传递只包含将整数作为 In/Out 参数的结构的数组。可以更改数组的成员。 |
其他封送处理示例
诸如垃圾回收和线程处理等因素可能影响 interop 封送拆收器的行为。此外,编程和类型模型方面的微小差异也可能使向非托管库传递数据的操作出现混乱。本节提供一组解决其中某些差异的示例。
下表列出各种类型的项的封送处理选项,描述它们的用法并提供到相应示例的链接。
项的类型 | 说明 | 示例 |
---|---|---|
函数指针 | 将委托传递给需要函数指针的非托管函数。 | 回调 |
HandleRef | 使用 HandleRef 结构防止垃圾回收。 | HandleRef |
LPARAM | 使用 GCHandle 结构将托管对象传递给需要 LPARAM 类型的非托管函数。 | GCHandle |
单线程单元 (STA)/多线程单元 (MTA) | 在非托管函数调用 CoInitialize 时更改默认单元设置。 | ActiveDir |
void* | 调用将 void* 作为参数的函数。 | Void |