[转贴]关于在c#里面调用win api的方法.(我看过得最详细的帖子)

    在.Net Framework SDK文档中,关于调用Windows API的指示比较零散,并且其中稍全面一点的是针对Visual Basic .net讲述的。本文将C#中调用API的要点汇集如下,希望给未在C#中使用过API的朋友一点帮助。另外如果安装了Visual Studio .net的话,在C:/Program Files/Microsoft Visual Studio .NET/FrameworkSDK/Samples/Technologies/Interop/PlatformInvoke/WinAPIs/CS目录下有大量的调用API的例子。

一、调用格式
 

using  System.Runtime.InteropServices;  // 引用此名称空间,简化后面的代码
 
  // 使用DllImportAttribute特性来引入api函数,注意声明的是空方法,即方法体为空。
 [DllImport( " user32.dll " )]
  public   static   extern  ReturnType FunctionName(type arg1,type arg2,);
  // 调用时与调用其他方法并无区别 

 


 可以使用字段进一步说明特性,用逗号隔开,如:
 

[ DllImport(  " kernel32 " , EntryPoint = " GetVersionEx "  )]


 DllImportAttribute特性的公共字段如下:
 
1、CallingConvention 指示向非托管实现传递方法参数时所用的 CallingConvention 值。 
  CallingConvention.Cdecl : 调用方清理堆栈。它使您能够调用具有 varargs 的函数。
  CallingConvention.StdCall : 被调用方清理堆栈。它是从托管代码调用非托管函数的默认约定。
 
2、CharSet 控制调用函数的名称版本及指示如何向方法封送 String 参数。
      此字段被设置为 CharSet 值之一。如果 CharSet 字段设置为 Unicode,则所有字符串参数在传递到非托管实现之前都转换成 Unicode 字符。这还导致向 DLL EntryPoint 的名称中追加字母“W”。如果此字段设置为 Ansi,则字符串将转换成 ANSI 字符串,同时向 DLL EntryPoint 的名称中追加字母“A”。大多数 Win32 API 使用这种追加“W”或“A”的约定。如果 CharSet 设置为 Auto,则这种转换就是与平台有关的(在 Windows NT 上为 Unicode,在 Windows 98 上为 Ansi)。CharSet 的默认值为 Ansi。CharSet 字段也用于确定将从指定的 DLL 导入哪个版本的函数。CharSet.Ansi 和 CharSet.Unicode 的名称匹配规则大不相同。对于 Ansi 来说,如果将 EntryPoint 设置为“MyMethod”且它存在的话,则返回“MyMethod”。如果 DLL 中没有“MyMethod”,但存在“MyMethodA”,则返回“MyMethodA”。对于 Unicode 来说则正好相反。如果将 EntryPoint 设置为“MyMethod”且它存在的话,则返回“MyMethodW”。如果 DLL 中不存在“MyMethodW”,但存在“MyMethod”,则返回“MyMethod”。如果使用的是 Auto,则匹配规则与平台有关(在 Windows NT 上为 Unicode,在 Windows 98 上为 Ansi)。如果 ExactSpelling 设置为 true,则只有当 DLL 中存在“MyMethod”时才返回“MyMethod”。


3、EntryPoint 指示要调用的 DLL 入口点的名称或序号。 
      如果你的方法名不想与api函数同名的话,一定要指定此参数,例如:
 

[DllImport( " user32.dll " ,CharSet = " CharSet.Auto " ,EntryPoint = " MessageBox " )]
  public   static   extern   int  MsgBox(IntPtr hWnd, string  txt, string  caption,  int  type);

 

 4、ExactSpelling 指示是否应修改非托管 DLL 中的入口点的名称,以与 CharSet 字段中指定的 CharSet 值相对应。如果为 true,则当 DllImportAttribute.CharSet 字段设置为 CharSet 的 Ansi 值时,向方法名称中追加字母 A,当 DllImportAttribute.CharSet 字段设置为 CharSet 的 Unicode 值时,向方法的名称中追加字母 W。此字段的默认值是 false。 
 
