作者:边城浪子◎2005-01-16

关键字:
 C#,API,FindWindow,FindWindowEx,SendMessage,进程,注册表

 设计初衷:
 公司为了便于网络管理,使用了IEEE 802.1X的网络访问控制,这样每次开机需要输入两次登录密码,于是我就研究了一下用C#来帮我输入第二此登录的密码

 设计思想:
 主要是通过调用Windows API中的一些方法,主要使用的也就是FindWindow,FindWindowEx和SendMessage这三个函数,循环遍历当前的所有窗口,找到目标窗口和进程以后把保存在特定位置的用户名密码以及域信息自动填入输入框中,然后再触发一下button事件,最后程序本身退出。

 环境:
 在Windows 2000中文版 + sp4,VS.net 2003中文版下开发
 在Windows 2000中文版下测试通过

程序截图:
用C#调用Windows API和其它进程通信

具体设计这个Form的代码就略过不详细说了

 为了使用Win32 API,需要先引入下面这个命名空间:

用C#调用Windows API和其它进程通信using System.Runtime.InteropServices;

另外还需要用到进程和注册表,所以还需要引入下面的两个命名空间:

用C#调用Windows API和其它进程通信using System.Threading;
用C#调用Windows API和其它进程通信
using Microsoft.Win32;

下面的代码是用来添加对API的引用:


用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信    [DllImport("User32.dll",EntryPoint="FindWindow")]
用C#调用Windows API和其它进程通信    
private static extern IntPtr FindWindow(string lpClassName,
用C#调用Windows API和其它进程通信
string lpWindowName);
用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信    [DllImport(
"user32.dll",EntryPoint="FindWindowEx")]
用C#调用Windows API和其它进程通信    
private static extern IntPtr FindWindowEx(IntPtr hwndParent,
用C#调用Windows API和其它进程通信IntPtr hwndChildAfter, 
string lpszClass, string lpszWindow);
用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信    [DllImport(
"User32.dll",EntryPoint="SendMessage")]
用C#调用Windows API和其它进程通信    
private static extern int SendMessage(IntPtr hWnd,
用C#调用Windows API和其它进程通信
int Msg, IntPtr wParam, string lParam);
用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信    
#endregion
用C#调用Windows API和其它进程通信

主要用到的就是这三个方法,具体的我这里就不详细介绍了,请参考MSDN。

 需要用到的一些参数:

用C#调用Windows API和其它进程通信    const int WM_GETTEXT = 0x000D;
用C#调用Windows API和其它进程通信    
const int WM_SETTEXT = 0x000C;
用C#调用Windows API和其它进程通信    
const int WM_CLICK = 0x00F5;
用C#调用Windows API和其它进程通信

从名称上应该就可以了解这些参数具体的含义是什么了,而且这些参数都可以通过VS附带的工具Spy ++查到。

 下面是整个程序的核心部分,查找窗体并对它进行操作:


