在 DataPackage.SetData 或 DataPackage.GetDataAsync?

问题描述

考虑使用 SetData 将数据放到 Windows 剪贴板 DataPackage 上,然后使用 GetDataAsync 检索它,如下所示:

        IEnumerable<T> objects = ...; 
        var randomAccessstream = new InMemoryRandomAccessstream();
        using (XmlDictionaryWriter xmlWriter = XmlDictionaryWriter.CreateTextWriter(randomAccessstream.AsstreamForWrite(),Encoding.Unicode)) {
            var serializer = new DataContractSerializer(typeof(T),kNownTypes);
            foreach (T obj in objects) {
                serializer.WriteObject(xmlWriter,obj);
            }
        }
        dataPackage.SetData(formatId,randomAccessstream);

然后(例如在 Clipboard.ContentsChanged 中),

        randomAccessstream = await dataPackageView.GetDataAsync(formatId) as IRandomAccessstream;
        xmlReader = XmlDictionaryReader.CreateTextReader(randomAccessstream.AsstreamForRead(),Encoding.Unicode,XmlDictionaryReaderQuotas.Max,(OnXmlDictionaryReaderClose?)null);
        var serializer = new DataContractSerializer(typeof(T),kNownTypes);
        while (serializer.IsstartObject(xmlReader)) {
            object? obj = serializer.Readobject(xmlReader);
            ...
        }
        xmlReader.dispose(); // in the real code,this is in a finally clause

我的问题是,我什么时候处理 randomAccessstream?我已经进行了一些搜索,我看到的所有使用 SetData 和 GetDataAsync 的示例都完全没有处理放入或从数据包中获取的对象。

我应该在 SetData 之后、GetDataAsync 之后、DataPackage.OperationCompleted 中、这些的某种组合中处理它,还是不处理它们?

sjb

附言如果我可以在这里提出第二个问题......当我使用例如 dataPackage.Properties.Add("IEnumerable",entities) 将引用放入 DataPackage 时,它​​是否会产生安全风险——其他应用程序可以吗?访问参考 并使用它?

解决方法

tldr

剪贴板旨在应用程序之间传递内容,并且只能传递字符串内容或对文件的引用,所有其他内容必须要么序列化为字符串,要么保存到文件中,要么必须行为就像一个文件一样,可以通过剪贴板跨应用程序域访问。

有通过剪贴板传递自定义数据和格式的支持和指导,最终这涉及围绕“如何在提供者端准备内容”和“如何在消费者端解释内容”的离散管理。如果您可以为此使用简单的序列化,那么 KISS.

IEnumerable<Test> objectsIn = new Test[] { new Test { Name = "One" },new Test { Name = "two" } };
var dataPackage = new DataPackage();
dataPackage.SetData("MyCustomFormat",Newtonsoft.Json.JsonConvert.SerializeObject(objectsIn));
Clipboard.SetContent(dataPackage);

...

var dataPackageView = Clipboard.GetContent();
string contentJson = (await dataPackageView.GetDataAsync("MyCustomFormat")) as string;
IEnumerable<Test> objectsOut = Newtonsoft.Json.JsonConvert.DeserializeObject<IEnumerable<Test>>(contentJson);

在 WinRT 中,DataPackageView 类实现确实支持传递,但是正常规则适用于流的生命周期以及流是否被释放。这对于传输大型内容或当消费者可能请求不同格式的内容时非常有用。

  • 如果您没有高级需求,或者您不传输基于文件或图像的资源,那么您不需要使用流来传输您的数据。

DataPackageView - Remarks
在共享操作期间,源应用程序将共享的数据放入 DataPackage 对象中,并将该对象发送到目标应用程序进行处理。 DataPackage 类包括许多支持以下默认格式的方法:文本、Rtf、Html、位图和 StorageItems。它还具有支持自定义数据格式的方法。要使用这些格式,源应用和目标应用都必须已经知道自定义格式的存在。

  • OP 尝试将流保存到剪贴板在这种情况下是将任意或自定义 对象保存到剪贴板的示例,它既不是字符串也不是指向文件的指针,因此操作系统级别没有处理此信息的本机方式。