5、PreserveSig 指示托管方法签名不应转换成返回 HRESULT、并且可能有一个对应于返回值的附加 [out, retval] 参数的非托管签名。 
 
6、SetLastError 指示被调用方在从属性化方法返回之前将调用 Win32 API SetLastError。 true 指示调用方将调用 SetLastError,默认为 false。运行时封送拆收器将调用 GetLastError 并缓存返回的值,以防其被其他 API 调用重写。用户可通过调用 GetLastWin32Error 来检索错误代码。


二、参数类型:
 
1、数值型直接用对应的就可。(DWORD -> int , WORD -> Int16)
 2、API中字符串指针类型 -> .net中string
 3、API中句柄 (dWord)  -> .net中IntPtr
 4、API中结构   -> .net中结构或者类。注意这种情况下,要先用StructLayout特性限定声明结构或类
  公共语言运行库利用StructLayoutAttribute控制类或结构的数据字段在托管内存中的物理布局,即类或结构需要按某种方式排列。如果要将类传递给需要指定布局的非托管代码,则显式控制类布局是重要的。它的构造函数中用LayoutKind值初始化 StructLayoutAttribute 类的新实例。 LayoutKind.Sequential 用于强制将成员按其出现的顺序进行顺序布局。
 
    LayoutKind.Explicit 用于控制每个数据成员的精确位置。利用 Explicit, 每个成员必须使用 FieldOffsetAttribute 指示此字段在类型中的位置。如:
 

[StructLayout(LayoutKind.Explicit, Size = 16 , CharSet = CharSet.Ansi)]
  public   class  MySystemTime 
  {
    [FieldOffset(0)]public ushort wYear; 
    [FieldOffset(2)]public ushort wMonth;
    [FieldOffset(4)]public ushort wDayOfWeek; 
    [FieldOffset(6)]public ushort wDay; 
    [FieldOffset(8)]public ushort wHour; 
    [FieldOffset(10)]public ushort wMinute; 
    [FieldOffset(12)]public ushort wSecond; 
    [FieldOffset(14)]public ushort wMilliseconds; 
 }


 下面是针对API中OSVERSIONINFO结构,在.net中定义对应类或结构的例子:

/**/ /**********************************************
* API中定义原结构声明
* OSVERSIONINFOA STRUCT
*  dwOSVersionInfoSize   DWORD      ?
*  dwMajorVersion        DWORD      ?
*  dwMinorVersion        DWORD      ?
*  dwBuildNumber         DWORD      ?
*  dwPlatformId          DWORD      ?
*  szCSDVersion          BYTE 128 dup (?)
* OSVERSIONINFOA ENDS
*
* OSVERSIONINFO  equ  
*********************************************/


// .net中声明为类
[ StructLayout( LayoutKind.Sequential )]   
public   class  OSVersionInfo 
{   
    public int OSVersionInfoSize;
    public int majorVersion; 
    public int minorVersion;
    public int buildNumber;
    public int platformId;

    [ MarshalAs( UnmanagedType.ByValTStr, SizeConst=128 )]    
    public String versionString;
}

// 或者
// .net中声明为结构
[ StructLayout( LayoutKind.Sequential )]  
public   struct  OSVersionInfo2 
{
    public int OSVersionInfoSize;
    public int majorVersion; 
    public int minorVersion;
    public int buildNumber;
    public int platformId;

    [ MarshalAs( UnmanagedType.ByValTStr, SizeConst=128 )]    
    public String versionString;
}

 

 此例中用到MashalAs特性,它用于描述字段、方法或参数的封送处理格式。用它作为参数前缀并指定目标需要的数据类型。例如,以下代码将两个参数作为数据类型长指针封送给 Windows API 函数的字符串 (LPStr): 
 

   [MarshalAs(UnmanagedType.LPStr)]
 String existingfile;
   [MarshalAs(UnmanagedType.LPStr)]
 String newfile;


