通过 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

相关文章

windos server2022的配置故障转移服务的图文教程

《windosserver2022的配置故障转移服务的图文教程》本文主要介绍了windosserver2022的配置故障转移服务的图文教程,以确保服务和应用程序的连续性和可用性,文中通过图文介绍的非... 目录准备环境:步骤故障转移群集是 Windows Server 2022 中提供的一种功能,用于在多个

NFS实现多服务器文件的共享的方法步骤

《NFS实现多服务器文件的共享的方法步骤》NFS允许网络中的计算机之间共享资源,客户端可以透明地读写远端NFS服务器上的文件,本文就来介绍一下NFS实现多服务器文件的共享的方法步骤,感兴趣的可以了解一... 目录一、简介二、部署1、准备1、服务端和客户端:安装nfs-utils2、服务端:创建共享目录3、服

Spring常见错误之Web嵌套对象校验失效解决办法

《Spring常见错误之Web嵌套对象校验失效解决办法》:本文主要介绍Spring常见错误之Web嵌套对象校验失效解决的相关资料,通过在Phone对象上添加@Valid注解,问题得以解决,需要的朋... 目录问题复现案例解析问题修正总结  问题复现当开发一个学籍管理系统时,我们会提供了一个 API 接口去

解决systemctl reload nginx重启Nginx服务报错:Job for nginx.service invalid问题

《解决systemctlreloadnginx重启Nginx服务报错:Jobfornginx.serviceinvalid问题》文章描述了通过`systemctlstatusnginx.se... 目录systemctl reload nginx重启Nginx服务报错:Job for nginx.javas

windows系统下shutdown重启关机命令超详细教程

《windows系统下shutdown重启关机命令超详细教程》shutdown命令是一个强大的工具,允许你通过命令行快速完成关机、重启或注销操作,本文将为你详细解析shutdown命令的使用方法,并提... 目录一、shutdown 命令简介二、shutdown 命令的基本用法三、远程关机与重启四、实际应用

使用IntelliJ IDEA创建简单的Java Web项目完整步骤

《使用IntelliJIDEA创建简单的JavaWeb项目完整步骤》:本文主要介绍如何使用IntelliJIDEA创建一个简单的JavaWeb项目,实现登录、注册和查看用户列表功能,使用Se... 目录前置准备项目功能实现步骤1. 创建项目2. 配置 Tomcat3. 项目文件结构4. 创建数据库和表5.

Windows自动化Python pyautogui RPA操作实现

《Windows自动化PythonpyautoguiRPA操作实现》本文详细介绍了使用Python的pyautogui库进行Windows自动化操作的实现方法,文中通过示例代码介绍的非常详细,对大... 目录依赖包睡眠:鼠标事件:杀死进程:获取所有窗口的名称:显示窗口:根据图片找元素:输入文字:打开应用:依

使用Nginx来共享文件的详细教程

《使用Nginx来共享文件的详细教程》有时我们想共享电脑上的某些文件,一个比较方便的做法是,开一个HTTP服务,指向文件所在的目录,这次我们用nginx来实现这个需求,本文将通过代码示例一步步教你使用... 在本教程中,我们将向您展示如何使用开源 Web 服务器 Nginx 设置文件共享服务器步骤 0 —

javafx 如何将项目打包为 Windows 的可执行文件exe

《javafx如何将项目打包为Windows的可执行文件exe》文章介绍了三种将JavaFX项目打包为.exe文件的方法:方法1使用jpackage(适用于JDK14及以上版本),方法2使用La... 目录方法 1:使用 jpackage(适用于 JDK 14 及更高版本)方法 2:使用 Launch4j(

手把手教你idea中创建一个javaweb(webapp)项目详细图文教程

《手把手教你idea中创建一个javaweb(webapp)项目详细图文教程》:本文主要介绍如何使用IntelliJIDEA创建一个Maven项目,并配置Tomcat服务器进行运行,过程包括创建... 1.启动idea2.创建项目模板点击项目-新建项目-选择maven,显示如下页面输入项目名称,选择