0

Consider putting data onto a windows clipboard DataPackage using SetData and later retrieving it using GetDataAsync, like this:

        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);

Then later on (e.g. in 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

The question I have is, when do I dispose the randomAccessStream? I've done some searching and all the examples I've seen using SetData and GetDataAsync do absolutely nothing about disposing the object that is put into or obtain from the data package.

Should I dispose it after the SetData, after the GetDataAsync, in DataPackage.OperationCompleted, in some combination of these, or none of them?

sjb

P.S. If I can squeeze in a second question here ... when I put a reference into a DataPackage using for example dataPackage.Properties.Add( "IEnumerable<T>", entities), does it create a security risk -- can other apps access the reference and use it?

sjb-sjb
  • 1,112
  • 6
  • 14
  • Any ideas here folks? – sjb-sjb Mar 23 '21 at 23:16
  • Dispose every stream you get yourself. If you don't, this shouldn't be a big problem as most (all?) of these streams are in-memory, so no big deal if they stay around until next GC – Simon Mourier Mar 28 '21 at 10:08
  • As a general rule on SO, which you know by now ;), no, you cannot ask a second question. – Chris Schaller Mar 31 '21 at 03:00
  • If you are concerned that some 3rd party addon will record your data or try to interact with it that is stored in the clipboard, then do not use it. Are object references easy to intercept and consume in this manner, it depends on the definition of `T`, can you do it, yes, is it a vulnerability, yes, can you get much value from this information, really depends, using a stream poses the same level of risk, just slightly less effort. `DataPackage` is a risk itself, by design it is allowing you to pass data across app domains. You are effectively broadcasting to the OS with calls to `SetData()` – Chris Schaller Mar 31 '21 at 03:08
  • I don’t mind them getting the data that I put in the clipboard, I just don’t want them crawling around in my memory. What I’m not clear in is whether another prices can access my memory or not. Yes about the 2nd question . – sjb-sjb Mar 31 '21 at 16:26
  • As a general rule, on pass serialized content to the clipboard, then it is not an issue, you have removed any implementation detail or references to memory altogether. Not sure I would use Xml either, but thats up to you. – Chris Schaller Mar 31 '21 at 23:04
  • What data format specifier (`formatId`) are you using for this anyway? – Chris Schaller Mar 31 '21 at 23:47

2 Answers2

0

tldr

The Clipboard is designed to pass content between applications and can only pass string content or a references to files, all other content must be either serialized to string, or saved to a file, or must behave like a file, to be access across application domains via the clipboard.

There is support and guidance for passing custom data and formats via the clipboard, ultimately this involves discrete management around what is "how to prepare the content on the provider side" and "how to interpret the content on the consumer side". If you can use simple serialization for this, then 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);

In WinRT the DataPackageView class implementation does support passing streams however the normal rules apply for the stream in terms of lifecycle and if the stream is disposed or not. This is useful for transferring large content or when the consumer might request the content in different formats.

  • If you do not have an advanced need for it, or you are not transmitting file or image based resources, then you do not need to use a stream to transfer your data.

DataPackageView - Remarks
During a share operation, the source app puts the data being shared in a DataPackage object and sends that object to the target app for processing. The DataPackage class includes a number of methods to support the following default formats: text, Rtf, Html, Bitmap, and StorageItems. It also has methods to support custom data formats. To use these formats, both the source app and target app must already be aware that the custom format exists.

  • OPs attempt to save a stream to the Clipboard is in this case an example of saving an arbitrary or custom object to the clipboard, it is neither a string or a pointer to a file, so the OS level does not have a native way to handle this information.

Historically, putting string data, or a file reference onto the clipboard is effectively broadcasting this information to ALL applications on the same running OS, however Windows 10 extends this by making your clipboard content able to be synchronised across devices as well. The DataTransfer namespace implementation allows you to affect the scope of this availability, but ultimately this feature is designed to allow you to push data outside of your current application sandboxed domain.

So whether you choose serialize the content yourself, or you want the DataTransfer implementation to try and do it for you, the content will be serialized if it is not already a string or file reference format, and that serialized content, if it succeeds, is what will be made available to consumers.

In this way there is no memory leak or security issue where you might inadvertently provide external processes access to your current process memory or execution context, but data security is still a concern, so don't use the clipboard to pass sensitive content.