注意结构作为参数时候,一般前面要加上ref修饰符,否则会出现错误:对象的引用没有指定对象的实例。

 [ DllImport(  " kernel32 " , EntryPoint = " GetVersionEx "  )] 
  public   static   extern   bool  GetVersionEx2(  ref  OSVersionInfo2 osvi ); 

 

三、如何保证使用托管对象的平台调用成功?

    如果在调用平台 invoke 后的任何位置都未引用托管对象,则垃圾回收器可能将完成该托管对象。这将释放资源并使句柄无效,从而导致平台invoke 调用失败。用 HandleRef 包装句柄可保证在平台 invoke 调用完成前,不对托管对象进行垃圾回收。
 
   例如下面:
 

       FileStream fs  =   new  FileStream(  " a.txt " , FileMode.Open );
        StringBuilder buffer  =   new  StringBuilder(  5  );
         int  read  =   0 ;
        ReadFile(fs.Handle, buffer,  5 ,  out  read,  0  );  // 调用Win API中的ReadFile函数


 由于fs是托管对象,所以有可能在平台调用还未完成时候被垃圾回收站回收。将文件流的句柄用HandleRef包装后,就能避免被垃圾站回收:
 

[ DllImport(  " Kernel32.dll "  )]
  public   static   extern   bool  ReadFile( 
  HandleRef hndRef, 
  StringBuilder buffer, 
   int  numberOfBytesToRead, 
   out   int  numberOfBytesRead, 
   ref  Overlapped flag );
 
 
        FileStream fs  =   new  FileStream(  " HandleRef.txt " , FileMode.Open );
        HandleRef hr  =   new  HandleRef( fs, fs.Handle );
        StringBuilder buffer  =   new  StringBuilder(  5  );
         int  read  =   0 ;
         //  platform invoke will hold reference to HandleRef until call ends
        ReadFile( hr, buffer,  5 ,  out  read,  0  );

 

 


     Api函数是构筑Windws应用程序的基石,每一种Windows应用程序开发工具,它提供的底层函数都间接或直接地调用了Windows API函数,同时为了实现功能扩展,一般也都提供了调用WindowsAPI函数的接口, 也就是说具备调用动态连接库的能力。Visual C#和其它开发工具一样也能够调用动态链接库的API函数。.NET框架本身提供了这样一种服务,允许受管辖的代码调用动态链接库中实现的非受管辖函数,包括操作系统提供的Windows API函数。它能够定位和调用输出函数,根据需要,组织其各个参数(整型、字符串类型、数组、和结构等等)跨越互操作边界。


下面以C#为例简单介绍调用API的基本过程:

动态链接库函数的声明

  动态链接库函数使用前必须声明,相对于VB,C#函数声明显得更加罗嗦,前者通过 Api Viewer粘贴以后,可以直接使用,而后者则需要对参数作些额外的变化工作。


 动态链接库函数声明部分一般由下列两部分组成,一是函数名或索引号,二是动态链接库的文件名。

  譬如,你想调用User32.DLL中的MessageBox函数,我们必须指明函数的名字MessageBoxA或MessageBoxW,以及库名字User32.dll,我们知道Win32 API对每一个涉及字符串和字符的函数一般都存在两个版本,单字节字符的ANSI版本和双字节字符的UNICODE版本。


 下面是一个调用API函数的例子:

[DllImport( " KERNEL32.DLL " , EntryPoint = " MoveFileW " , SetLastError = true , 
CharSet = CharSet.Unicode, ExactSpelling = true , 
CallingConvention = CallingConvention.StdCall)] 
public   static   extern   bool  MoveFile(String src, String dst); 


 其中入口点EntryPoint标识函数在动态链接库的入口位置,在一个受管辖的工程中,目标函数的原始名字和序号入口点不仅标识一个跨越互操作界限的函数。而且,你还可以把这个入口点映射为一个不同的名字,也就是对函数进行重命名。重命名可以给调用函数带来种种便利,通过重命名,一方面我们不用为函数的大小写伤透脑筋,同时它也可以保证与已有的命名规则保持一致,允许带有不同参数类型的函数共存,更重要的是它简化了对ANSI和Unicode版本的调用。CharSet用于标识函数调用所采用的是Unicode或是ANSI版本,ExactSpelling=false将告诉编译器,让编译器决定使用Unicode或者是Ansi版本。其它的参数请参考MSDN在线帮助.


 在C#中,你可以在EntryPoint域通过名字和序号声明一个动态链接库函数,如果在方法定义中使用的函数名与DLL入口点相同,你不需要在EntryPoint域显示声明函数。否则,你必须使用下列属性格式指示一个名字和序号。

 

[DllImport( " dllname " , EntryPoint = " Functionname " )] 
[DllImport( " dllname " , EntryPoint = " #123 " )] 


值得注意的是,你必须在数字序号前加“#”
下面是一个用MsgBox替换MessageBox名字的例子:

[C#] 
using  System.Runtime.InteropServices; 

public   class  Win32 
[DllImport("user32.dll", EntryPoint="MessageBox")] 
public static extern int MsgBox(int hWnd, String text, String caption, uint type); 
}
 


    许多受管辖的动态链接库函数期望你能够传递一个复杂的参数类型给函数,譬如一个用户定义的结构类型成员或者受管辖代码定义的一个类成员,这时你必须提供额外的信息格式化这个类型,以保持参数原有的布局和对齐。


    C#提供了一个StructLayoutAttribute类,通过它你可以定义自己的格式化类型,在受管辖代码中,格式化类型是一个用

    StructLayoutAttribute说明的结构或类成员,通过它能够保证其内部成员预期的布局信息。布局的选项共有三种:


布局选项

    描述

LayoutKind.Automatic 


为了提高效率允许运行态对类型成员重新排序。

注意:永远不要使用这个选项来调用不受管辖的动态链接库函数。

LayoutKind.Explicit 


对每个域按照FieldOffset属性对类型成员排序

LayoutKind.Sequential 


对出现在受管辖类型定义地方的不受管辖内存中的类型成员进行排序。

传递结构成员

下面的例子说明如何在受管辖代码中定义一个点和矩形类型,并作为一个参数传递给User32.dll库中的PtInRect函数,

函数的不受管辖原型声明如下:

BOOL PtInRect( const  RECT  * lprc, POINT pt); 


注意你必须通过引用传递Rect结构参数,因为函数需要一个Rect的结构指针。
 

[C#] 
using  System.Runtime.InteropServices; 

[StructLayout(LayoutKind.Sequential)] 
public   struct  Point 
public int x; 
public int y; 
}
 

[StructLayout(LayoutKind.Explicit] 
public   struct  Rect 
[FieldOffset(0)] public int left; 
[FieldOffset(4)] public int top; 
[FieldOffset(8)] public int right; 
[FieldOffset(12)] public int bottom; 
}
 

class  Win32API 
[DllImport("User32.dll")] 
public static extern Bool PtInRect(ref Rect r, Point p); 
}
 


类似你可以调用GetSystemInfo函数获得系统信息:

# using  System.Runtime.InteropServices; 
[StructLayout(LayoutKind.Sequential)] 
public   struct  SYSTEM_INFO 
public uint dwOemId; 
public uint dwPageSize; 
public uint lpMinimumApplicationAddress; 
public uint lpMaximumApplicationAddress; 
public uint dwActiveProcessorMask; 
public uint dwNumberOfProcessors; 
public uint dwProcessorType; 
public uint dwAllocationGranularity; 
public uint dwProcessorLevel; 
public uint dwProcessorRevision; 
}
 


