標籤:blog http 使用 strong os 資料
C#調用API向外部程式發送資料
最近有可能要做一個項目。在項目中有這麼一個功能,在A程式中調用B程式,同時在A程式中進行登陸後,要將A程式的登入名稱和密碼自動填滿到B程式的登陸對話方塊中,這樣B程式就不需要再輸入一次使用者名稱和密碼了,簡化操作人員的操作。剛好最近閑著沒事,就在怎麼想怎麼去實現。經過兩天的折騰,基本上完成了上述功能的實現。下面就把實現方法、過程與大家進行分享。
一、原理
要實現上述功能,需要調用Win API來實現。Win32 API即為Microsoft 32位平台的API(Application Programming Interface)。所有在Win32平台上啟動並執行應用程式都可以調用這些函數。
那麼在本程式的實現過程中,需要用到以下三個API函數(函數說明均從網上找的,方便大家查看),以及自己編寫的一個FindWindowByIndex函數。
1、static extern int SendMessage1(IntPtr hwnd, uint wMsg, int wParam, int lParam);
顧名思義,SendMessage函數的功能是“發送訊息”,即將一條訊息發送到指定對象(作業系統、視窗或控制項等)上,以產生特定的動作(如滾屏、修改對象外觀等)。
其中四個自變數的含義和說明如下:
hWnd:對象的控制代碼。希望將訊息傳送給哪個對象,就把該對象的控制代碼作為實參傳送。
wMsg:被發送的訊息。根據具體需求和不同的對象,將不同的訊息作為實參傳送,以產生預期的動作。
wParam、lParam:附加的訊息資訊。這兩個是可選的參數,用來提供關於wMsg訊息更多的資訊,不同的wMsg可能使用這兩個參數中的0、1或2個,如果不需要哪個附加參數,則將實參賦為NULL(在VB中賦為0)。
2、public static extern IntPtr FindWindow(string className, string windowName);
FindWindow函數返回與指定字元創相匹配的視窗類別名或視窗名的最頂層視窗的視窗控制代碼。這個函數不會尋找子視窗。
lpClassName:指向一個以null結尾的、用來指定類名的字串或一個可以確定類名字串的原子。如果這個參數是一個原子,那麼它必須是一個在調用此函數前已經通過GlobalAddAtom函數建立好的全域原子。這個原子(一個16bit的值),必須被放置在lpClassName的低位位元組中,lpClassName的高位位元組置零。
lpWindowName:指向一個以null結尾的、用來指定視窗名(即視窗標題)的字串。如果此參數為NULL,則匹配所有視窗名。
傳回值:如果函數執行成功,則傳回值是擁有指定視窗類別名或視窗名的視窗的控制代碼。如果函數執行失敗,則傳回值為 NULL 。可以通過調用GetLastError函數獲得更加詳細的錯誤資訊。
3、static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
在視窗列表中尋找與指定條件相符的第一個子視窗 。該函數獲得一個視窗的控制代碼,該視窗的類名和視窗名與給定的字串相匹配。這個函數尋找子視窗,從排在給定的子視窗後面的下一個子視窗開始。在尋找時不區分大小寫。
hwndParent:要尋找的子視窗所在的父視窗的控制代碼(如果設定了hwndParent,則表示從這個hwndParent指向的父視窗中搜尋子視窗)。如果hwndParent為 0 ,則函數以桌面視窗為父視窗,尋找桌面視窗的所有子視窗。Windows NT5.0 and later:如果hwndParent是HWND_MESSAGE,函數僅尋找所有訊息視窗。
hwndChildAfter :子視窗控制代碼。尋找從在Z序中的下一個子視窗開始。子視窗必須為hwndParent視窗的直接子視窗而非後代視窗。如果HwndChildAfter為NULL,尋找從hwndParent的第一個子視窗開始。如果hwndParent 和 hwndChildAfter同時為NULL,則函數尋找所有的頂層視窗及訊息視窗。
lpszClass:指向一個指定了類名的空結束字串,或一個標識類名字串的成員的指標。如果該參數為一個成員,則它必須為前次調用theGlobaIAddAtom函數產生的全域成員。該成員為16位,必須位於lpClassName的低16位,高位必須為0。
pszWindow:指向一個指定了視窗名(視窗標題)的空結束字串。如果該參數為 NULL,則為所有視窗全匹配。
傳回值:Long,找到的視窗的控制代碼。如未找到相符視窗,則返回零。會設定GetLastError如果函數成功,傳回值為具有指定類名和視窗名的視窗控制代碼。如果函數失敗,傳回值為NULL。
4、static IntPtr FindWindowByIndex(IntPtr hwndParent, int index)
該函數通過隱含的索引來尋找相應的控制項。
該函數原始碼如下:
static IntPtr FindWindowByIndex(IntPtr hwndParent, int index)
{
if (index == 0)
return hwndParent;
else
{
int ct = 0;
IntPtr result = IntPtr.Zero;
do
{
result = FindWindowEx(hwndParent, result, null, null);
if (result != IntPtr.Zero)
++ct;
} while (ct < index && result != IntPtr.Zero);
return result;
}
}
二、API調用方法(註:本段文字從網上摘錄)
1、使用相應的命名空間using System.Runtime.InteropServices;
2、使用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 : 被呼叫者清理堆棧。它是從Managed 程式碼調用非託管函數的預設約定。
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(IntPtrhWnd,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 來檢索錯誤碼。
三、程式實現過程
程式A:兩個文字框與一個啟動按鈕。兩個文字框用來輸入使用者名稱和密碼,啟動按鈕用來啟動程式B。
程式B:兩個文字框,用來接受程式A所發送過來的字串。
1、A程式碼清單如下:
using System.Runtime.InteropServices;
using System.Threading;
private staticSystem.Diagnostics.Process p;
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]
static extern int SendMessage1(IntPtrhwnd, uint wMsg, intwParam, int lParam);
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true, CallingConvention = CallingConvention.Winapi,CharSet = CharSet.Unicode)]
public static extern IntPtrFindWindow(string className, string windowName);
[DllImport("user32.dll", EntryPoint = "FindWindowEx", CharSet = CharSet.Auto)]
static extern IntPtr FindWindowEx(IntPtrhwndParent, IntPtr hwndChildAfter, string lpszClass, stringlpszWindow);
static IntPtrFindWindowByIndex(IntPtr hwndParent, int index)
{
if(index == 0)
returnhwndParent;
else
{
intct = 0;
IntPtrresult = IntPtr.Zero;
do
{
result =FindWindowEx(hwndParent, result, null, null);
if(result != IntPtr.Zero)
++ct;
} while(ct < index && result != IntPtr.Zero);
returnresult;
}
}
2、啟動按鈕事件代碼
privatevoid button4_Click(objectsender, EventArgs e)
{
if(p == null)
{
p = newSystem.Diagnostics.Process();
p.StartInfo.FileName =”B程式Path”;
p.Start();
//必須讓線程掛起一定時間,否則字串不能自動發送過去。
Thread.Sleep(500);
IntPtrParenthWnd = new IntPtr(0);
IntPtrpp = new IntPtr(0);
IntPtrmwh = IntPtr.Zero;
//通過視窗標題來擷取視窗
ParenthWnd = FindWindow(null, "******");
//通過索引來擷取B程式的文本編輯框,通過索引先擷取該控制項的ID,然後將該ID轉換為16進位,與Spy++查看到ID進行對比,從而確定控制項的索引。
IntPtrbutt = FindWindowByIndex(ParenthWnd, 5);
uintWM_CHAR = 0x0102;
// SendMessage1每次發送一個字串,所以通過迴圈發送完整使用者名稱
foreach(char c in this.textBox1.Text)
{
SendMessage1(butt, WM_CHAR, c, 0);
}
//擷取密碼輸入框
IntPtrbutt1 = FindWindowByIndex(ParenthWnd, 3);
//發送密碼
foreach(char c in this.textBox2.Text)
{
SendMessage1(butt1, WM_CHAR, c,0);
}
}
else
{
if(p.HasExited) //是否正在運行{
p.Start();
}
}
3、程式運行結果
點擊啟動後,在第二個程式中(前面的),直接擷取到第一個程式所發送的使用者名稱和密碼。