1. 程式人生 > >C# API讓指定視窗最大化、最小化

C# API讓指定視窗最大化、最小化

在C#中呼叫windows API函式的幾個實現 
 
 
 對於windows 系統API函式的呼叫在程式設計中有時是必不可少的,各種程式語言都規範了呼叫的方法和介面,在C#語言中的呼叫方法如下(以下程式設計環境為Visual Studio .NET):

1、  在工程專案中新增一個類新項,開啟這個類檔案,在檔案頭部加入對以下名稱空間的引用:

     using System.Runtime.InteropServices; 

     在類定義主體中,以靜態呼叫的方式加入對API的引用,本文以下的API呼叫為例:

        /// <summary>

              /// 開啟和關閉CD托盤.

              /// </summary>

              [DllImport("winmm.dll" , EntryPoint="mciSendString", CharSet=CharSet.Auto)]

              public  static extern int  mciSendString (string  lpstrCommand,string  lpstrReturnstring ,int  uReturnLength,int  hwndCallback);

              /// <summary>

              /// 顯示和隱藏滑鼠指標.

              /// </summary>

              [DllImport("user32.dll", EntryPoint="ShowCursor", CharSet=CharSet.Auto)]

              public  static extern int  ShowCursor(int  bShow);

  /// <summary>

              /// 清空回收站.

              /// </summary>

              [DllImport("shell32.dll", EntryPoint="SHEmptyRecycleBin", CharSet=CharSet.Auto)]

              public static extern long SHEmptyRecycleBin(IntPtr  hwnd, string pszRootPath, long dwFlags);

        /// <summary>

              /// 開啟瀏覽器

              /// </summary>

              [DllImport("shell32.dll", EntryPoint="ShellExecute", CharSet=CharSet.Auto)]

              public  static extern int  ShellExecute(IntPtr hwnd,string  lpOperation,string  lpFile,string  lpParameters,string  lpDirectory,int  nShowCmd);

        /// <summary>

              /// 最大化視窗,最小化視窗,正常大小視窗;

              /// </summary>

              [DllImport("user32.dll", EntryPoint="ShowWindow", CharSet=CharSet.Auto)]


public  static extern int  ShowWindow(IntPtr hwnd,int  nCmdShow);

2、  有了上面的檔案後,就可以在自己的窗體物件的事件處理中呼叫以上的API,方法如下:

     以下strReturn是string型別的公有變數,ApiCalls指代第一步建立的類名。

開啟CD托盤:

long lngReturn = ApiCalls.mciSendString("set CDAudio door open", strReturn, 127, 0);

     關閉CD托盤:

long lngReturn = ApiCalls.mciSendString("set CDAudio door closed", strReturn, 127, 0);

在應用程式窗體中顯示滑鼠指標:

ApiCalls.ShowCursor(1);

在應用程式窗體中隱藏滑鼠指標:

ApiCalls.ShowCursor(0);

清空回收站:

ApiCalls.SHEmptyRecycleBin(Form.ActiveForm.Handle,"",0x00000000);

開啟瀏覽器視窗,textBox1.Text中表示要訪問的URL地址:

Long lngReturn= ApiCalls.ShellExecute(Form.ActiveForm.Handle,"Open",textBox1.Text,"","",1);

最大化視窗:

ApiCalls.ShowWindow(Form.ActiveForm.Handle,3); 

最小化視窗:

ApiCalls.ShowWindow(Form.ActiveForm.Handle,2);

恢復正常大小視窗:

ApiCalls.ShowWindow(Form.ActiveForm.Handle,1);
開啟瀏覽器視窗,textBox1.Text中表示要訪問的URL地址:

Long lngReturn= ApiCalls.ShellExecute(Form.ActiveForm.Handle,"Open",textBox1.Text,"","",1);

最大化視窗:

ApiCalls.ShowWindow(Form.ActiveForm.Handle,3); 

最小化視窗:

ApiCalls.ShowWindow(Form.ActiveForm.Handle,2);

恢復正常大小視窗:

ApiCalls.ShowWindow(Form.ActiveForm.Handle,1);


 
在.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 <OSVERSIONINFOA>

*********************************************/

//.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 );