[DllImport( " kernel32 " )] 
static   extern   void  GetSystemInfo( ref  SYSTEM_INFO pSI); 

SYSTEM_INFO pSI  =   new  SYSTEM_INFO(); 
GetSystemInfo( ref  pSI); 


类成员的传递

同样只要类具有一个固定的类成员布局,你也可以传递一个类成员给一个不受管辖的动态链接库函数,下面的例子主要说明如何传递一个sequential顺序定义的MySystemTime类给User32.dll的GetSystemTime函数, 函数用C/C++调用规范如下:

 

void  GetSystemTime(SYSTEMTIME *  SystemTime); 


不像传值类型,类总是通过引用传递参数.

[C#] 
[StructLayout(LayoutKind.Sequential)] 
public   class  MySystemTime 
public ushort wYear; 
public ushort wMonth; 
public ushort wDayOfWeek; 
public ushort wDay; 
public ushort wHour; 
public ushort wMinute; 
public ushort wSecond; 
public ushort wMilliseconds; 
}
 
class  Win32API 
[DllImport("User32.dll")] 
public static extern void GetSystemTime(MySystemTime st); 
}
 


回调函数的传递:

从受管辖的代码中调用大多数动态链接库函数,你只需创建一个受管辖的函数定义,然后调用它即可,这个过程非常直接。
如果一个动态链接库函数需要一个函数指针作为参数,你还需要做以下几步:

首先,你必须参考有关这个函数的文档,确定这个函数是否需要一个回调;第二,你必须在受管辖代码中创建一个回调函数;最后,你可以把指向这个函数的指针作为一个参数创递给DLL函数,.


回调函数及其实现:

回调函数经常用在任务需要重复执行的场合,譬如用于枚举函数,譬如Win32 API 中的EnumFontFamilies(字体枚举), EnumPrinters(打印机), EnumWindows (窗口枚举)函数. 下面以窗口枚举为例,谈谈如何通过调用EnumWindow 函数遍历系统中存在的所有窗口

分下面几个步骤:

1. 在实现调用前先参考函数的声明

BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARMAM IParam)


显然这个函数需要一个回调函数地址作为参数.

2. 创建一个受管辖的回调函数,这个例子声明为代表类型(delegate),也就是我们所说的回调,它带有两个参数hwnd和lparam,第一个参数是一个窗口句柄,第二个参数由应用程序定义,两个参数均为整形。


  当这个回调函数返回一个非零值时,标示执行成功,零则暗示失败,这个例子总是返回True值,以便持续枚举。

3. 最后创建以代表对象(delegate),并把它作为一个参数传递给EnumWindows 函数,平台会自动地 把代表转化成函数能够识别的回调格式。

 

[C#] 
using  System; 
using  System.Runtime.InteropServices; 

public   delegate   bool  CallBack( int  hwnd,  int  lParam); 

public   class  EnumReportApp 

[DllImport("user32")] 
public static extern int EnumWindows(CallBack x, int y); 

public static void Main() 

CallBack myCallBack = new CallBack(EnumReportApp.Report); 
EnumWindows(myCallBack, 0); 
}
 

public static bool Report(int hwnd, int lParam) 
Console.Write("窗口句柄为"); 
Console.WriteLine(hwnd); 
return true; 
}
 
}
 


指针类型参数传递:

  在Windows API函数调用时,大部分函数采用指针传递参数,对一个结构变量指针,我们除了使用上面的类和结构方法传递参数之外,我们有时还可以采用数组传递参数。


 下面这个函数通过调用GetUserName获得用户名

BOOL GetUserName( 
LPTSTR lpBuffer,  //  用户名缓冲区 
LPDWORD nSize  //  存放缓冲区大小的地址指针 
); 
  
