通过 Web 服务共享 Windows 剪贴板

2024-04-07 08:18

本文主要是介绍通过 Web 服务共享 Windows 剪贴板,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

通过 Web 服务共享 Windows 剪贴板

发布日期: 2007-03-08 | 更新日期: 2007-03-08

您曾经在多台计算机上工作过吗?您希望能够将剪贴板内容从一台计算机复制到其他计算机吗?我一直都很希望能够有一种快速而简便的方法,通过简单的复制和粘贴将文本代码段、屏幕快照、甚至文件移动到其他计算机上。如果您对这个话题感兴趣,那么请继续阅读本文。

我希望无论两台计算机是否同时在线都可以实现此操作,并且不会被防火墙、NAT 等停止。因此我选择基于服务器的体系结构而非对等体系结构。该体系结构包括客户端应用程序(通过调用 Web 服务将剪贴板内容传输到服务器)、Web 服务(缓存剪贴板内容)、另一个客户端组件(从服务器上检索剪贴板内容并将它们放置到本地计算机的剪贴板上)。

为解决这个问题,我们需要以编程方式在作为复制来源和复制目标的两台计算机上访问剪贴板。值得庆幸的是,.NET 在本地 Windows 剪贴板 API 中提供了一个受管包装程序,我们可以通过它进行访问。相关命名空间是 C# 的 Clipboard 和 my.Computer.Clipboard。由于我们所感兴趣的是将剪贴板对象从一台计算机移动到另一台,因此我们首先需要确定要将哪些类型的对象放置到剪贴板上,以便于我们对其进行各种操作(复制文本、图像及文件)。通过使用 Clipboard 命名空间编写简要代码段,我们可以遍历要对其执行各种类型操作的剪贴板上的所有对象,以了解我们正在处理的内容。

Visual C#

IDataObject clipData = Clipboard.GetDataObject();
//检索剪贴板上所有可用格式的一个字符串数组。
string[] formats = clipData.GetFormats();
//遍历剪贴板可用格式列表
foreach (string format in formats)
{
//将每个对象添加到一个数组列表,以便我们能够检查对象类型
object dataObject = clipData.GetData(format);
}

Visual Basic

Dim clipData As IDataObject = Clipboard.GetDataObject
Dim formats() As String = clipData.GetFormats
'遍历剪贴板可用格式列表
For Each format As String In formats
'将每个对象添加到一个数组列表,以便我们能够检查对象类型
Dim dataObject As Object = clipData.GetData(format)
Next

下面的屏幕快照显示了将 Word 中的文本复制到剪贴板中的结果。字符串数组中的每一个格式都表示剪贴板中的数据。因为目标应用程序(我们将向其中粘贴)还是未知的,所以剪贴板中有多种格式,每种格式包含相同的数据。本项目的目的是在目标计算机的剪贴板上复制所有这些格式。

.

检查完剪贴板上的文本、图像及文件对象的对象之后,就很容易确定出我们需要关注的主要对象类型。它们是“System.IO.MemoryStream”、“System.IO.FileStream”、“System.Drawing.Bitmap”和“System.String”。因为所有这些信息都会通过 Web 服务传输到服务器,一种简单的方法就是将所有对象序列化为字节进行传输。这样操作的原因有很多,其中一项事实就是,复杂对象(例如 MemoryStream)不能像 Strings 那样被简单地序列化并通过 Web 服务发送。此外,某些对象很大,已超出 Web 服务调用所允许的范围,因此在传输时需要分解成较小的部分,然后再在服务器端以正确的顺序重新组装。同样,当客户端请求剪贴板项目时,我们需要分解各个对象,然后通过 Web 服务将结果返回到客户端,接着再重新组装。

要创建的第一项是一个基本函数,它将这些很大的流分解成更多的易于管理的字节数组,以传输给 Web 服务。下面的这个函数通过发送 MemoryStream 块执行该任务,其中的块大小通过“byteCount”常量进行限制。达到该限制值之后,缓冲区中的内容就会通过调用 Web 服务来发送,以在服务器上进行存储和组装。一旦我们需要发送的内容为 0 字节或字节数少于“byteCount”常量数,我们将发送缓冲区中的剩余元素,并使用“isFinalTransaction”标志来通知 Web 服务此特定对象已传输完毕。

Visual C#