从历史上看,将字符串数据或文件引用放到剪贴板上可以有效地将此信息广播到同一运行操作系统上的所有应用程序,但是 Windows 10 通过使您的剪贴板内容能够也可以跨设备同步。 DataTransfer 命名空间实现允许您影响此可用性的范围,但最终此功能旨在允许您将数据推送到当前应用程序沙盒域之外。

因此,无论您选择自己序列化内容,还是希望 DataTransfer 实现尝试为您执行此操作,如果内容还不是字符串或文件引用格式,那么内容将被序列化,并且已序列化内容,如果成功,就是提供给消费者的内容。

这样就不会出现内存泄漏或安全问题,您可能会无意中向外部进程提供对当前进程内存或执行上下文的访问权限,但数据安全仍然是一个问题,所以不要使用剪贴板传递敏感内容。


任意自定义数据的简单示例

OP 示例是将 IEnumerable<T> 对象集合放入剪贴板,并在以后检索它们。 OP 选择通过 DataContractSerializer 使用 XML 序列化,但是对序列化程序使用的流的引用保存到剪贴板,而不是实际内容。

有很多管道和第一原则逻辑在进行,但收益不大,如果您要流式传输内容,流很有用,因此如果您要允许消费者控制流,但如果您打算在单个同步进程中写入流,那么最好完全关闭流并传递您填充的缓冲区通过您的信息流,我们甚至不会尝试在以后的某个时间点重复使用相同的信息流。

以下解决方案适用于 WinRT 中的剪贴板访问,以预序列化对象集合并将它们传递给使用者:

IEnumerable<Test> objectsIn = new Test[] { new Test { Name = "One" },new Test { Name = "two" } };

var dataPackage = new DataPackage();
string formatId = "MyCustomFormat";

var serial = Newtonsoft.Json.JsonConvert.SerializeObject(objectsIn);
dataPackage.SetData(formatId,serial);
Clipboard.SetContent(dataPackage);

然后在一个完全不同的应用程序中:

string formatId = "MyCustomFormat";
var dataPackageView = Clipboard.GetContent();
object content = await dataPackageView.GetDataAsync(formatId);
string contentString = content as string;
var objectsOut = Newtonsoft.Json.JsonConvert.DeserializeObject<IEnumerable<Test>>(contentString);

foreach (var o in objectsOut)
{
    Console.WriteLine(o);
}

Test 的定义,在提供者和消费者应用程序上下文中:

public class Test
{
    public string Name { get; set; }
}
,

我什么时候处理 randomAccessStream?

仅在您使用完流后处置流,当您处置流后,它将不再可用于任何其他上下文,即使您已存储或在其他对象实例中传递对它的多个引用。

如果您谈论的是 SetData() 逻辑中引用的原始流,那么请从另一个角度来看这一点,如果您过早处置,则使用代码将无法再访问该流并会失败。

作为一般规则,我们应该尝试设计逻辑,以便在任何给定的时间点对任何给定的流都有一个明确的所有者,这样就应该清楚谁拥有负责处理流。这种对稍微不同的场景的响应很好地解释了这一点,https://stackoverflow.com/a/8791525/1690217 但是作为一般模式,只有创建流的范围应该负责处理它。

  • 一个例外是,如果您需要在创建方法之外访问流,那么父类应该持有对它的引用,在这种情况下,您应该让父类实现 IDisposable 和确保它清理了可能存在的所有资源。

您在文档中没有看到这一点的原因通常是调用 Dispose() 的时间上的细微差别超出了范围,或者会丢失在为其他目的而设计的示例中。

  • 特别是对于通过任何机制传递流并随后使用的示例,如DataPackage,很难显示所有编排代码以涵盖存储之间的时间带有 DataPackage.SetData(...) 的流,稍后通过 DataPackage.GetDataAsync(...)
  • 访问流
  • 还要考虑 DataPackage 的最常见场景,其中消费者不仅处于不同的逻辑范围,而且很可能处于完全不同的应用程序域中,以包含所有代码以涵盖何时或是否调用 dispose应包含 2 个不同应用程序的整个代码库。