[DllImport( " Advapi32.dll " , 
EntryPoint = " GetComputerName " , 
ExactSpelling = false , 
SetLastError = true )] 
static   extern   bool  GetComputerName ( 
[MarshalAs(UnmanagedType.LPArray)]  byte [] lpBuffer, 
[MarshalAs(UnmanagedType.LPArray)] Int32[] nSize ); 

 
     这个函数接受两个参数,char * 和int *,因为你必须分配一个字符串缓冲区以接受字符串指针,你可以使用String类代替这个参数类型,当然你还可以声明一个字节数组传递ANSI字符串,同样你也可以声明一个只有一个元素的长整型数组,使用数组名作为第二个参数。上面的函数可以调用如下:

 

byte [] str = new   byte [ 20 ]; 
Int32[] len = new  Int32[ 1 ]; 
len[ 0 ] = 20 ; 
GetComputerName (str,len); 
MessageBox.Show(System.Text.Encoding.ASCII.GetString(str)); 


  最后需要提醒的是,每一种方法使用前必须在文件头加上:

using  System.Runtime.InteropServices;

 

本主题说明 DllImport 属性的常见用法。第一节讨论使用 DllImport 从托管应用程序调用本机代码的优点。第二节集中讨论封送处理和 DllImport 属性的各个方面。

从托管应用程序调用非托管代码

当在托管应用程序中重用现有的非托管代码时,DllImport 属性非常有用。例如,托管应用程序可能需要调用非托管 WIN32 API。

下面的代码示例说明此通用方案,此示例将调用 MessageBox(位于 User32.lib 中):

 

# using   < mscorlib.dll >
using   namespace  System::Runtime::InteropServices; 
//  for DllImportAttribute

namespace  SysWin32
{
   [DllImport("user32.dll", EntryPoint = "MessageBox", CharSet = Unicode)]
   int MessageBox(void* hWnd, wchar_t* lpText, wchar_t* lpCaption, 
                  unsigned int uType);
}


int  main( )
{
   SysWin32::MessageBox( 0, L"Hello world!", L"Greetings", 0 );
}

主要注意包含 DllImport 的代码行。此代码行根据参数值通知编译器,使之声明位于 User32.dll 中的函数并将签名中出现的所有字符串(如参数或返回值)视为 Unicode 字符串。如果缺少 EntryPoint 参数,则默认值为函数名。另外,由于 CharSet 参数指定 Unicode,因此公共语言运行库将首先查找称为 MessageBoxW(有 W 是因为 Unicode 规范)的函数。如果运行库未找到此函数,它将根据调用约定查找 MessageBox 以及相应的修饰名。受支持的调用约定只有 __cdecl__stdcall

当调用用户定义的 DLL 中所包含的函数时,有必要将 extern "C" 添加在 DLL 函数声明之前,如下所示:

 

//  The function declaration in SampleDLL.h file
extern   " C "  SAMPLEDLL_API  int  fnSampleDLL( void );

有关受支持的其他参数值的更多信息,请参见 DllImport

将非结构化参数由托管封送处理为非托管

除使用上述方法外,还可以使用另一种方法将托管参数(来自托管应用程序)封送处理为非托管参数(在非托管 DLL 中)。

以下代码示例说明封送处理技术:

 

# using   < mscorlib.dll >
using   namespace  System;  //  To bring System::String in
using   namespace  System::Runtime::InteropServices; 
//  for DllImportAttribute
namespace  SysWin32
{
   [DllImport("user32.dll", EntryPoint = "MessageBox", CharSet = Unicode)]
   Int32 MessageBox( Int32 hWnd, String* lpText, String* lpCaption, 
                     UInt32 uType );
}


int  main( )
{
   SysWin32::MessageBox(0, S"Hello world!", S"Greetings", 0);
}

完成实际的调用后,由于 CharSet 参数值的作用,所有参数字符串都自动转换为 wchar_t*。同样,所有 Int32 参数类型都转换为非托管 int,而 UInt32 参数类型转换为非托管 unsigned int

