zoukankan      html  css  js  c++  java
  • WPF中嵌入普通Win32程序的方法

    公司现在在研发基于.Net中WPF技术的产品,由于要兼容旧有产品,比如一些旧有的Win32程序、第三方的Win32程序等等,还要实现自动登录这些外部Win32程序,因此必须能够将这些程序整合到我们的系统中来,让使用者看起来它们好像是一个程序。

    在MSDN中有专门的章节提到了在WPF中嵌入Win32控件的办法,那就是使用 HwndHost ,只要把 Win32控件的句柄传递给 HwndHost 就可以了。MSDN中的例子演示的都是在同一个进程内创建的 Win32控件,我一开始认为只要通过FindWindow等Win32API得到外部Win32程序的窗口句柄,然后将窗口句柄交给 HwndHost 就可以了。实现核心代码如下:

             protected   override   HandleRef  BuildWindowCore( HandleRef  hwndParent)

            {

                appProc =  new   Process ();

                appProc.StartInfo.WindowStyle =  ProcessWindowStyle .Hidden;

                appProc.StartInfo.FileName =  @"D:greeninst etterm etterm.exe" ;

                appProc.Start();

     

                 //等待初始化完成,实现有点土

                 Thread .Sleep(1000);

     

                hwndHost =  Win32Native .FindWindow( "NetTermClass" null );

                 // 嵌入在HwnHost中的窗口必须要 设置为WS_CHILD风格

                 uint  oldStyle =  Win32Native .GetWindowLong(hwndHost,  Win32Native .GWL_STYLE);

                   Win32Native .SetWindowLong(hwndHost,  Win32Native .GWL_STYLE, (oldStyle |  Win32Native .WS_CHILD));

                 //将netterm的父窗口设置为HwndHost

                 Win32Native .SetParent(hwndHost, hwndParent.Handle);

                 return   new   HandleRef ( this , hwndHost);

            }

    这里启动的是NetTerm这个外部程序。实践证明我这种想法是可行的,但是唯一的问题就是虽然 外部Win32程序显示到WPF程序中来了,但是很奇怪的是嵌入的Win32程序再也无法点击了,点击按钮、输入按键都不起作用,程序好像死了一样。经过分析,我认为由于通过 SetParent 这个 Win32API 将NetTerm的父窗口设置为了 HwndHost ,这样 NetTerm就不再有自己独立的窗口消息循环,而是眼巴巴等着 HwndHost 这个爹给他发 消息。可能由于WPF对于消息循环的处理 不同于以前的Win32程序,导致所有的鼠标点击、按键 消息都不能被传递给NetTerm这个儿子,这样NetTerm就得不到任何消息,所以就像死了一样。

    解决这个问题的思路是截获WPF的窗口消息,然后把它通过 SendMessage 这个Win32API 转发给NetTerm。但是找了半天也没找到WPF的消息处理的地方,请教同事以后得知WPF根本不像传统的Win32程序那样有窗口消息循环,而是自己搞了一套。郁闷了一会儿,突然灵光一现:管它什么WPF不WPF,它本质上还是Win32程序,只不过是一个内部使用了DirectX技术的Win32程序而已,只要是Win32程序一定有办法拿到它的窗口消息循环。啥办法呢?对!就是窗口钩子。使用 SetWindowsHookEx 这个Win32API可以截获一个窗口所有的 消息循环,这样只要挑出来发给 HwndHost 的消息,然后把它转发给 NetTerm窗口就ok了。经过改造以后NetTerm终于活过来了!!!

    解决了最核心的问题就该处理普通问题了,主要问题及对策如下:

    1、隐藏NetTerm的窗口边框,这样看起来就感觉不出来NetTerm是一个外部程序了。思路很简单使用 GetWindowLong 得到窗口原来的风格,然后再附加一个 WS_BORDER 风格就ok了。

    //设置为WS_CHILD风格

                 uint  oldStyle =  Win32Native .GetWindowLong(hwndHost,  Win32Native .GWL_STYLE);

                 //&~WS_BORDER去掉边框,这样看起来更像一个内嵌的程序,注意()的作用,改变默认的优先级

                 Win32Native .SetWindowLong(hwndHost,  Win32Native .GWL_STYLE, (oldStyle |  Win32Native .WS_CHILD)&~ Win32Native .WS_BORDER);

    2、隐藏NetTerm在任务栏上的按钮

    只要找到任务栏的句柄,然后首先向它发送TB_BUTTONCOUNT得到它上边按钮的个数,由于NetTerm是刚刚启动的,可以认为最后一个按钮就是NetTerm的按钮,只要向任务栏的句柄发送TB_DELETEBUTTON消息将最后一个按钮删掉就ok了。

            private void HideTaskBarButton()

            {

                IntPtr vHandle = Win32Native.FindWindow("Shell_TrayWnd", null);

                vHandle = Win32Native.FindWindowEx(vHandle, IntPtr.Zero,

     "ReBarWindow32", IntPtr.Zero);

                vHandle = Win32Native.FindWindowEx(vHandle, 

    IntPtr.Zero, "MSTaskSwWClass", IntPtr.Zero);

                vHandle = Win32Native.FindWindowEx(vHandle, IntPtr.Zero, 

    "ToolbarWindow32", IntPtr.Zero);

                //得到任务栏中按钮的数目

                int vCount = Win32Native.SendMessage(new HandleRef(this, vHandle), 

    (uint)Win32Native.TB_BUTTONCOUNT, IntPtr.Zero, IntPtr.Zero).ToInt32();

                

                //认为最后一个按钮就是被嵌套程序的按钮,删除它

                Win32Native.SendMessage(new HandleRef(this, vHandle), 

    Win32Native.TB_DELETEBUTTON, new IntPtr(vCount - 1), IntPtr.Zero);

            }

    这是在WinXP下的处理。好像Win2000、Vista的任务栏的结构是不同的,如果需要运行在这些OS下需要做进一步的改进。

    3、 自动登录。在NetTerm启动以后自动登录到服务器,并且自动输入用户名、密码,并且启动指定的程序。NetTerm支持在启动参数中指定要连接的服务器地址,这样可以解决自动登录到服务器的问题;使用 SendMessage( handle , Win32Native.WM_CHAR,  ch , IntPtr.Zero) 向NetTerm窗口发送模拟按键就可以实现自动键入Linux指令的效果。由于Linux指令需要一定的处理的时间,所以每发完一条指令就要Sleep一会儿以防止键入指令速度过快。

    运行效果如下: 主要代码如下, Win32Native .cs是我们写的一个对Win32API的调用声明,都是简单的PInvoke声明,由于尺寸比较大这里就不贴出来了,大家可以查MSDN自己来声明。

    =======================================NetTermHost.cs============================

    namespace Client.Pages

    {

        class NetTermHost : HwndHost

        {

            public IntPtr hwndHost;

     

            private IntPtr hookId = new IntPtr(3);

     

            private HookProc hookProc;

     

            private Process appProc;

     

            protected override HandleRef BuildWindowCore(HandleRef hwndParent)

            {

                appProc = new Process();

                appProc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;

                appProc.StartInfo.FileName = @"D:greeninst etterm etterm.exe";

                //设置要连接的主机名,这样启动以后就立即连接了

                appProc.StartInfo.Arguments = "192.168.88.128";

                appProc.Start();

     

                //等待初始化完成,实现有点土

                Thread.Sleep(1000);

     

                hwndHost = Win32Native.FindWindow("NetTermClass", null);

     

                //设置为WS_CHILD风格

                uint oldStyle = Win32Native.GetWindowLong(hwndHost, Win32Native.GWL_STYLE);

                //&~WS_BORDER去掉边框,这样看起来更像一个内嵌的程序,注意()的作用,改变默认的优先级

                Win32Native.SetWindowLong(hwndHost, Win32Native.GWL_STYLE, (oldStyle | Win32Native.WS_CHILD)&~Win32Native.WS_BORDER);

     

                //将netterm的父窗口设置为HwndHost,爹地我来了

                Win32Native.SetParent(hwndHost, hwndParent.Handle);

         

          //窗口最大化

                Win32Native.ShowWindow(hwndHost.ToInt32(), Win32Native.SW_MAXIMIZE);

     

          //隐藏netterm在任务栏上的按钮

                HideTaskBarButton();

          //隐藏netterm的工具栏

                HideNetTermToolBar();

     

                //由于登录过程非常长,所以不要在这里等太久,否则界面像死了一样,所以启动线程来操作

                ThreadStart ts = new ThreadStart(

                                delegate()

                                {

                    //自动登录telnet

                                    AutoLogin();

                                }

                            );

                Thread thread = new Thread(ts);

                thread.Start();       

     

                hookProc = new HookProc(MyHookHandler);

     

                //设置钩子,截获主窗口界面消息循环

                //对当前的窗口,使用IntPtr.Zero

                HookApi.SetWindowsHookEx(hookId.ToInt32(), hookProc, IntPtr.Zero, 

                    HookApi.GetCurrentThreadId());

     

                return new HandleRef(this, hwndHost);

            }

     

            private int MyHookHandler(int code, IntPtr wparam, ref MSG msg)

            {

                //如果是当前Host的消息,则将其转发给netterm程序

                if (msg.hwnd == this.Handle)

                {

                    HandleRef handleRef = new HandleRef(this, hwndHost);

                    Win32Native.SendMessage(handleRef, (uint)msg.message, msg.wParam, msg.lParam);

                }

     

                int nextHook = HookApi.CallNextHookEx(hookId, code, wparam, ref msg);

                return nextHook;

            }

     

            private void HideTaskBarButton()

            {

                IntPtr vHandle = Win32Native.FindWindow("Shell_TrayWnd", null);

                vHandle = Win32Native.FindWindowEx(vHandle, IntPtr.Zero, "ReBarWindow32", IntPtr.Zero);

                vHandle = Win32Native.FindWindowEx(vHandle, IntPtr.Zero, "MSTaskSwWClass", IntPtr.Zero);

                vHandle = Win32Native.FindWindowEx(vHandle, IntPtr.Zero, "ToolbarWindow32", IntPtr.Zero);

                //得到任务栏中按钮的数目

                int vCount = Win32Native.SendMessage(new HandleRef(this, vHandle), (uint)Win32Native.TB_BUTTONCOUNT, IntPtr.Zero, IntPtr.Zero).ToInt32();

                

                //认为最后一个按钮就是被嵌套程序的按钮,删除它

                Win32Native.SendMessage(new HandleRef(this, vHandle), Win32Native.TB_DELETEBUTTON, new IntPtr(vCount - 1), IntPtr.Zero);

            }

     

            private void HideNetTermToolBar()

            {

                IntPtr toolBarWin = Win32Native.FindWindowEx(hwndHost, IntPtr.Zero, "ToolbarWindow32", IntPtr.Zero);

                Win32Native.ShowWindow(toolBarWin.ToInt32(), 0);

            }

     

            private void AutoLogin()

            {      

                Thread.Sleep(10000);

          //输用户名

                SendString("yzk ");

                Thread.Sleep(1000);

          //输密码

                SendString("123456 ");

                Thread.Sleep(1000);

          //进入目录

                SendString("cd /mnt/hgfs/NAHA/src/ ");

                Thread.Sleep(1000);

          //运行字符终端

                SendString("python FrontEnd.py ");

            }

     

        //模拟按键

            private void SendString(String s)

            {

                foreach(char c in s)

                {

                    Win32Native.SendMessage(new HandleRef(this, hwndHost), Win32Native.WM_CHAR, new IntPtr(c), IntPtr.Zero);

                }            

            }

     

            protected override void DestroyWindowCore(HandleRef hwnd)

            {

                HandleRef handleRef = new HandleRef(this, hwndHost);

                //关闭netterm窗口

                //Win32Native.SendMessage(handleRef, Win32Native.WM_CLOSE, IntPtr.Zero, IntPtr.Zero);

     

                //很黄很暴力,直接杀死

                appProc.Kill();

     

                //有bug,如果netterm已经连上远程主机,那么如果不退出就close的话会弹出对话框,这就会造成主程序无法退出

                //几种策略:杀死netterm、发送模拟键点击“是”按钮、把netterm释放出来让用户决定、只是demo而已不管它

                //没有主菜单的bug

                //由于是拦截消息循环搞的,所以有可能有潜在的bug

     

                Win32Native.DestroyWindow(hwnd.Handle); 

                HookApi.UnhookWindowsHookEx(hookId); 

            }

        }

    }

     

     

    =======================TradeNetTermHost.cs===========================================

    public partial class TradeNetTermHost : UserControl

    {

      private NetTermHost ch;

      public TradeNetTermHost()

      {

        Win32Native.InitCommonControls();

        InitializeComponent();

        ch = new NetTermHost();

        this.Win32HosterBorder.Child = ch;

     

        Loaded += new RoutedEventHandler(TradeNetTermHost_Loaded);

      }

     

      void TradeNetTermHost_Loaded(object sender, RoutedEventArgs e)

      {

        //设置netterm容器为焦点,否则消息不会发给它

        Win32Native.SetFocus(ch.Handle);

      }

    }

     

    REF:http://www.blogjava.net/huanzhugege/archive/2008/04/24/195516.html

  • 相关阅读:
    Linux Core Dump
    ODP.NET Managed正式推出
    获取EditText的光标位置
    (Java实现) 洛谷 P1603 斯诺登的密码
    (Java实现) 洛谷 P1603 斯诺登的密码
    (Java实现) 洛谷 P1036 选数
    (Java实现) 洛谷 P1036 选数
    (Java实现) 洛谷 P1012 拼数
    (Java实现) 洛谷 P1012 拼数
    (Java实现) 洛谷 P1028 数的计算
  • 原文地址:https://www.cnblogs.com/dotfun/p/4282702.html
Copyright © 2011-2022 走看看