C#實現對外部程序的調用操作


測試工具,首先也是一個C#的程序,它的主要目的是:

  1:獲取上文應用程序的窗口句柄,繼而獲取TextBox句柄及Button句柄;

  2:為TextBox隨機填入一些字符;

  3:模擬點擊Button;

1.1:EnumChildWindows介紹

在這里需要介紹下EnumChildWindows,

EnumChildWindows可是個好東西,可以枚舉一個父窗口的所有子窗口:

BOOL EnumChildWindows( 
  HWND hWndParent, // handle to parent window // 父窗口句柄 
  WNDENUMPROC lpEnumFunc, // callback function // 回調函數的地址 
  LPARAM lParam // application-defined value // 你自已定義的參數 
);

  

就這么簡單,讓我們再定義一個回調函數,像下面這樣:

BOOL CALLBACK EnumChildProc( 
  HWND hwnd, // handle to child window 
  LPARAM lParam // application-defined value 
);

在調用EnumChildWindows 這個函數時,直到調用到最個一個子窗口被枚舉或回調函數返回一個false,否則將一直枚舉下去。

1.2:簡單例子的主要源碼

測試工具的主要代碼如下:

private void button1_Click(object sender, EventArgs e)
{
  //獲取測試程序的窗體句柄
  IntPtr mainWnd = FindWindow(null, "FormLogin");
  List<IntPtr> listWnd = new List<IntPtr>();
  //獲取窗體上OK按鈕的句柄 
  IntPtr hwnd_button = FindWindowEx(mainWnd, new IntPtr(0), null, "OK");
  //獲取窗體上所有控件的句柄
  EnumChildWindows(mainWnd, new CallBack(delegate(IntPtr hwnd, int lParam)
  {
  listWnd.Add(hwnd);
  return true;
  }), 0);
  foreach (IntPtr item in listWnd)
  {
    if (item != hwnd_button)
    {
      char[] UserChar = "luminji".ToCharArray();
      foreach (char ch in UserChar)
      {
        SendChar(item, ch, 100);
      }
    }
  }
SendMessage(hwnd_button, WM_CLICK, mainWnd, "0");
}

public void SendChar(IntPtr hand, char ch, int SleepTime)
{
  PostMessage(hand, WM_CHAR, ch, 0);
  System.Threading.Thread.Sleep(SleepTime);
}

public static int WM_CHAR = 0x102;
public static int WM_CLICK = 0x00F5;

[DllImport("User32.dll", EntryPoint = "SendMessage")]
public static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, string lParam);

[DllImport("user32.dll")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter,
string lpszClass, string lpszWindow);