下表提供关于转换非托管和托管上下文的指导:

非托管代码C++ 的托管扩展
int Int32
unsigned int UInt32
short Int16
char* 用于 [in] 参数的 String* (CharSet = Ansi),用于 [out] 参数或返回值的 Text::StringBuilder*
wchar_t* 用于 [in] 参数的 String* (CharSet = Unicode),用于 [out] 参数或返回值的 Text::StringBuilder*
函数指针(回调)
限制:函数指针必须具有 __stdcall 调用约定,因为这是 DllImport 支持的唯一类型。
委托类型
数组(如 wchar_t*[])
限制:CharSet 参数仅应用于函数参数的根类型。因此,无论 CharSet 的值是什么,String* __gc[] 都将被封送处理为 wchar_t* []
相应类型的托管数组(如 String*__gc[]

将结构化类型由非托管封送处理为托管

除简单类型外,运行库还提供了一种机制,可以将简单结构由托管上下文封送处理为非托管上下文。简单结构不包含任何内部数据成员指针、结构化类型的成员或其他元素。

例如,本主题显示如何调用本机 DLL 中具有以下签名的函数:

 

#include  < stdio.h >
struct  S
{
   char* str;
   int n;
}
;

int  __cdecl func(  struct  S *  p )
{
   printf( "%s/n", p->str );
   return p->n;
}

若要创建此函数的托管包装,请将 StructLayout 属性应用到调用类。此属性确定封送处理结构时结构的组织方式。若要确保以传统的 C 格式组织结构,请指定顺序布局 (LayoutKind::Sequential)。结果代码如下:

 

# using   < mscorlib.dll >
using   namespace  System;
using   namespace  System::Runtime::InteropServices;

//  CharSet = Ansi(Unicode) means that everything that is a string 
//  in this structure should be marshaled as Ansi(Unicode) 
//  strings
[StructLayout( LayoutKind::Sequential, CharSet = Ansi )]
__gc  class  MS  //  To be compatible with the type in the native 
//  code, this structure should have the members laid out in
//  the same order as those in the native struct
{
public:
   String* m_str;
   Int32 m_n;
}
;

[DllImport( " some.dll " )]
Int32 func( MS *  ptr );
int  main( )
{
   MS* p = new MS;
   p->m_str = S"Hello native!";
   p->m_n = 7;
   Console::WriteLine(func(p)); // Should print 7
}

也可以在托管应用程序中使用 __nogc 关键字,以确保不发生封送处理:

 

#include  < stdlib.h >
#include  < string .h >
# using   < mscorlib.dll >
using   namespace  System;
using   namespace  System::Runtime::InteropServices;
__nogc  class  UMS
{
public:
   char* m_str;
   int m_n;
}
;
[DllImport( " some.dll " )]
Int32 func( UMS *  ptr );
int  main( )
{
   UMS* p = new UMS;
   p->m_str = strdup( "Hello native!" );
   p->m_n = 7;
   Console::WriteLine(func(p)); // Should print 7
   free( p->m_str );
   delete p;
}

第二个方案是:

 

#include  < stdio.h >
struct  S
{
   wchar_t* str;
   int n;
}
;
int  __cdecl func(  struct  S p )
{
   printf( "%S/n", p.str );
   return p.n;
}

注意参数是通过值传递的。若要在托管应用程序中包装此调用,请使用值而不是 __gc 类型。结果代码如下:

# using   < mscorlib.dll >
using   namespace  System;
using   namespace  System::Runtime::InteropServices;
[StructLayout( LayoutKind::Sequential, CharSet = Unicode )]
__value  class  VS
{
public:
   String* m_str;
   Int32 m_n;
}
;
[DllImport(  " some.dll "  )]
Int32 func( VS ptr );
int  main( )
{
   VS v;
   v.m_str = S"Hello native!";
   v.m_n = 7;
   Console::WriteLine(func(v)); // should print 7 also
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值