private void UploadStreamBlock(string format, string objectType, MemoryStream memStream)
{
//每次我们输入此函数,即开始了一个新事务。一个事务代表剪贴板上的一个完整对象,
//我们在服务器端使用它来知道如何将流放回到一起
string transactionGuid = System.Guid.NewGuid().ToString();
memStream.Position = 0;
byte[] buffer = new byte[byteCount];
bool isFinalTransaction = false;
//当目前的流位置加上我们的字节计数小于流长度时,尽可能
//继续发送。
while ((memStream.Position + byteCount) <= memStream.Length)
{
//如果恰好位于流的最后一个字节,则将最终事务标志设置为 true,使服务器
//知道这是所需的此事务的最后一位。
if (memStream.Position + byteCount == memStream.Length)
{
isFinalTransaction = true;
}
//将流读入缓冲区,以便通过 Web 服务进行传输。
memStream.Read(buffer, 0, byteCount);
ws.InsertMessageStream(buffer, format, objectType, transactionGuid, isFinalTransaction, clipBoardGUID);
}
long remainingBytes = memStream.Length - memStream.Position;
//如果还有剩余字节,则计算出还有多少剩余字节并通过 Web 服务传输此对象的
//最后一位。
if ((int)remainingBytes > 0)
{
byte[] remainingBuffer = new byte[(int)remainingBytes];
memStream.Read(remainingBuffer, 0, (int)remainingBytes);
ws.InsertMessageStream(remainingBuffer, format, objectType, transactionGuid, true, clipBoardGUID);
}
}

Visual Basic

Private Sub UploadStreamBlock(ByVal format As String, ByVal objectType As String, ByVal memStream As MemoryStream)
'每次我们输入此函数,即开始了一个新事务。一个事务代表剪贴板上的一个完整对象,
'我们在服务器端使用它来知道如何将流放回到一起
Dim transactionGuid As String = System.Guid.NewGuid.ToString
memStream.Position = 0
Dim buffer() As Byte = New Byte((byteCount) - 1) {}
Dim isFinalTransaction As Boolean = False
'当目前的流位置加上我们的字节计数小于流长度时,尽可能
'继续发送。
While ((memStream.Position + byteCount) _
<= memStream.Length)
'如果恰好位于流的最后一个字节,则将最终事务标志设置为 true,使服务器
'知道这是所需的此事务的最后一位。
If ((memStream.Position + byteCount) _
= memStream.Length) Then
isFinalTransaction = True
End If
'将流读入缓冲区,以便通过 Web 服务进行传输。
memStream.Read(buffer, 0, byteCount)
clipService.InsertMessageStream(buffer, format, objectType, transactionGuid, isFinalTransaction, clipBoardGUID)
End While
Dim remainingBytes As Long = (memStream.Length - memStream.Position)
'如果还有剩余字节,则计算出还有多少剩余字节并通过 Web 服务传输此对象的
'最后一位。
If (CType(remainingBytes, Integer) > 0) Then
Dim remainingBuffer() As Byte = New Byte((CType(remainingBytes, Integer)) - 1) {}
memStream.Read(remainingBuffer, 0, CType(remainingBytes, Integer))
clipService.InsertMessageStream(remainingBuffer, format, objectType, transactionGuid, True, clipBoardGUID)
End If
End Sub

Web 服务的服务器端需要将全部剪贴板内容由大量的字节数组重新组合到一起,因此保留所有对象、对象的类型以及格式对于剪贴板在目标计算机上正常工作是至关重要的。我们使用 clipBoardGuid 来确定我们是在进行新的剪贴板张贴,还是在将对象添加到已有实例上。同时使用 isFinalTranaction 标志来了解此字节数组应该是现有事务的一部分,还是新事务中的第一个。所有剪贴板项都会保存到磁盘中,以便稍后由请求它们的任何客户端进行检索。下面是执行此功能的代码。

Visual C#

    [WebMethod]