用C#调用Windows API和其它进程通信    private int SearchWindow()
{
用C#调用Windows API和其它进程通信        
int retval = 0//增加一个返回值用来判断操作是否成功
用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信        
//下面的这些参数都可以用Spy++查到
用C#调用Windows API和其它进程通信
        string lpszParentClass = "#32770"//整个窗口的类名
用C#调用Windows API和其它进程通信
        string lpszParentWindow = "本地连接"//窗口标题
用C#调用Windows API和其它进程通信
        string lpszClass = "Edit"//需要查找的子窗口的类名,也就是输入框
用C#调用Windows API和其它进程通信
        string lpszClass_Submit = "Button"//需要查找的Button的类名
用C#调用Windows API和其它进程通信
        string lpszName_Submit = "确定"//需要查找的Button的标题
用C#调用Windows API和其它进程通信
        string text = "";
用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信        IntPtr ParenthWnd 
= new IntPtr(0);
用C#调用Windows API和其它进程通信        IntPtr EdithWnd 
= new IntPtr(0);
用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信        
//查到窗体,得到整个窗体
用C#调用Windows API和其它进程通信
        ParenthWnd = FindWindow(lpszParentClass,lpszParentWindow);
用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信        
//判断这个窗体是否有效
用C#调用Windows API和其它进程通信
        if (!ParenthWnd.Equals(IntPtr.Zero))
{
用C#调用Windows API和其它进程通信            
//得到User Name这个子窗体,并设置其内容
用C#调用Windows API和其它进程通信
            EdithWnd = FindWindowEx(ParenthWnd,EdithWnd,lpszClass,"");
用C#调用Windows API和其它进程通信            
if (!EdithWnd.Equals(IntPtr.Zero))
{
用C#调用Windows API和其它进程通信                text 
= this.tbUserName.Text.Trim();
用C#调用Windows API和其它进程通信                
//调用SendMessage方法设置其内容
用C#调用Windows API和其它进程通信
                SendMessage(EdithWnd, WM_SETTEXT, (IntPtr)0, text);
用C#调用Windows API和其它进程通信                retval 
++;
用C#调用Windows API和其它进程通信            }

用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信            
//得到Password这个子窗体,并设置其内容
用C#调用Windows API和其它进程通信
            EdithWnd = FindWindowEx(ParenthWnd,EdithWnd,lpszClass,"");
用C#调用Windows API和其它进程通信            
if (!EdithWnd.Equals(IntPtr.Zero))
{
用C#调用Windows API和其它进程通信                text 
= this.tbPassword.Text.Trim();
用C#调用Windows API和其它进程通信                SendMessage(EdithWnd, WM_SETTEXT, (IntPtr)
0, text);
用C#调用Windows API和其它进程通信                retval 
++;
用C#调用Windows API和其它进程通信            }

用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信            
//得到Domain这个子窗体,并设置其内容
用C#调用Windows API和其它进程通信
            EdithWnd = FindWindowEx(ParenthWnd,EdithWnd,lpszClass,"");
用C#调用Windows API和其它进程通信            
if (!EdithWnd.Equals(IntPtr.Zero))
{
用C#调用Windows API和其它进程通信                text 
= this.tbDomain.Text.Trim();
用C#调用Windows API和其它进程通信                SendMessage(EdithWnd, WM_SETTEXT, (IntPtr)
0, text);
用C#调用Windows API和其它进程通信                retval 
++;
用C#调用Windows API和其它进程通信            }

用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信            
//得到Button这个子窗体,并触发它的Click事件
用C#调用Windows API和其它进程通信
            EdithWnd = FindWindowEx(ParenthWnd,
用C#调用Windows API和其它进程通信                EdithWnd,lpszClass_Submit,lpszName_Submit);
用C#调用Windows API和其它进程通信            
if (!EdithWnd.Equals(IntPtr.Zero))
{
用C#调用Windows API和其它进程通信                SendMessage(EdithWnd,WM_CLICK,(IntPtr)
0,"0");
用C#调用Windows API和其它进程通信                retval 
++;
用C#调用Windows API和其它进程通信            }

用C#调用Windows API和其它进程通信        }

用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信        
return retval;
用C#调用Windows API和其它进程通信    }

用C#调用Windows API和其它进程通信    
#endregion
用C#调用Windows API和其它进程通信

这里有一点需要说明的是,当一个窗体下面有几个类名相同的子窗体时,也就是说如果有三个输入框,这三个输入框的类名都是Edit,查找结果是依次从上往下的,最开始我不知道该怎么办才能分出具体的每个不同的输入框,后来只能这样一个一个来查找来试一下,没想到居然是对的。(有别的办法么?)

 上面的这段代码也只适用于中文版的操作系统,因为不同的操作系统下同一个窗体的名称都是不一样的,我这里也没有英文版的系统,所以也没办法进行测试。

 为了免去每次都让用户手动输入的烦恼,我需要把这些信息都保存到一个特定的文件里面去,当用户在第一次运行这个程序的时候,只需要输入一次,点下Save,先把这些信息保存到一个文件中,然后再把程序本身加载到系统启动项里去,这样下次开机的时候程序就可以自启动,然后从文件中读取信息完成以下的操作。

 选择存放文件的路径:

用C#调用Windows API和其它进程通信private string UserPro = 
用C#调用Windows API和其它进程通信        System.Environment.GetEnvironmentVariable(
"USERPROFILE");
用C#调用Windows API和其它进程通信    
private string PATH = System.Environment.GetEnvironmentVariable("USERPROFILE") + @"\Local Settings\AutoLog.ini";
用C#调用Windows API和其它进程通信

当用户点下Save按钮所触发的事件:


用C#调用Windows API和其它进程通信    private void btSubmit_Click(object sender, System.EventArgs e)
{
用C#调用Windows API和其它进程通信        SaveData();
用C#调用Windows API和其它进程通信    }

用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信    
private void SaveData()
{
用C#调用Windows API和其它进程通信        
try
{
用C#调用Windows API和其它进程通信            
//Save Data
用C#调用Windows API和其它进程通信
            FileInfo obj = new FileInfo(PATH);
用C#调用Windows API和其它进程通信            
if(obj.Exists)
用C#调用Windows API和其它进程通信                obj.Delete();
用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信            FileStream ofile 
= new FileStream(PATH,FileMode.Create);
用C#调用Windows API和其它进程通信            
//Hidden the file
用C#调用Windows API和其它进程通信
            File.SetAttributes(PATH,FileAttributes.Hidden);
用C#调用Windows API和其它进程通信            StreamWriter sw 
= new StreamWriter(ofile);
用C#调用Windows API和其它进程通信            
//把用户名密码和域信息写入文件
用C#调用Windows API和其它进程通信
            sw.WriteLine(this.tbUserName.Text);
用C#调用Windows API和其它进程通信            sw.WriteLine(
this.tbPassword.Text);
用C#调用Windows API和其它进程通信            sw.WriteLine(
this.tbDomain.Text);
用C#调用Windows API和其它进程通信            sw.Flush();
用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信            sw.Close();
用C#调用Windows API和其它进程通信            ofile.Close();
用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信            
//把当前文件拷贝到指定位置,然后再添加到注册表的启动项里
用C#调用Windows API和其它进程通信
            string opath = Application.StartupPath + @"\Login.exe";
用C#调用Windows API和其它进程通信            
string tpath = UserPro + @"\Local Settings\Login.exe";
用C#调用Windows API和其它进程通信            
if(File.Exists(tpath))
用C#调用Windows API和其它进程通信                File.Delete(tpath);
用C#调用Windows API和其它进程通信            File.Copy(opath,tpath);
用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信            RegistryKey hklm 
= Registry.CurrentUser;
用C#调用Windows API和其它进程通信            RegistryKey run 
= 
用C#调用Windows API和其它进程通信hklm.CreateSubKey(
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run");
用C#调用Windows API和其它进程通信            run.SetValue(
"AutoLogin",tpath);
用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信            
//最后程序退出
用C#调用Windows API和其它进程通信
            MessageBox.Show("OK","Information",
用C#调用Windows API和其它进程通信MessageBoxButtons.OK,MessageBoxIcon.Information);
用C#调用Windows API和其它进程通信            Application.Exit();
用C#调用Windows API和其它进程通信        }

用C#调用Windows API和其它进程通信        
catch(Exception ex)
{
用C#调用Windows API和其它进程通信            MessageBox.Show(ex.ToString(),
"Error",
用C#调用Windows API和其它进程通信MessageBoxButtons.OK,MessageBoxIcon.Error);
用C#调用Windows API和其它进程通信        }

用C#调用Windows API和其它进程通信    }

用C#调用Windows API和其它进程通信    
#endregion
用C#调用Windows API和其它进程通信

这样的话,程序就可以从文件中读取已经存放好的信息来进行验证了。最后要做的就是,需要单独开一个进程来循环执行上面的SearchWindow这个方法,直到找到符合条件的窗口并成功验证为止,并且这个进程需要随程序的启动而启动。

 我们可以在构造函数中添加一个名为LoadData的方法,然后在这个方法中进行具体的读文件信息和启动进程的操作。

 当然,先定义好这个进程:

用C#调用Windows API和其它进程通信private Thread thread;

然后是LoadData这个方法:


用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信    private void LoadData()
{
用C#调用Windows API和其它进程通信        
//Load Data
用C#调用Windows API和其它进程通信
        FileStream ofile = new FileStream(PATH,FileMode.OpenOrCreate);
用C#调用Windows API和其它进程通信        StreamReader sr 
= new StreamReader(ofile);
用C#调用Windows API和其它进程通信        
this.tbUserName.Text = sr.ReadLine();
用C#调用Windows API和其它进程通信        
this.tbPassword.Text = sr.ReadLine();
用C#调用Windows API和其它进程通信        
this.tbDomain.Text = sr.ReadLine();
用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信        sr.Close();
用C#调用Windows API和其它进程通信        ofile.Close();
用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信        
//Thread Start
用C#调用Windows API和其它进程通信
        thread = new Thread(new ThreadStart(Watch));
用C#调用Windows API和其它进程通信        thread.IsBackground 
= true;
用C#调用Windows API和其它进程通信        thread.Start();
用C#调用Windows API和其它进程通信    }

用C#调用Windows API和其它进程通信        
用C#调用Windows API和其它进程通信    
private void Watch()
{
用C#调用Windows API和其它进程通信        
//循环查找这个窗口,直到成功为止
用C#调用Windows API和其它进程通信
        while(true)
{
用C#调用Windows API和其它进程通信            
int i = this.SearchWindow();
用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信            
if(i == 4)
用C#调用Windows API和其它进程通信                
break;
用C#调用Windows API和其它进程通信        }

用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信        
//程序退出并释放资源
用C#调用Windows API和其它进程通信
        Application.Exit();
用C#调用Windows API和其它进程通信        
this.Dispose();
用C#调用Windows API和其它进程通信        
this.Close();
用C#调用Windows API和其它进程通信        }

用C#调用Windows API和其它进程通信
用C#调用Windows API和其它进程通信        
#endregion
用C#调用Windows API和其它进程通信

好,到这里就介绍完毕了,当然还有很多需要改进的地方,比如说密码存在本机上应该用一种加密的方式来保存等等。我还是第一次接触用C#调用Windows API这方面的东西,很多东西也都是从网上查资料才得到的,不足之处,恳请指出。

相关文章:

  • 2021-07-29
  • 2022-12-23
  • 2022-02-09
  • 2021-11-12
  • 2022-01-21
  • 2022-12-23
  • 2021-10-27
猜你喜欢
  • 2021-05-16
  • 2021-06-10
相关资源
相似解决方案