假如这个世界上只剩下你一个人,当你正坐在屋子里的时候,这时突然响起了敲门声...
WPF中嵌入普通Win32程序的方法
查看( 76 ) /
评论( 0 )
公司现在在研发基于.Net中WPF技术的产品,由于要兼容旧有产品,比如一些旧有的Win32程序、第三方的Win32程序等等,还要实现自动登录这些外部Win32程序,因此必须能够将这些程序整合到我们的系统中来,让使用者看起来它们好像是一个程序。ITPUB个人空间-U%B!Q*M;}
ITPUB个人空间 z+M%g DPrD
在MSDN中有专门的章节提到了在WPF中嵌入Win32控件的办法,那就是使用 HwndHost ,只要把 Win32控件的句柄传递给 HwndHost 就可以了。MSDN中的例子演示的都是在同一个进程内创建的 Win32控件,我一开始认为只要通过FindWindow等Win32API得到外部Win32程序的窗口句柄,然后将窗口句柄交给 HwndHost 就可以了。实现核心代码如下:
[xwF U W!V%F#w ^0 ITPUB个人空间-P`;v&^7o&LP{Q'k
protected override HandleRef BuildWindowCore( HandleRef hwndParent)ITPUB个人空间7Ow:A;n)PzG^4[
ITPUB个人空间H%J3|n'u(PR\ T
{
Q-Tp"s;a5so}2q:S-]O0
v)m$A{/Q,Z0 appProc = new Process ();
l psfd0
p^%^1NU0 appProc.StartInfo.WindowStyle = ProcessWindowStyle .Hidden;ITPUB个人空间 hg|[;Z
'f$jA2q.B^,p2L \0 appProc.StartInfo.FileName = @"D:greeninst
n oL0nSb&Q6Y#Z0 etterm
.a6w[k_R0 etterm.exe" ;ITPUB个人空间:^j6w2Tj+[H
ITPUB个人空间(b9oYf.A
appProc.Start();
'yrm'L;j9?0
#hF7w1E4n*@@o0 //等待初始化完成,实现有点土ITPUB个人空间@j/Jqi[y
ITPUB个人空间8]Q-V?2BS bH)K
Thread .Sleep(1000);
/r_RVR/a0e9o0 ITPUB个人空间mC9G IE0H0}
hwndHost = Win32Native .FindWindow( "NetTermClass" , null );
hhFnxJU2u0 ITPUB个人空间+E3mfnzuA3l
// 嵌入在HwnHost中的窗口必须要 设置为WS_CHILD风格ITPUB个人空间] G7a2j ? J!]
Uq.Fw'vg0 uint oldStyle = Win32Native .GetWindowLong(hwndHost, Win32Native .GWL_STYLE);
-`e\m.r/O0
b8wk^wm0 Win32Native .SetWindowLong(hwndHost, Win32Native .GWL_STYLE, (oldStyle | Win32Native .WS_CHILD));
,T,Kyx#g%k0 ITPUB个人空间 Da5Ob f?
//将netterm的父窗口设置为HwndHost
'VY+Rt6Xe O0
.?4|gqX L:QjT0 Win32Native .SetParent(hwndHost, hwndParent.Handle);ITPUB个人空间Z;X)bXTpIA
1Tv7Uz"g/?0 return new HandleRef ( this , hwndHost);ITPUB个人空间O,T:t?P0b(p
``(ci1rkD;b0 }
Dc5kt3a2M0 ITPUB个人空间QB\hx(p2k.Bxo
这里启动的是NetTerm这个外部程序。实践证明我这种想法是可行的,但是唯一的问题就是虽然 外部Win32程序显示到WPF程序中来了,但是很奇怪的是嵌入的Win32程序再也无法点击了,点击按钮、输入按键都不起作用,程序好像死了一样。经过分析,我认为由于通过 SetParent 这个 Win32API 将NetTerm的父窗口设置为了 HwndHost ,这样 NetTerm就不再有自己独立的窗口消息循环,而是眼巴巴等着 HwndHost 这个爹给他发 消息。可能由于WPF对于消息循环的处理 不同于以前的Win32程序,导致所有的鼠标点击、按键 消息都不能被传递给NetTerm这个儿子,这样NetTerm就得不到任何消息,所以就像死了一样。ITPUB个人空间s9X8i!{ S
B7Cg$DB5AS0 解决这个问题的思路是截获WPF的窗口消息,然后把它通过 SendMessage 这个Win32API 转发给NetTerm。但是找了半天也没找到WPF的消息处理的地方,请教同事以后得知WPF根本不像传统的Win32程序那样有窗口消息循环,而是自己搞了一套。郁闷了一会儿,突然灵光一现:管它什么WPF不WPF,它本质上还是Win32程序,只不过是一个内部使用了DirectX技术的Win32程序而已,只要是Win32程序一定有办法拿到它的窗口消息循环。啥办法呢?对!就是窗口钩子。使用 SetWindowsHookEx 这个Win32API可以截获一个窗口所有的 消息循环,这样只要挑出来发给 HwndHost 的消息,然后把它转发给 NetTerm窗口就ok了。经过改造以后NetTerm终于活过来了!!!ITPUB个人空间1?c*R]3rWV
L.j2n7Qbk3n)P0 解决了最核心的问题就该处理普通问题了,主要问题及对策如下:ITPUB个人空间3e!{GddW Z8Z9a:L
ITPUB个人空间qHBB/wo+`$t
1、隐藏NetTerm的窗口边框,这样看起来就感觉不出来NetTerm是一个外部程序了。思路很简单使用 GetWindowLong 得到窗口原来的风格,然后再附加一个 WS_BORDER 风格就ok了。ITPUB个人空间/E-o1[!VMoC6A u
ITPUB个人空间L#iRp.Z
//设置为WS_CHILD风格ITPUB个人空间C7A0~ut
ITPUB个人空间MA8P'Ew:]
uint oldStyle = Win32Native .GetWindowLong(hwndHost, Win32Native .GWL_STYLE);
WbmtV e&{.DNq/Y.v0
O/S8PxWw&V-bPq0 //&~WS_BORDER去掉边框,这样看起来更像一个内嵌的程序,注意()的作用,改变默认的优先级
SXrX4FdNk'A%Db0 ITPUB个人空间? ~D/Z%c"t3B
Win32Native .SetWindowLong(hwndHost, Win32Native .GWL_STYLE, (oldStyle | Win32Native .WS_CHILD)&~ Win32Native .WS_BORDER);ITPUB个人空间ua`:KgEm'g
S0HTm o0 2、隐藏NetTerm在任务栏上的按钮ITPUB个人空间:^5@BYh?4tY
ITPUB个人空间ZIdX}P6a
只要找到任务栏的句柄,然后首先向它发送TB_BUTTONCOUNT得到它上边按钮的个数,由于NetTerm是刚刚启动的,可以认为最后一个按钮就是NetTerm的按钮,只要向任务栏的句柄发送TB_DELETEBUTTON消息将最后一个按钮删掉就ok了。ITPUB个人空间$qm HJ&R0Bkwc8|!w
ITPUB个人空间uzrme-MKr
private void HideTaskBarButton()
Mu9}P0ZO0
w5}9Z3~|d'f0 {
U-j~A:g GJ]0 ITPUB个人空间2MAu1H4OQ
IntPtr vHandle = Win32Native.FindWindow("Shell_TrayWnd", null);ITPUB个人空间,TK*VH3^p%m
t^ JeM$?0 vHandle = Win32Native.FindWindowEx(vHandle, IntPtr.Zero,ITPUB个人空间`"V O;uY;D
!Js2KA'DC%HT0 "ReBarWindow32", IntPtr.Zero);ITPUB个人空间~p D,n0E:d
ITPUB个人空间2`f'_xp+} {
vHandle = Win32Native.FindWindowEx(vHandle, ITPUB个人空间0`Tm!b*i
xUq/D6J0 IntPtr.Zero, "MSTaskSwWClass", IntPtr.Zero);
5n5K;W!N7jI0
8Pg|P yL3z#vF0y0OYi0 vHandle = Win32Native.FindWindowEx(vHandle, IntPtr.Zero, ITPUB个人空间@y THofu
;v L/ratQ0 "ToolbarWindow32", IntPtr.Zero);
] @Y(x#|x!b0 ITPUB个人空间7Q2s Y"zB&`H/W"K8v
//得到任务栏中按钮的数目
'v6S Am ^0
jnig?/x#Dk0 int vCount = Win32Native.SendMessage(new HandleRef(this, vHandle), ITPUB个人空间H].p!vQ:A#H!VL
^L]-iRfnh0 (uint)Win32Native.TB_BUTTONCOUNT, IntPtr.Zero, IntPtr.Zero).ToInt32();
n*u2s\%d0 ITPUB个人空间%`.q}s4IYQ5l
ITPUB个人空间y_#Ct9[:a
5^%r&U+Za-m0 //认为最后一个按钮就是被嵌套程序的按钮,删除它
{\j;W Q'a5s#t0 ITPUB个人空间B4psx?1jU/M
Win32Native.SendMessage(new HandleRef(this, vHandle),
V"Tj'A#Z_0
&iR_T&B Rq0 Win32Native.TB_DELETEBUTTON, new IntPtr(vCount - 1), IntPtr.Zero);
E+^Da"l${0 ITPUB个人空间-iq\x1Xp:k*`
}
x RHFx.l0
2g8g4u?2qz0 这是在WinXP下的处理。好像Win2000、Vista的任务栏的结构是不同的,如果需要运行在这些OS下需要做进一步的改进。
O8k.Sb3[.A+@ L0 ITPUB个人空间4O6T*Q(T)b%DG D
3、 自动登录。在NetTerm启动以后自动登录到服务器,并且自动输入用户名、密码,并且启动指定的程序。NetTerm支持在启动参数中指定要连接的服务器地址,这样可以解决自动登录到服务器的问题;使用 SendMessage( handle , Win32Native.WM_CHAR, ch , IntPtr.Zero) 向NetTerm窗口发送模拟按键就可以实现自动键入Linux指令的效果。由于Linux指令需要一定的处理的时间,所以每发完一条指令就要Sleep一会儿以防止键入指令速度过快。 ITPUB个人空间!xX \/zq:QF._Q} xB
主要代码如下, Win32Native .cs是我们写的一个对Win32API的调用声明,都是简单的PInvoke声明,由于尺寸比较大这里就不贴出来了,大家可以查MSDN自己来声明。ITPUB个人空间7b |DSMr$[ Zb
6?2h Yg];y0 =======================================NetTermHost.cs============================ITPUB个人空间Y.Nl VW
ITPUB个人空间(|#bO{{w$r+D'D
namespace Client.PagesITPUB个人空间 V:}1uRe]
ITPUB个人空间nXD$V C,n%K-v P]
{ITPUB个人空间Tc%~1x,r%EQ"m
%i^XY2OH\!n/q0 class NetTermHost : HwndHost
%q9l!kNT*P u(Z0
'CV:vYk1Ce1N,P0 {ITPUB个人空间NE D&H [sX
r:A_^#{*Q1|i0 public IntPtr hwndHost;ITPUB个人空间f'xXJbj W$c
5FNUf2l5Qq?v3i0j0 private IntPtr hookId = new IntPtr(3);ITPUB个人空间?!zpD;N6V7h|u
^0R{SkP]D?0 private HookProc hookProc;ITPUB个人空间3H1Hg2Y}7OY
ITPUB个人空间(L*SD't+O [^M
private Process appProc;ITPUB个人空间]]!t a4a+Ry
ITPUB个人空间;^-jn9h$E$ey:d.L
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
UR-h Rf0
_;s S%a s+GD)miV0 {
'd"B\7aM*up)O0 ITPUB个人空间J?+EH w[
appProc = new Process();
OagA7v0
E!go'Uje0 appProc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
Hz/g'm/Qc0 ITPUB个人空间h)}(S{ GHB8]
appProc.StartInfo.FileName = @"D:greeninstITPUB个人空间H_3PmC6I n
etterm
\4k9Ap Tw8J0 etterm.exe";
*wQ9ckYK7df#T"b0
ITPUB个人空间 z+M%g DPrD
在MSDN中有专门的章节提到了在WPF中嵌入Win32控件的办法,那就是使用 HwndHost ,只要把 Win32控件的句柄传递给 HwndHost 就可以了。MSDN中的例子演示的都是在同一个进程内创建的 Win32控件,我一开始认为只要通过FindWindow等Win32API得到外部Win32程序的窗口句柄,然后将窗口句柄交给 HwndHost 就可以了。实现核心代码如下:
[xwF U W!V%F#w ^0 ITPUB个人空间-P`;v&^7o&LP{Q'k
protected override HandleRef BuildWindowCore( HandleRef hwndParent)ITPUB个人空间7Ow:A;n)PzG^4[
ITPUB个人空间H%J3|n'u(PR\ T
{
Q-Tp"s;a5so}2q:S-]O0
v)m$A{/Q,Z0 appProc = new Process ();
l psfd0
p^%^1NU0 appProc.StartInfo.WindowStyle = ProcessWindowStyle .Hidden;ITPUB个人空间 hg|[;Z
'f$jA2q.B^,p2L \0 appProc.StartInfo.FileName = @"D:greeninst
n oL0nSb&Q6Y#Z0 etterm
.a6w[k_R0 etterm.exe" ;ITPUB个人空间:^j6w2Tj+[H
ITPUB个人空间(b9oYf.A
appProc.Start();
'yrm'L;j9?0
#hF7w1E4n*@@o0 //等待初始化完成,实现有点土ITPUB个人空间@j/Jqi[y
ITPUB个人空间8]Q-V?2BS bH)K
Thread .Sleep(1000);
/r_RVR/a0e9o0 ITPUB个人空间mC9G IE0H0}
hwndHost = Win32Native .FindWindow( "NetTermClass" , null );
hhFnxJU2u0 ITPUB个人空间+E3mfnzuA3l
// 嵌入在HwnHost中的窗口必须要 设置为WS_CHILD风格ITPUB个人空间] G7a2j ? J!]
Uq.Fw'vg0 uint oldStyle = Win32Native .GetWindowLong(hwndHost, Win32Native .GWL_STYLE);
-`e\m.r/O0
b8wk^wm0 Win32Native .SetWindowLong(hwndHost, Win32Native .GWL_STYLE, (oldStyle | Win32Native .WS_CHILD));
,T,Kyx#g%k0 ITPUB个人空间 Da5Ob f?
//将netterm的父窗口设置为HwndHost
'VY+Rt6Xe O0
.?4|gqX L:QjT0 Win32Native .SetParent(hwndHost, hwndParent.Handle);ITPUB个人空间Z;X)bXTpIA
1Tv7Uz"g/?0 return new HandleRef ( this , hwndHost);ITPUB个人空间O,T:t?P0b(p
``(ci1rkD;b0 }
Dc5kt3a2M0 ITPUB个人空间QB\hx(p2k.Bxo
这里启动的是NetTerm这个外部程序。实践证明我这种想法是可行的,但是唯一的问题就是虽然 外部Win32程序显示到WPF程序中来了,但是很奇怪的是嵌入的Win32程序再也无法点击了,点击按钮、输入按键都不起作用,程序好像死了一样。经过分析,我认为由于通过 SetParent 这个 Win32API 将NetTerm的父窗口设置为了 HwndHost ,这样 NetTerm就不再有自己独立的窗口消息循环,而是眼巴巴等着 HwndHost 这个爹给他发 消息。可能由于WPF对于消息循环的处理 不同于以前的Win32程序,导致所有的鼠标点击、按键 消息都不能被传递给NetTerm这个儿子,这样NetTerm就得不到任何消息,所以就像死了一样。ITPUB个人空间s9X8i!{ S
B7Cg$DB5AS0 解决这个问题的思路是截获WPF的窗口消息,然后把它通过 SendMessage 这个Win32API 转发给NetTerm。但是找了半天也没找到WPF的消息处理的地方,请教同事以后得知WPF根本不像传统的Win32程序那样有窗口消息循环,而是自己搞了一套。郁闷了一会儿,突然灵光一现:管它什么WPF不WPF,它本质上还是Win32程序,只不过是一个内部使用了DirectX技术的Win32程序而已,只要是Win32程序一定有办法拿到它的窗口消息循环。啥办法呢?对!就是窗口钩子。使用 SetWindowsHookEx 这个Win32API可以截获一个窗口所有的 消息循环,这样只要挑出来发给 HwndHost 的消息,然后把它转发给 NetTerm窗口就ok了。经过改造以后NetTerm终于活过来了!!!ITPUB个人空间1?c*R]3rWV
L.j2n7Qbk3n)P0 解决了最核心的问题就该处理普通问题了,主要问题及对策如下:ITPUB个人空间3e!{GddW Z8Z9a:L
ITPUB个人空间qHBB/wo+`$t
1、隐藏NetTerm的窗口边框,这样看起来就感觉不出来NetTerm是一个外部程序了。思路很简单使用 GetWindowLong 得到窗口原来的风格,然后再附加一个 WS_BORDER 风格就ok了。ITPUB个人空间/E-o1[!VMoC6A u
ITPUB个人空间L#iRp.Z
//设置为WS_CHILD风格ITPUB个人空间C7A0~ut
ITPUB个人空间MA8P'Ew:]
uint oldStyle = Win32Native .GetWindowLong(hwndHost, Win32Native .GWL_STYLE);
WbmtV e&{.DNq/Y.v0
O/S8PxWw&V-bPq0 //&~WS_BORDER去掉边框,这样看起来更像一个内嵌的程序,注意()的作用,改变默认的优先级
SXrX4FdNk'A%Db0 ITPUB个人空间? ~D/Z%c"t3B
Win32Native .SetWindowLong(hwndHost, Win32Native .GWL_STYLE, (oldStyle | Win32Native .WS_CHILD)&~ Win32Native .WS_BORDER);ITPUB个人空间ua`:KgEm'g
S0HTm o0 2、隐藏NetTerm在任务栏上的按钮ITPUB个人空间:^5@BYh?4tY
ITPUB个人空间ZIdX}P6a
只要找到任务栏的句柄,然后首先向它发送TB_BUTTONCOUNT得到它上边按钮的个数,由于NetTerm是刚刚启动的,可以认为最后一个按钮就是NetTerm的按钮,只要向任务栏的句柄发送TB_DELETEBUTTON消息将最后一个按钮删掉就ok了。ITPUB个人空间$qm HJ&R0Bkwc8|!w
ITPUB个人空间uzrme-MKr
private void HideTaskBarButton()
Mu9}P0ZO0
w5}9Z3~|d'f0 {
U-j~A:g GJ]0 ITPUB个人空间2MAu1H4OQ
IntPtr vHandle = Win32Native.FindWindow("Shell_TrayWnd", null);ITPUB个人空间,TK*VH3^p%m
t^ JeM$?0 vHandle = Win32Native.FindWindowEx(vHandle, IntPtr.Zero,ITPUB个人空间`"V O;uY;D
!Js2KA'DC%HT0 "ReBarWindow32", IntPtr.Zero);ITPUB个人空间~p D,n0E:d
ITPUB个人空间2`f'_xp+} {
vHandle = Win32Native.FindWindowEx(vHandle, ITPUB个人空间0`Tm!b*i
xUq/D6J0 IntPtr.Zero, "MSTaskSwWClass", IntPtr.Zero);
5n5K;W!N7jI0
8Pg|P yL3z#vF0y0OYi0 vHandle = Win32Native.FindWindowEx(vHandle, IntPtr.Zero, ITPUB个人空间@y THofu
;v L/ratQ0 "ToolbarWindow32", IntPtr.Zero);
] @Y(x#|x!b0 ITPUB个人空间7Q2s Y"zB&`H/W"K8v
//得到任务栏中按钮的数目
'v6S Am ^0
jnig?/x#Dk0 int vCount = Win32Native.SendMessage(new HandleRef(this, vHandle), ITPUB个人空间H].p!vQ:A#H!VL
^L]-iRfnh0 (uint)Win32Native.TB_BUTTONCOUNT, IntPtr.Zero, IntPtr.Zero).ToInt32();
n*u2s\%d0 ITPUB个人空间%`.q}s4IYQ5l
ITPUB个人空间y_#Ct9[:a
5^%r&U+Za-m0 //认为最后一个按钮就是被嵌套程序的按钮,删除它
{\j;W Q'a5s#t0 ITPUB个人空间B4psx?1jU/M
Win32Native.SendMessage(new HandleRef(this, vHandle),
V"Tj'A#Z_0
&iR_T&B Rq0 Win32Native.TB_DELETEBUTTON, new IntPtr(vCount - 1), IntPtr.Zero);
E+^Da"l${0 ITPUB个人空间-iq\x1Xp:k*`
}
x RHFx.l0
2g8g4u?2qz0 这是在WinXP下的处理。好像Win2000、Vista的任务栏的结构是不同的,如果需要运行在这些OS下需要做进一步的改进。
O8k.Sb3[.A+@ L0 ITPUB个人空间4O6T*Q(T)b%DG D
3、 自动登录。在NetTerm启动以后自动登录到服务器,并且自动输入用户名、密码,并且启动指定的程序。NetTerm支持在启动参数中指定要连接的服务器地址,这样可以解决自动登录到服务器的问题;使用 SendMessage( handle , Win32Native.WM_CHAR, ch , IntPtr.Zero) 向NetTerm窗口发送模拟按键就可以实现自动键入Linux指令的效果。由于Linux指令需要一定的处理的时间,所以每发完一条指令就要Sleep一会儿以防止键入指令速度过快。 ITPUB个人空间!xX \/zq:QF._Q} xB
主要代码如下, Win32Native .cs是我们写的一个对Win32API的调用声明,都是简单的PInvoke声明,由于尺寸比较大这里就不贴出来了,大家可以查MSDN自己来声明。ITPUB个人空间7b |DSMr$[ Zb
6?2h Yg];y0 =======================================NetTermHost.cs============================ITPUB个人空间Y.Nl VW
ITPUB个人空间(|#bO{{w$r+D'D
namespace Client.PagesITPUB个人空间 V:}1uRe]
ITPUB个人空间nXD$V C,n%K-v P]
{ITPUB个人空间Tc%~1x,r%EQ"m
%i^XY2OH\!n/q0 class NetTermHost : HwndHost
%q9l!kNT*P u(Z0
'CV:vYk1Ce1N,P0 {ITPUB个人空间NE D&H [sX
r:A_^#{*Q1|i0 public IntPtr hwndHost;ITPUB个人空间f'xXJbj W$c
5FNUf2l5Qq?v3i0j0 private IntPtr hookId = new IntPtr(3);ITPUB个人空间?!zpD;N6V7h|u
^0R{SkP]D?0 private HookProc hookProc;ITPUB个人空间3H1Hg2Y}7OY
ITPUB个人空间(L*SD't+O [^M
private Process appProc;ITPUB个人空间]]!t a4a+Ry
ITPUB个人空间;^-jn9h$E$ey:d.L
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
UR-h Rf0
_;s S%a s+GD)miV0 {
'd"B\7aM*up)O0 ITPUB个人空间J?+EH w[
appProc = new Process();
OagA7v0
E!go'Uje0 appProc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
Hz/g'm/Qc0 ITPUB个人空间h)}(S{ GHB8]
appProc.StartInfo.FileName = @"D:greeninstITPUB个人空间H_3PmC6I n
etterm
\4k9Ap Tw8J0 etterm.exe";
*wQ9ckYK7df#T"b0