public void InsertMessageStream(byte[] buffer, string format, string objectType, string transactionGuid, bool isFinalTransaction, string clipBoardGUID)
{
//当前目录始终基于此时正发送的剪贴板。
string clipBoardGUIDDirectory = System.Web.HttpContext.Current.Request.PhysicalApplicationPath + clipBoardGUID;
try
{
//如果该目录不存在,则删除所有其他目录(剪贴板实例)并创建一个新目录
//如果该目录已经存在,则此特定事务是同一剪贴板的一部分,因此不要做任何事情。
//这能奏效是因为 clipboardDirectory 不是从客户端发送的 GUID。
if (!Directory.Exists(clipBoardGUIDDirectory))
{
string[] dirs = Directory.GetDirectories(System.Web.HttpContext.Current.Request.PhysicalApplicationPath);
foreach (string dir in dirs)
{
Directory.Delete(dir, true);
}
Directory.CreateDirectory(clipBoardGUIDDirectory);
}
}
catch
{
}
//根据当前事务、格式和对象类型创建文件名。我们将在以后对此进行解析,
//以便知道如何将其添加回目标剪贴板。
string fileName = clipBoardGUIDDirectory + "//" + transactionGuid + "_" + format + "_" + objectType;
FileStream fs = new FileStream(fileName, FileMode.Append, FileAccess.Write);
fs.Position = fs.Length;
fs.Write(buffer, 0, buffer.Length);
fs.Close();
}

Visual Basic

    <WebMethod()> _
Public Sub InsertMessageStream(ByVal buffer() As Byte, ByVal format As String, ByVal objectType As String, ByVal transactionGuid As String, ByVal isFinalTransaction As Boolean, ByVal clipBoardGUID As String)
'当前目录始终基于此时正发送的剪贴板。
Dim clipBoardDataDirectory As String = (System.Web.HttpContext.Current.Request.PhysicalApplicationPath + "//Clipboard_Data")
Dim clipBoardGUIDDirectory As String = (clipBoardDataDirectory + ("//" + clipBoardGUID))
Try
'如果该目录不存在,则删除所有其他目录(剪贴板实例)并创建一个新目录
'如果该目录已经存在,则此特定事务是同一剪贴板的一部分,因此不要做任何事情。
'这能奏效是因为 clipboardDirectory 不是基于从客户端发送的 GUID。
If Not Directory.Exists(clipBoardGUIDDirectory) Then
Dim dirs() As String = Directory.GetDirectories(clipBoardDataDirectory)
For Each dir As String In dirs
Directory.Delete(dir, True)
Next
Directory.CreateDirectory(clipBoardGUIDDirectory)
End If
Catch
End Try
'根据当前事务、格式和对象类型创建文件名。我们将在以后对此进行解析
'以便知道如何将其添加回目标剪贴板。
Dim fileName As String = (clipBoardGUIDDirectory + ("//" _
+ (transactionGuid + ("_" _
+ (format + ("_" + objectType))))))
Dim fs As FileStream = New FileStream(fileName, FileMode.Append, FileAccess.Write)
fs.Position = fs.Length
fs.Write(buffer, 0, buffer.Length)
fs.Close()
End Sub

每种剪贴板格式对象都存储在磁盘上,以便客户端以后进行检索。请注意下面屏幕快照中如何使用文件名来存储对象、对象类型以及剪贴板格式的唯一 transactionID。所有这些信息片段对于正确地重新组装项以及将它们放置到目标剪贴板上都是必不可少的。

.

现在服务器上对每种剪贴板格式对象都有了对应的表示,我们需要一种方法能够将每一项重新放回目标剪贴板上。下面的 Web 服务方法提供类型为“ClipboardStream”的返回结果。ClipboardStream 对象包含将各项重新组装到目标剪贴板中所必需的所有相关信息。因为 Web 服务是请求-响应类型关系,所以 Web 服务期望客户端继续调用 Web 服务,直到成功接收所有剪贴板项。此外,更大的复杂性也由此引入,因为每个单独的剪贴板项都可能会拆分成多个项(当它们超出常量“byteCount”所设置的最大长度时),因此目标计算机必须跟踪每个请求并通过名为“currentByte”的变量告知服务器最后一个事务停止的位置。Web 服务代码如下所示。

Visual C#

    [WebMethod]
