熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

用Visual C#調用Windows API函數

2022-06-13   來源: .NET編程 

  Api函數是構築Windws應用程序的基石每一種Windows應用程序開發工具它提供的底層函數都間接或直接地調用了Windows API函數同時為了實現功能擴展一般也都提供了調用WindowsAPI函數的接口 也就是說具備調用動態連接庫的能力Visual C#和其它開發工具一樣也能夠調用動態鏈接庫的API函數NET框架本身提供了這樣一種服務允許受管轄的代碼調用動態鏈接庫中實現的非受管轄函數包括操作系統提供的Windows API函數它能夠定位和調用輸出函數根據需要組織其各個參數(整型字符串類型數組和結構等等)跨越互操作邊界
  
  下面以C#為例簡單介紹調用API的基本過程
  
  動態鏈接庫函數的聲明
  動態鏈接庫函數使用前必須聲明相對於VBC#函數聲明顯得更加羅嗦前者通過 Api Viewer粘貼以後可以直接使用而後者則需要對參數作些額外的變化工作
  
  動態鏈接庫函數聲明部分一般由下列兩部分組成一是函數名或索引號二是動態鏈接庫的文件名
  
  譬如你想調用UserDLL中的MessageBox函數我們必須指明函數的名字MessageBoxA或MessageBoxW以及庫名字Userdll我們知道Win API對每一個涉及字符串和字符的函數一般都存在兩個版本單字節字符的ANSI版本和雙字節字符的UNICODE版本
  
  下面是一個調用API函數的例子
  [DllImport(KERNELDLL EntryPoint=MoveFileW SetLastError=true
  CharSet=CharSetUnicode ExactSpelling=true
  CallingConvention=CallingConventionStdCall)]
  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=#)]
  值得注意的是你必須在數字序號前加
  下面是一個用MsgBox替換MessageBox名字的例子
  [C#]
  using SystemRuntimeInteropServices;
  
  public class Win {
  [DllImport(userdll EntryPoint=MessageBox)]
  public static extern int MsgBox(int hWnd String text String caption uint type);
  }
  許多受管轄的動態鏈接庫函數期望你能夠傳遞一個復雜的參數類型給函數譬如一個用戶定義的結構類型成員或者受管轄代碼定義的一個類成員這時你必須提供額外的信息格式化這個類型以保持參數原有的布局和對齊
  
  C#提供了一個StructLayoutAttribute類通過它你可以定義自己的格式化類型在受管轄代碼中格式化類型是一個用StructLayoutAttribute說明的結構或類成員通過它能夠保證其內部成員預期的布局信息布局的選項共有三種
  
  布局選項
  描述
  LayoutKindAutomatic
  為了提高效率允許運行態對類型成員重新排序
  注意永遠不要使用這個選項來調用不受管轄的動態鏈接庫函數
  LayoutKindExplicit
  對每個域按照FieldOffset屬性對類型成員排序
  LayoutKindSequential
  對出現在受管轄類型定義地方的不受管轄內存中的類型成員進行排序
  
  傳遞結構成員
  下面的例子說明如何在受管轄代碼中定義一個點和矩形類型並作為一個參數傳遞給Userdll庫中的PtInRect函數
  函數的不受管轄原型聲明如下
  BOOL PtInRect(const RECT *lprc POINT pt);
  注意你必須通過引用傳遞Rect結構參數因為函數需要一個Rect的結構指針
  [C#]
  using SystemRuntimeInteropServices;
  
  [StructLayout(LayoutKindSequential)]
  public struct Point {
  public int x;
  public int y;
  }
  
  [StructLayout(LayoutKindExplicit]
  public struct Rect {
  [FieldOffset()] public int left;
  [FieldOffset()] public int top;
  [FieldOffset()] public int right;
  [FieldOffset()] public int bottom;
  }
  
  class WinAPI {
  [DllImport(Userdll)]
  public static extern Bool PtInRect(ref Rect r Point p);
  }
  類似你可以調用GetSystemInfo函數獲得系統信息
  ? using SystemRuntimeInteropServices;
  [StructLayout(LayoutKindSequential)]
  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(kernel)]
  static extern void GetSystemInfo(ref SYSTEM_INFO pSI);
  
  SYSTEM_INFO pSI = new SYSTEM_INFO();
  GetSystemInfo(ref pSI);
  
  類成員的傳遞
  同樣只要類具有一個固定的類成員布局你也可以傳遞一個類成員給一個不受管轄的動態鏈接庫函數下面的例子主要說明如何傳遞一個sequential順序定義的MySystemTime類給Userdll的GetSystemTime函數 函數用C/C++調用規范如下:
  
  void GetSystemTime(SYSTEMTIME* SystemTime);
  不像傳值類型類總是通過引用傳遞參數
  [C#]
  [StructLayout(LayoutKindSequential)]
  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 WinAPI {
  [DllImport(Userdll)]
  public static extern void GetSystemTime(MySystemTime st);
  }
  
  回調函數的傳遞:
  從受管轄的代碼中調用大多數動態鏈接庫函數你只需創建一個受管轄的函數定義然後調用它即可這個過程非常直接
  
  如果一個動態鏈接庫函數需要一個函數指針作為參數你還需要做以下幾步
  
  首先你必須參考有關這個函數的文檔確定這個函數是否需要一個回調第二你必須在受管轄代碼中創建一個回調函數最後你可以把指向這個函數的指針作為一個參數創遞給DLL函數
  
  回調函數及其實現:
  回調函數經常用在任務需要重復執行的場合譬如用於枚舉函數譬如Win API 中的EnumFontFamilies(字體枚舉) EnumPrinters(打印機) EnumWindows (窗口枚舉)函數 下面以窗口枚舉為例談談如何通過調用EnumWindow 函數遍歷系統中存在的所有窗口
  
  分下面幾個步驟:
   在實現調用前先參考函數的聲明
  BOOL EnumWindows(WNDENUMPROC lpEnumFunc LParmAM IParam)
  
  顯然這個函數需要一個回調函數地址作為參數
  
   創建一個受管轄的回調函數這個例子聲明為代表類型(delegate)也就是我們所說的回調它帶有兩個參數hwnd和lparam第一個參數是一個窗口句柄第二個參數由應用程序定義兩個參數均為整形
  
  當這個回調函數返回一個非零值時標示執行成功零則暗示失敗這個例子總是返回True值以便持續枚舉
  
   最後創建以代表對象(delegate)並把它作為一個參數傳遞給EnumWindows 函數平台會自動地 把代表轉化成函數能夠識別的回調格式
  
  [C#]
  using System;
  using SystemRuntimeInteropServices;
  
  public delegate bool CallBack(int hwnd int lParam);
  
  public class EnumReportApp {
  
  [DllImport(user)]
  public static extern int EnumWindows(CallBack x int y);
  
  public static void Main()
  {
  CallBack myCallBack = new CallBack(EnumReportAppReport);
  EnumWindows(myCallBack );
  }
  
  public static bool Report(int hwnd int lParam) {
  ConsoleWrite(窗口句柄為);
  ConsoleWriteLine(hwnd);
  return true; From:http://tw.wingwit.com/Article/program/net/201311/13429.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.