背景:
  在net framework环境下,我们在日常的开发中一般使用的都是使用其本身提供的类库或者基于其类库进行继承或扩展.
  windows api是windows操作系统的基础,它是一个给我们开发人员用来构建应用程序的功能强大的函数库,借助它我们可以实现windows操作系统下所有功能.
  
  net framework提供了一种在托管代码环境下调用动态连接库中的非托管代码服务,借助该服务,我们可以很方便地在net框架中调用windows api,以构建功能更为强大的应用.

解决办法:
 1.首先我们要引入如下命名空间:
   using System.Runtime.InteropServices;

   该命名空间包括一系列成员以支持COM互操作,其中包括一个最重要的属性即DllImport.该属性被用来修饰API中定义的方法,它提供了调用非托管的动态连接库中定义的函数所需要信息.

 2. 然后我们借助DllImport属性声明非托管动态连接库中函数
  DllImport属性声明常见格式示例:
  [DllImport("KERNEL32.DLL", EntryPoint="MoveFileW", SetLastError=true,
CharSet=CharSet.Unicode, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
  public static extern bool MoveFile(String src, String dst);
  说明:
  "KERNEL32.DLL": 指定后续要声明方法所在动态连接库文件名 
  EntryPoint: 用来指定动态连接库文件中对应的函数名,可省略,当省略时,其值与后续声明的方法同名
  CharSet : 用来指定调用函数的名称版本以及如何向api中定义的方法传送string类型参数
     我们知道,win32 api中的部分函数(主要是处理字符或字符串数据的函数)存在两个版本,即一个以
  单字节存储的ANSI文本和以双字节方式存储的Unicode版本.
   CharSet的值可被设置为Auto,ANSI或者Unicode:
    当被设置为Unicode时,则所有字符或字符串参数在传递到非托管方法前将被转化为两字节的Unicode字符,同时导致向DllImport属性中的EntryPoint值追加W字符;
    当被设置为ANSI时,则所有字符或字符串参数在传递前先被转换为Ansi字符,同时给EntryPoint值追加A字符;
    若被设置为Auto,则转换方式与当前程序所运行的平台有关,即win NT上为unicode,win 98为ansi
    注:charset设置的值同时也影响最终调用的函数,对于ansi,则先检索指定动态连接库中是否存在EntryPoint值指定的函数,若存在,则调用它;若不存在,但存在EntryPoint值+W为名字的函数,则调用加W字符的版本;
  而对于unicode,则有所不同,若指定动态连接库文件中存在EntryPoint值指定的函数时,则调用其unicode版本;若不存在其unicode版本,但存在entrypoint值指定的函数,则调用entrypoint值指定的版本函数;
  若为auto,则匹配规则与具体平台有关.
  ExactSpelling :表示是否应修改非托管 DLL 中的入口点的名称,以与 CharSet 字段中指定的 CharSet 值相对应。如果为 true,则当 DllImportAttribute.CharSet 字段设置为 CharSet 的 Ansi 值时,向方法名称中追加字母 A,当 DllImportAttribute.CharSet 字段设置为 CharSet 的 Unicode 值时,向方法的名称中追加字母 W。此字段的默认值是 false。
  EntryPoint 表示要调用的 DLL 入口点的名称或序号。 如果你的方法名不想与api函数同名的话,一定要指定此参数.它可以是一个具体的函数名或一个序号,如#123
erveSig 表示托管方法签名不应转换成返回 HRESULT、并且可能有一个对应于返回值的附加 [out, retval] 参数的非托管签名。
 SetLastError 表示被调用方在从属性化方法返回之前将调用 Win32 API SetLastError。 true 指示被调用方将调用 SetLastError,默认为 false。运行时封送拆收器将调用 GetLastError 并缓存返回的值,以防其被其他 API 调用重写。用户可通过调用 System.Runtime.InteropServices.Marshal.GetLastWin32Error() 来检索错误代码。
 CallingConvention 表示向非托管方法传递参数时所用的 CallingConvention 值。
  它可以被设置为:CallingConvention.Cdecl : 调用方清理堆栈,使得调用方能够调用具有 varargs 的函数。
              CallingConvention.StdCall : 被调用方清理堆栈,默认值

    附:
  对于传递给非托管api函数的参数类型处理:
  1.对于简单类型,直接使用C#中对应类型即可,如api中的short类型对应于system.int16;
      2.  对于复杂的参数类型,如结构类型或类类型,则需要格式化类型,以保持参数原有布局格式.

  C#提供了一个StructLayoutAttribute类,通过它你可以定义自己的格式化类型,布局的选项共有三种,分别为
  (1). LayoutKind.Automatic :
   为了提高效率允许运行时对类型成员重新排序。
   注意:永远不要使用这个选项来调用不受管辖的动态链接库函数。
  (2). LayoutKind.Explicit
   对每个域/成员按照FieldOffset属性对类型成员排序
  (3). 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);
  }

  
  传递类成员:
  我们也可以传递一类成员给动态链接库函数,下面的例子主要说明如何传递一个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("窗口句柄为" + hwnd);
  return true;
}
}

posted on 2008-11-06 10:21  晃晃悠悠  阅读(438)  评论(0)    收藏  举报