public ClipboardStream GetMessageStream(string transactionGUID, string[] previousTransactionGUIDs, string clipBoardGUID, long currentByte)
{
string clipBoardDataDirectory = System.Web.HttpContext.Current.Request.PhysicalApplicationPath + "Clipboard_Data";
string clipBoardGUIDDirectory = clipBoardDataDirectory + "//" + clipBoardGUID;
string currentTransaction = "";
bool isLastTransaction = false;
//如果 clipBoardGUID 不为空,则只需确保该目录仍存在。
if (clipBoardGUID != "")
{
//如果该目录不存在,会引发异常,它一定已经被删除了。
if (!Directory.Exists(clipBoardGUIDDirectory))
{
throw new Exception("请求的剪贴板不存在。它一定已经被删除了。");
}
}
//如果 clipboardGUID 为空,则这是客户端与服务器的第一次接触,我们需要
//选择可用的剪贴板 GUID 返回给用户。
else
{
string[] availableClipBoard = Directory.GetDirectories(clipBoardDataDirectory)[0].Split('//');
clipBoardGUID = availableClipBoard[availableClipBoard.Length - 1];
clipBoardGUIDDirectory += clipBoardGUID;
}
//我们需要获取下一个事务。每次完成一个事务,我们都在客户端将其添加到 previousTransactionGUIDs,
//使我们知道不用再次发送它。
currentTransaction = GetCurrentTransaction(clipBoardGUIDDirectory, previousTransactionGUIDs);
//如果当前事务为空,则我们的工作已经完成,已经没有内容需要发送给客户端
if (currentTransaction == null)
{
return null;
}
//打开文件流并将其设置到客户端需要的位置。
FileStream fs = new FileStream(currentTransaction, FileMode.Open);
fs.Position = currentByte;
//确定这是否是该对象的最后一个事务,以便通知客户端。
long numBytesToRead = fs.Length - currentByte;
if (numBytesToRead > byteCount)
{
numBytesToRead = byteCount;
isLastTransaction = false;
}
else
{
isLastTransaction = true;
}
//将文件流字节读入缓冲区并填充对象以返回给客户端。
byte[] buffer = new byte[numBytesToRead];
fs.Read(buffer, 0, (int)numBytesToRead);
fs.Close();
FileInfo fi = new FileInfo(currentTransaction);
ClipboardStream clipboardStream = new ClipboardStream();
clipboardStream.Buffer = buffer;
clipboardStream.ClipBoardID = clipBoardGUID;
clipboardStream.Format = fi.Name.Split('_')[1];
clipboardStream.ObjectType = fi.Name.Split('_')[2];
clipboardStream.IsLastTransaction = isLastTransaction;
clipboardStream.TransactionID = currentTransaction;
return clipboardStream;
}

Visual Basic

    <WebMethod()> _
Public Function GetMessageStream(ByVal transactionGUID As String, ByVal previousTransactionGUIDs() As String, ByVal clipBoardGUID As String, ByVal currentByte As Long) As ClipboardStream
Dim clipBoardDataDirectory As String = (System.Web.HttpContext.Current.Request.PhysicalApplicationPath + "Clipboard_Data")
Dim clipBoardGUIDDirectory As String = clipBoardDataDirectory
Dim currentTransaction As String = ""
Dim isLastTransaction As Boolean = False
'if the clipBoardGUID is not empty then we only need to make sure that the directory still exists.
If (clipBoardGUID <> "") Then
'if the directory does not exist throw an exception, it must have already been deleted.
If Not Directory.Exists(clipBoardGUIDDirectory) Then
Throw New Exception("Requested clipboard does not exist.  It must have been deleted.")
End If
End If
'if the clipboardGUID is empty then this is the client's first contact with the server and we need
'to select the available clipboard GUID to return to the user.
Dim availableClipBoard() As String = Directory.GetDirectories(clipBoardDataDirectory)(0).Split(Microsoft.VisualBasic.ChrW(92))
clipBoardGUID = availableClipBoard((availableClipBoard.Length - 1))
clipBoardGUIDDirectory = (clipBoardGUIDDirectory + "/" + clipBoardGUID)
'we need to get the next transaction.  Each time we finish a transaction we add it to previousTransactionGUIDs
'at the client end so we know not to send it again.
currentTransaction = GetCurrentTransaction(clipBoardGUIDDirectory, previousTransactionGUIDs)
'if the current transaction is null then we're done and there are no more to send to the client
If (currentTransaction Is Nothing) Then
Return Nothing
End If
'open the filestream and set it to the position requested by the client.
Dim fs As FileStream = New FileStream(currentTransaction, FileMode.Open)
fs.Position = currentByte
'determind if this is the last transaction or not for this object so we can let the client know.
Dim numBytesToRead As Long = (fs.Length - currentByte)
If (numBytesToRead > byteCount) Then
numBytesToRead = byteCount
isLastTransaction = False
Else
isLastTransaction = True
End If
'read the filestream bytes to the buffer and populate the object to return to the client.
Dim buffer() As Byte = New Byte((numBytesToRead) - 1) {}
fs.Read(buffer, 0, CType(numBytesToRead, Integer))
fs.Close()
Dim fi As FileInfo = New FileInfo(currentTransaction)
Dim clipboardStream As ClipboardStream = New ClipboardStream
clipboardStream.Buffer = buffer
clipboardStream.ClipBoardID = clipBoardGUID
clipboardStream.Format = fi.Name.Split(Microsoft.VisualBasic.ChrW(95))(1)
clipboardStream.ObjectType = fi.Name.Split(Microsoft.VisualBasic.ChrW(95))(2)
clipboardStream.IsLastTransaction = isLastTransaction
clipboardStream.TransactionID = currentTransaction
Return clipboardStream
End Function

