C# 調用API -炒冷飯系列


陽歷今年的最后一天了,又該干點什么。 小鬼職業病又來了"神情恍惚,目光呆滯的敲着鍵盤,陶醉的望着跳動的光標,嘴角上揚。"

 最近項目中用到C#調用第三方提供的動態鏈接庫 就是DLL 中API。

這也是VB6.0的時代就有如何調用Window API了,點選API Viewer 查詢相關API點選兩下就可以生成方法的簽名 (聲明函數)  用方法簽名 + 參數就可 直接調用了。

C#剛開始沒有提供相關類是API Viewer; 在自己動手,豐衣足食的年代,就手寫寫吧。這也無可厚非的做法,但是手寫要知道API的方法簽名才行。還有許多的類型替換和構造;寬字符怎樣正確的調用API得到我們想要的的結果。

用C的和用ASM的同仁調用API就是像呼吸一樣簡單。小鬼就沒法想象了。

  C# 調用API是件麻煩的事 

int WINAPI MessageBox(
__in_opt HWND hWnd,
__in_opt LPCTSTR lpText,
__in_opt LPCTSTR lpCaption,
__in UINT uType
);

具體介紹這個API

這是聲明API的全方法簽名 但run不了。為了看看繁瑣 特性描述而已

 

  好在可以缺省部分參數是個不錯特性 

MessageBox
        [DllImport("user32.dll")]
public static extern IntPtr MessageBox(
IntPtr hWnd,
String lpText,
String lpCaption,
uint uType);

static void Main(string[] args)
{
MessageBox(IntPtr.Zero, "Hello world", "Hello world!", 1u);
}


好的這就是一個最簡單API調用了。接下來就可以一步步來了

 

 但遇到更復雜API 就有點迷茫了

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

  具體介紹這個API

 RECT 是什么?

 Point 是什么?

 ... 

這些問題 以前讓小鬼迷茫; 將來讓小鬼迷離;現在讓小鬼着迷。 對照了API和查閱一些文章 (有些作者不詳 知道告訴我一聲 我會加鏈接的)

    [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;
}

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

這樣的API簽名就可以調用了。

 C# API 有什么講究 

調用API還是Winform項目用得多。  

 Api 函數是構築Windws 應用程序的基石,每一種Windows 應用程序開發工具,它提供的底層函數都間接或直接地調用了Windows API 函數,同時為了實現功能擴展,一般也都提供了調用WindowsAPI 函數的接口, 也就是說具備調用動態連接庫的能力。Visual C#和其它開發工具一樣也能夠調用動態鏈接庫的API 函數。.NET 框架本身提供了這樣一種服務,允許受管轄的代碼調用動態鏈接庫中實現的非受管轄函數,包括操作系統提供的Windows API 函數。它能夠定位和調用輸出函數,根據需要,組織其各個參數(整型、字符串類型、數組、和結構等等)跨越互操作邊界。

   C# 調用API基本步驟

 C#對動態鏈接庫函數聲明部分一般由下列兩部分組成,一是函數名或索引號,二是動態鏈接庫的文件名。

譬如,你想調用User32.DLL 中的MessageBox 函數,我們必須指明函數的名字MessageBoxA 或MessageBoxW,以及庫名字User32.dll,我們知道Win32 API 對每一個涉及字符串和字符的函數一般都存在兩個版本,單字節字符的ANSI 版本和雙字節字符的UNICODE 版本。

 

[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 名字的例子:
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 的結構指針。

        [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;
}
[DllImport("User32.dll")]
public static extern bool PtInRect(ref Rect r, Point p);

 

  類似你可以調用GetSystemInfo 函數獲得系統信息:

[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.dll")]
staticexternvoid GetSystemInfo(ref SYSTEM_INFO pSI);
ClientCode
SYSTEM_INFO pSI = new SYSTEM_INFO();
GetSystemInfo(ref pSI);

類成員的傳遞

   同樣只要類具有一個固定的類成員布局,你也可以傳遞一個類成員給一個不受管轄的動態鏈接庫函數,
下面的例子主要說明如何傳遞一個sequential 順序定義的MySystemTime 類給User32.dll 的GetSystem
Time 函數, 函數用C/C++調用規范如下:
void GetSystemTime(SYSTEMTIME* SystemTime);
不像傳值類型,類總是通過引用傳遞參數.

 

[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 函數,平台會自動地 把
代表轉化成函數能夠識別的回調格式。

 

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;

本來想給連接的的 但是好像找不到 這樣全的  我是在百度文庫沒封殺之前下載在硬盤上的 現在應該還有文檔。知道源地址的同仁 吱我一聲。

有更好的也吱一聲。 呵呵 ☺有想法的同仁也 評論幾句。

 

 

最后預祝大家 2010事事順心,吉祥萬福!:)


 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM