1. 程式人生 > >C#實現對外部程式的呼叫操作

C#實現對外部程式的呼叫操作

C#藉助API實現黑盒自動化測試工具的編寫

本文程式碼下載(VS2010開發):http://download.csdn.net/source/2796362

本文摘要:

1:一個簡單的例子    

   1.1:EnumChildWindows介紹

   1.2:主要原始碼

2:難點:如何獲取指定的控制元件控制代碼

   2.1:使用SPY++

   2.2:獲取控制元件位置

   2.3:獲取控制元件ID

1:一個簡單的例子  
     在日常編碼過程中,我們常常會進行自動化測試。這裡的自動化測試不是指單元測試,而是模擬人工輸入來進行快速的、高併發的測試。可以使用的自動化工具有LOADRUNNER,以及目前在VS2010中的功能很強大的測試工作平臺(錄製操作步驟,自動生成程式碼)。但是,這些工具的熟練掌握也有一定的時間成本,並且,最主要的,對於一個程式設計師來說,那不夠靈活。所以,比較高效的一個做法是,呼叫WINDOWS API,自己動手寫編碼來實現。
     下面做一個簡單的演示。為了簡便起見,假設存在這樣一個應用程式:

1:提供一個WINFORM窗體,上面存在一個TextBox,以及一個Button;

2:點選Button,會彈出提示框,提示框內容為TextBox的值;

     現在,測試要求如下:

1:在300臺機器上執行上面的程式;

2:到這300臺機器上去點選這個Button,看看上文中的功能2有沒有實現;

     很顯然,實際情況中沒有這麼簡單的程式,實際的情況有可能是點選Button,統一下載一個檔案,而測試的要求可能就變為考核伺服器的負載。現在,測試部顯然也沒有300個人坐在客戶機上驗證測試的結果,這個時候,就需要我們提供一個自動化的測試工具,來完成必要的測試任務。

     測試工具,首先也是一個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)]
public static extern IntPtr PostMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);

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

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

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

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

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
public static extern IntPtr GetParent(IntPtr hWnd);

public delegate bool CallBack(IntPtr hwnd, int lParam);

執行效果:


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程式中,那是再好也不過了。


根據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

 

 

 

轉載自 https://blog.csdn.net/m0_37283423/article/details/74910283