A simpler example for Arbitrary or Custom data

OPs example is to put an IEnumerable<T> collection of objects onto the clipboard, and to retrieve them later. OP is choosing to use XML serialization via the DataContractSerializer however a reference to the stream used by the serializer was saved to the clipboard, and not the actual content.

There is a lot of plumbing and first principals logic going on that for little benefit, streams are useful if you are going to stream the content, so if you are going to allow the consumer to control the stream but if you were going to write to the stream in a single synchronous process, then it is better to close off the stream altogether and pass around the buffer that you filled via your stream, we don't even try to re-use the same stream at a later point in time.

The following solution works for Clipboard access in WinRT to pre-serialize a collection of objects and pass them to a consumer:

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);

Then in perhaps an entirely different application:

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);
}

The definition of Test, in both the provider and the consumer application contexts:

public class Test
{
    public string Name { get; set; }
}
Chris Schaller
  • 13,704
  • 3
  • 43
  • 81
  • Thanks for a great answer. What I take from this is my code was wrong because I should only put strings or "a reference to a file" into SetData. The InMemoryRandomAccessStream is not "a reference to a file", it would seem, so it should not be put on the clipboard. – sjb-sjb Jul 15 '21 at 03:00
  • My remaining question is about dataPackage.Properties.Add( "IEnumerable", entities). In this case I tested that the enumerable pointer goes in and can be used in the same app when it comes out. What I am guessing is that since each process has its own address space and data, that no other process (whether on the local machine or remotely) will be able to dereference this pointer even if they did get it out of the clipboard. Correct? – sjb-sjb Jul 15 '21 at 03:03
-1

when do I dispose the randomAccessStream?

Only Dispose the stream when you have finished using it, when you have Diposed the stream it will be no longer usable in any other contexts, even if you have stored or passed multiple references to it in other object instances.

If you are talking about the original stream referenced in the SetData() logic then look at this from the other angle, If you dispose too early, the consuming code will no longer have access to the stream and will fail.

As a general rule we should try to design the logic such that at any given point in time there is a clear and single Owner for any given stream, in that way it should be clear who has responsibility for Disposing the stream. This response to a slightly different scenario explains it well, https://stackoverflow.com/a/8791525/1690217 however as a general pattern only the scope that created the stream should be responsible for Disposing it.

  • The one exception to that is that if you need to access the stream outside of the creating method, then the parent class should hold a reference to it, in that scenario you should make the parent class implement IDisposable and make sure it cleans up any resources that might be hanging around.

The reason that you don't see this in documentation is often that the nuances around the timing for calling Dispose() are out of scope or will get lost in examples that are contrived for other purposes.

  • Specifically for examples where streams are passed via any mechanism and later used, as with DataPackage, it is too hard to show all of the orchestration code to cover the time in between storing the stream with DataPackage.SetData(...) and later accessing the stream via DataPackage.GetDataAsync(...)
  • Also consider the most common scenario for DataPackage where the consumer is not only in a different logical scope, but most likely in an entirely different application domain, to include all the code to cover when or if to call dispose should encompass the entire code base for 2 different applications.
Chris Schaller
  • 13,704
  • 3
  • 43
  • 81
  • I well understand the importance of object ownership and dispose etc. However the question I am asking here is a different one. When the stream goes into SetData and then back out with GetDataAsync, whether in the same process or a different process, is the same memory reference returned as went in? Or does the clipboard serialized the stream in SetData and then rebuild/deserialize it in GetDataAsync? Does it matter whether GetDataAsync is called from the same process or a different one? – sjb-sjb Mar 31 '21 at 16:32
  • This is a clipboard question, not about dispose generally. – sjb-sjb Mar 31 '21 at 16:33
  • Noting I cannot just put any old object in the clipboard, the ropes are severely restricted for SetData (but not for Properties) – sjb-sjb Mar 31 '21 at 16:35
  • The whole point is that you _CANT_ serialize a stream. You can either read it, or write to it, but you cannot persist it to say a file and later reload it. Basically, do not try to use the clipboard in this way, it is not designed to be used like that. Yes within your local context the object reference is persisted in some contexts, but it cannot be relied on especially across application domain boundaries. If your code works, well done, but please do not try to pass streams like this, `SetStorageItem` is the closest supported format to a stream. – Chris Schaller Mar 31 '21 at 23:00