[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

[DllImport("user32.dll")]
public static extern int AnyPopup();

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

[DllImport("user32.dll")]
public static extern int EnumThreadWindows(IntPtr dwThreadId, CallBack lpfn, int lParam);

[DllImport("user32.dll")]
public static extern int EnumChildWindows(IntPtr hWndParent, CallBack lpfn, int lParam);

[DllImport("user32.dll", CharSet = CharSet.Ansi)]
publicstaticexternIntPtrPostMessage(IntPtr hwnd,int wMsg,int wParam,int lParam);

[DllImport("user32.dll",CharSet=CharSet.Ansi)]
publicstaticexternIntPtrSendMessage(IntPtr hwnd,int wMsg,IntPtr wParam,IntPtr lParam);

[DllImport("user32.dll",CharSet=CharSet.Unicode)]
publicstaticexternIntPtrSendMessageA(IntPtr hwnd,int wMsg,int wParam,int lParam);

[DllImport("user32.dll",CharSet=CharSet.Auto)]
staticexternintGetClassName(IntPtr hWnd,StringBuilder lpClassName,int nMaxCount);

[DllImport("user32.dll",SetLastError=true,CharSet=CharSet.Auto)]
publicstaticexternintGetWindowTextLength(IntPtr hWnd);

[DllImport("user32.dll",CharSet=CharSet.Auto,SetLastError=false)]
publicstaticexternIntPtrGetParent(IntPtr hWnd);

publicdelegateboolCallBack(IntPtr hwnd,int lParam);

  

C#實現對外部程序的調用操作 - 空客 - Program Management
2:難點:如何獲取指定的控件句柄

細心的人可能已經發現,上文中,給文本框賦值的地方,使用了如下代碼:

foreach (IntPtr item in listWnd)
{
  if (item != hwnd_button)
  {
    char[] UserChar = "luminji".ToCharArray();
    foreach (char ch in UserChar)
    {
      SendChar(item, ch, 100);
    }
   }
}

  

假設我們的窗體上有多個文本框,那么事實上,這段代碼會給所有的文本框輸入"luminji”字樣。這在多數應用程序中都是不允許的,我們需要精確定位需要控制的控件。
我們在得到OK按鈕的句柄的時候,使用了函數:

IntPtr hwnd_button = FindWindowEx(mainWnd, new IntPtr(0), null, "OK");
而想要獲取文本框句柄的時候,這個函數卻不能使用,因為,所有文本框都是沒有標題的,也就是類似"OK"這個值。有人說,那就使用控件ID吧。且看:

2.1:獲取控件ID

非.NET程序,一旦程序被生成,控件ID就是固定的,所以這一招,用在非.NET程序中,那是再好也不過了。

 

C實現對外部程序的調用操作 - 空客 - Program Management
根據ID來得到控件句柄的函數聲明如下:

[DllImport("user32.dll ", EntryPoint = "GetDlgItem")] public static extern IntPtr GetDlgItem( IntPtr hParent, int nIDParentItem);
其中,第一個參數就是窗體的句柄,第二個參數就是控件ID。

但是,顯然,這種方法不適用於我們的.NET程序,因為我們會發現,我們的.NET程序沒運行一次,這個ID是變化的。

2.2:獲取控件位置

所以,最終的一個方案是:根據控件位置,人工比對后得到我們想要的控件句柄。該函數的聲明如下:

好了,現在的關鍵就是怎么取得這個控件的位置。我們在VS中查看,某個控件有X坐標和Y坐標,以上面程序的這個TextBox來說,其在VS中顯示的位置是“70,83”,但是而VS中顯示的,是不包含標題和邊框的坐標值。但是這個坐標值可以作為我們人工比對的參考。

更精確的坐標值,我們寫代碼來實現,如下:

EnumChildWindows(mainWnd, new CallBack(delegate(IntPtr hwnd, int lParam)
{
listWnd.Add(hwnd);
StringBuilder className = new StringBuilder(126);
StringBuilder title = new StringBuilder(200);
GetWindowText(hwnd, title, 200);
RECT clientRect;
GetClientRect(hwnd, out clientRect);
int controlWidth = clientRect.Width;
int controlHeight = clientRect.Height;
int x = 0, y = 0;
IntPtr parerntHandle = GetParent(hwnd);
if (parerntHandle != IntPtr.Zero)
{
GetWindowRect(hwnd, out clientRect);
RECT rect;
GetWindowRect(parerntHandle, out rect);
x = clientRect.X - rect.X;
y = clientRect.Y - rect.Y;
Debug.Print(x.ToString());
Debug.Print(y.ToString());
}
return true;
}), 0);

  

注意,上面代碼中的X和Y就是某個控件的精確的X和Y值,記錄下來,比對一下,我們就能得到精確的坐標值了。在上文的例子中,我們的文本框的坐標最終得到為“78,113”。
有了這個坐標值,我們便知道這個控件的句柄,也就是hwnd是屬於哪個控件的了。

2.3:根據EnumChildWindows枚舉次序得到句柄

如果你不想這么麻煩,還有一種簡單的方案,那就是利用EnumChildWindows的枚舉順序。要知道,在不同的機器上,EnumChildWindows枚舉一個窗體上子控件的順序是相同的,也就是說,如果有兩個文本框,它們在這台機器上被枚舉的順序一個是2,一個是3,那么,它們在其它機器上被枚舉的順序,也是這個固定次序。通過比對,我們也能得到它們各自的句柄。當然,如果我們有了這些句柄,還有什么是不能做到的呢
2.4:使用SPY++

SPY++是微軟的一個工具,用戶獲取窗體上的ID或者類型或者句柄等信息。因為在我們的這個例子里,ID和句柄在每台機器上都是不變的,所以這個工具對於我們來說,沒有多大的用處。但是,當你HACK別人的程序的時候,它會發揮一定作用。

 

C實現對外部程序的調用操作 - 空客 - Program Management

 

 

IntPtr p = IntPtr.Zero; //循環查找出同一層次上的所有#32770的句柄
do
{
  p = FindWindowEx(hwndCalcFrame, p, "#32770", null);
  Console.WriteLine(p.ToString());
} while (!p.Equals(IntPtr.Zero));

  


免責聲明!

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



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