这篇关于通过 Web 服务共享 Windows 剪贴板的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/882090

相关文章

Spring LDAP目录服务的使用示例

《SpringLDAP目录服务的使用示例》本文主要介绍了SpringLDAP目录服务的使用示例... 目录引言一、Spring LDAP基础二、LdapTemplate详解三、LDAP对象映射四、基本LDAP操作4.1 查询操作4.2 添加操作4.3 修改操作4.4 删除操作五、认证与授权六、高级特性与最佳

Redis在windows环境下如何启动

《Redis在windows环境下如何启动》:本文主要介绍Redis在windows环境下如何启动的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Redis在Windows环境下启动1.在redis的安装目录下2.输入·redis-server.exe

JSON Web Token在登陆中的使用过程

《JSONWebToken在登陆中的使用过程》:本文主要介绍JSONWebToken在登陆中的使用过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录JWT 介绍微服务架构中的 JWT 使用结合微服务网关的 JWT 验证1. 用户登录,生成 JWT2. 自定义过滤

Linux samba共享慢的原因及解决方案

《Linuxsamba共享慢的原因及解决方案》:本文主要介绍Linuxsamba共享慢的原因及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux samba共享慢原因及解决问题表现原因解决办法总结Linandroidux samba共享慢原因及解决

一文教你如何将maven项目转成web项目

《一文教你如何将maven项目转成web项目》在软件开发过程中,有时我们需要将一个普通的Maven项目转换为Web项目,以便能够部署到Web容器中运行,本文将详细介绍如何通过简单的步骤完成这一转换过程... 目录准备工作步骤一:修改​​pom.XML​​1.1 添加​​packaging​​标签1.2 添加

Linux上设置Ollama服务配置(常用环境变量)

《Linux上设置Ollama服务配置(常用环境变量)》本文主要介绍了Linux上设置Ollama服务配置(常用环境变量),Ollama提供了多种环境变量供配置,如调试模式、模型目录等,下面就来介绍一... 目录在 linux 上设置环境变量配置 OllamPOgxSRJfa手动安装安装特定版本查看日志在

SpringCloud之LoadBalancer负载均衡服务调用过程

《SpringCloud之LoadBalancer负载均衡服务调用过程》:本文主要介绍SpringCloud之LoadBalancer负载均衡服务调用过程,具有很好的参考价值,希望对大家有所帮助,... 目录前言一、LoadBalancer是什么?二、使用步骤1、启动consul2、客户端加入依赖3、以服务

Windows Server服务器上配置FileZilla后,FTP连接不上?

《WindowsServer服务器上配置FileZilla后,FTP连接不上?》WindowsServer服务器上配置FileZilla后,FTP连接错误和操作超时的问题,应该如何解决?首先,通过... 目录在Windohttp://www.chinasem.cnws防火墙开启的情况下,遇到的错误如下:无法与

Python解析器安装指南分享(Mac/Windows/Linux)

《Python解析器安装指南分享(Mac/Windows/Linux)》:本文主要介绍Python解析器安装指南(Mac/Windows/Linux),具有很好的参考价值,希望对大家有所帮助,如有... 目NMNkN录1js. 安装包下载1.1 python 下载官网2.核心安装方式3. MACOS 系统安

Windows系统下如何查找JDK的安装路径

《Windows系统下如何查找JDK的安装路径》:本文主要介绍Windows系统下如何查找JDK的安装路径,文中介绍了三种方法,分别是通过命令行检查、使用verbose选项查找jre目录、以及查看... 目录一、确认是否安装了JDK二、查找路径三、另外一种方式如果很久之前安装了JDK,或者在别人的电脑上,想