扩展 Microsoft 终端服务客户端以提供无缝窗口






4.93/5 (60投票s)
本文描述了一种扩展Microsoft的终端服务/远程桌面客户端以使用无缝窗口的可能方法。
概述和背景
Microsoft的终端服务客户端(也称为“远程桌面连接”)有一个主要缺点。远程应用程序不会像在本地桌面上运行一样显示,而是显示在代表服务器桌面的独立窗口中。如果您只想专心在服务器上工作,这还可以;但如果您想在服务器应用程序和本地桌面之间切换,或者想在不同的服务器上运行应用程序,这就会很麻烦。需要一种方法将远程应用程序作为“无缝窗口”显示在客户端。
已经开发了商业产品来实现Windows环境下的此功能,最著名的可能是Citrix。Citrix使用自己的协议(ICA)将应用程序发布到客户端。其他人则使用了Microsoft的RDP(远程桌面协议)协议以及其他软件来实现相同效果(其中最著名的是Tarentalla的Canaveral IQ – 我猜测他们使用了与本文提出的方法类似但更复杂的方法)。
虽然这些产品提供了比无缝窗口更多功能,但它们也非常昂贵。如果能在普通的RDP客户端中获得此功能,而无需购买整个应用程序发布产品,那就太好了。
本文通过使用虚拟通道扩展Microsoft的RDP客户端,并在服务器和客户端之间进行通信,为解决此问题提供了一种可能的方法。选择此选项而不是重写或扩展现有的开源RDP客户端(如rdesktop)是因为我们将能够继续利用Microsoft客户端的所有功能(以及他们将来添加的所有新功能)。此外,使用Microsoft客户端的一个优点是,由于其终端服务客户端具有用于此目的的ActiveX组件,我们可以通过网页实现一些基本的应用程序发布。
概念与方法
引言
RDP协议不提供有关打开窗口的任何信息,它只做的是将服务器屏幕的位图发送到客户端,并将鼠标和键盘按键路由回服务器。为了让应用程序看起来像在本地计算机上运行,我们需要在客户端“裁剪”掉服务器桌面。
要做到这一点,很明显我们需要知道服务器上有什么窗口以及它们在哪里。为此,我们可以使用全局钩子来了解服务器会话中发生的事情(窗口打开、关闭、最小化等)。接下来,我们需要将此信息传回客户端,这时就需要使用虚拟通道。这是Microsoft在打开终端服务会话时用于服务器和客户端之间双向通信的机制。它们主要用于文件传输和打印,但我们可以使用它将窗口信息发送回客户端。一旦客户端拥有这些信息,它就可以使用这些信息来“裁剪”服务器桌面,仅显示在服务器上运行的应用程序。
挂钩服务器上的窗口消息
此时,我必须感谢Markus Rollmann。我以他的Code Project文章为基础编写了软件的这部分。这部分应用程序运行在服务器上,并打开虚拟通道的服务器端。要使用全局钩子,有一个单独的DLL(称为“hookdll.dll”)。此DLL监视服务器上窗口发生的事情,并通过虚拟通道发送适当的字符串消息,然后由客户端代码解释。
我在这里学到的一个有趣的事情是,如果没有任何已注册的shell,就无法从全局钩子获得WH_SHELL
通知。通常,“explorer.exe”作为shell运行,但在终端服务会话中,您通常只运行应用程序而不运行shell。为了解决这个问题(我花了很长时间才弄清楚为什么我没有收到这些通知),您需要将您的应用程序注册为shell来替换“explorer.exe”(请参阅代码了解如何做到这一点)。
我们在服务器上的应用程序(“clipper.exe”)现在也是我们的shell。当客户端启动我们的会话时,我们将启动应用程序设置为“clipper.exe”。这确保了在启动服务器上的任何应用程序之前,所有全局钩子都已设置好。 “clipper.exe”的一个参数是要启动的应用程序的路径,它将“clipper.exe”作为新进程启动。此外,它将监视该进程,以便在进程退出时,“clipper.exe”也可以关闭。这将导致会话注销,因为shell应用程序已关闭。
响应客户端的消息
虚拟通道的工作原理是在服务器上有一个可执行文件将信息发送回客户端。在客户端,有一个DLL在终端服务客户端加载时被加载,它可以监听通过虚拟通道传来的信息(“TswindowClipper.dll” – 这是通过在注册表键 HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client\Default\AddIns\TSWindowClipper
中设置("Name" = "TSWindowClipper.dll"
)来加载的)。我们的客户端DLL代码维护一个哈希表,其中包含服务器上所有窗口的位置及其状态。然后,客户端DLL代码能够计算一个区域,该区域匹配服务器上的窗口,并相应地“裁剪”终端服务客户端窗口(客户端DLL在启动时获得终端服务客户端窗口的句柄)。当服务器上的窗口移动、大小调整、打开、关闭、最小化或最大化时,会向客户端DLL发送一条消息,客户端DLL随后能够重新计算此区域并相应地裁剪窗口。客户端的另一项功能是创建表示服务器上打开的窗口的虚拟任务栏项。
在Web上发布应用程序
可以使用远程桌面ActiveX控件从网页启动无缝终端服务会话。为此,我们需要告诉它使用我们的客户端虚拟通道DLL。我们这样操作:
MsRdpClient.SecuredSettings.StartProgram = "c:\tswinclipper\clipper.exe notepad.exe"
MsRdpClient.AdvancedSettings.PluginDlls = "tswindowclipper.dll"
MsRdpClient.FullScreen = TRUE
MsRdpClient.Width = screen.width
MsRdpClient.Height = screen.height
我们需要将“StartProgram”设置为“clipper.exe”的正确路径以及要启动的服务器应用程序。这是一个示例HTML文件(您需要从此处获取ActiveX控件的“msrdp.cab”文件,并将其放在与此HTML页面相同的目录中)。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Remote Desktop Web Connection</title>
<meta content="JavaScript" name="vs_defaultClientScript">
</head>
<body>
<script language="vbscript">
sub BtnConnect
MsRdpClient.server = "<%YOUR SERVER%>"
MsRdpClient.UserName = "<%YOUR USERNAME%>"
MsRdpClient.AdvancedSettings.ClearTextPassword="<%YOUR PASSWORD%>"
MsRdpClient.Domain = "<%YOUR DOMAIN%>"
MsRdpClient.SecuredSettings.StartProgram = "c:\tswinclipper\clipper.exe notepad.exe"
MsRdpClient.AdvancedSettings.PluginDlls = "tswindowclipper.dll"
MsRdpClient.FullScreen = TRUE
MsRdpClient.Width = screen.width
MsRdpClient.Height = screen.height
'These 2 do not work from the activeX control
'MsRdpClient.AdvancedSettings2.DisplayConnectionBar = FALSE
'MsRdpClient.AdvancedSettings2.PinConnectionBar = TRUE
'Device redirection options
MsRdpClient.AdvancedSettings2.RedirectDrives = FALSE
MsRdpClient.AdvancedSettings2.RedirectPrinters = TRUE
MsRdpClient.AdvancedSettings2.RedirectPorts = FALSE
MsRdpClient.AdvancedSettings2.RedirectSmartCards = FALSE
'Connect
MsRdpClient.Connect
end sub
sub MsRdpClient_OnDisconnected(disconnectCode)
'goback to the page that called this one
history.go(-1)
end sub
</script>
<br>
<object language="vbscript" id="MsRdpClient" onreadystatechange="BtnConnect"
codebase="msrdp.cab#version=5,1,2600,1050"
classid="CLSID:9059f30f-4eb1-4bd2-9fdc-36f43a218f4a">
</object>
<br>
</body>
</html>
如何安装和运行
以下是主要步骤:
- 在服务器和客户端上运行“setup.exe”。然后,在客户端上运行终端服务客户端或远程桌面连接。
- 分辨率必须设置为全屏,并且不得显示连接栏。
- 启动程序shell必须是“clipper.exe”的完整路径,后跟一个参数,即您要在服务器上无缝启动的应用程序。
- 在连接设置中必须开启“拖动时显示窗口内容”,否则移动或调整窗口大小将不起作用。
- 同样重要的是要注意,“拖动时显示窗口内容”也必须在服务器上启用。
-
最好关闭桌面背景以节省带宽,因为我们无论如何都会裁剪桌面。
客户端设置中安装了一个名为“windowclipper.pdf”的PDF文件。如果您遇到任何问题,请查看它。
限制
- 某些类型的窗口未被裁剪(DOS窗口和子窗口,例如打开/保存文件对话框是最明显的)。这是因为我还没有找到一种方法可以通过全局钩子检测到这些窗口。
- 最小化和最大化尚未完全实现。
- 客户端屏幕的分辨率必须在服务器上受支持(某些分辨率会在屏幕的左/右出现黑条)。
- 连接到断开的会话不会恢复裁剪,因为有关打开窗口的数据存储在客户端的内存中,并在远程桌面客户端关闭时丢失。
- 单击虚拟任务栏项无效(应该发送一条消息回服务器,目前通信仅从服务器到客户端)。
致谢
- Windows和系统托盘中的全局钩子:感谢Markus Rollmann提供的Code Project文章和代码。
- C++中的哈希表:感谢Jerry Coffin(hash.h,hash.cpp)
- C++中的字符串帮助类:感谢Joe O'Leary(StdString.h)
- C++中的标记器类:感谢Eduardo Velasquez(Tokenizer.h,Tokenizer.cpp)
修订历史
- 2005/01/16:添加了文章1.0版本。
- 2005/01/16:添加了源代码0.3版本和“setup.exe”。
- 2005/04/14:添加了源代码0.3.1版本和“/setup.exe/”。更新了代码ZIP文件以包含空的“build”文件夹(用于生成后事件,之前缺失)。修改了安装程序以复制依赖DLL(msvcr70d.dll和wtsapi32.dll)。