C#調用API向外部程序發送數據
最近有可能要做一個項目。在項目中有這么一個功能,在A程序中調用B程序,同時在A程序中進行登陸后,要將A程序的登錄名和密碼自動填充到B程序的登陸對話框中,這樣B程序就不需要再輸入一次用戶名和密碼了,簡化操作人員的操作。剛好最近閑着沒事,就在怎么想怎么去實現。經過兩天的折騰,基本上完成了上述功能的實現。下面就把實現方法、過程與大家進行分享。
一、原理
要實現上述功能,需要調用Win API來實現。Win32 API即為Microsoft 32位平台的應用程序編程接口(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 : 被調用方清理堆棧。它是從托管代碼調用非托管函數的默認約定。
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 來檢索錯誤代碼。
三、程序實現過程
程序A:兩個文本框與一個啟動按鈕。兩個文本框用來輸入用戶名和密碼,啟動按鈕用來啟動程序B。
程序B:兩個文本框,用來接受程序A所發送過來的字符串。
1、A程序代碼清單如下:
using System.Runtime.InteropServices;
using System.Threading;
private static System.Diagnostics.Process p;
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]
static extern int SendMessage1(IntPtr hwnd, uint wMsg, int wParam, int lParam);
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode)]
public static extern IntPtr FindWindow(string className, string windowName);
[DllImport("user32.dll", EntryPoint = "FindWindowEx", CharSet = CharSet.Auto)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
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;
}
}
2、啟動按鈕事件代碼
private void button4_Click(object sender, EventArgs e)
{
if (p == null)
{
p = new System.Diagnostics.Process();
p.StartInfo.FileName =”B程序Path”;
p.Start();
//必須讓線程掛起一定時間,否則字符串不能自動發送過去。
Thread.Sleep(500);
IntPtr ParenthWnd = new IntPtr(0);
IntPtr pp = new IntPtr(0);
IntPtr mwh = IntPtr.Zero;
//通過窗口標題來獲取窗口
ParenthWnd = FindWindow(null, "******");
//通過索引來獲取B程序的文本編輯框,通過索引先獲取該控件的ID,然后將該ID轉換為16進制,與Spy++查看到ID進行對比,從而確定控件的索引。
IntPtr butt = FindWindowByIndex(ParenthWnd, 5);
uint WM_CHAR = 0x0102;
// SendMessage1每次發送一個字符串,所以通過循環發送完整用戶名
foreach (char c in this.textBox1.Text)
{
SendMessage1(butt, WM_CHAR, c, 0);
}
//獲取密碼輸入框
IntPtr butt1 = FindWindowByIndex(ParenthWnd, 3);
//發送密碼
foreach (char c in this.textBox2.Text)
{
SendMessage1(butt1, WM_CHAR, c, 0);
}
}
else
{
if (p.HasExited) //是否正在運行{
p.Start();
}
}
3、程序運行結果
點擊啟動后,在第二個程序中(前面的),直接獲取到第一個程序所發送的用戶名和密碼。