Question #3: So now what the heck do I do with these buffer objects, when neither the Buffer class and the IBuffer interface have any methods?

Answer: I totally agree that this one stumped me for a while, and is one of the reasons I decided to write this lengthy post!

As in the previous question, straightforward methods like readAsync and writeAsync provide this odd duck called a Buffer instead of just giving us a byte array. The problem is, when you look at the reference docs for Buffer and IBuffer, all you see are two properties: length and capacity. “That’s all well and good,” you say (and I have!), “but how the hell do you get at the data itself?” After all, if you just opened a file and read its contents from an input stream into a buffer, that data exists somewhere, but this buffer thing is just a black box as far as you can see!

Indeed, looking at the Buffer itself it’s hard to understand how one even gets created in the first place, since the object itself has no methods for store data within it (the constructor takes only a capacity argument). This gets confusing when you need to provide a buffer to some API, like ProximityDevice.PublishBinaryMessage (for near-field communications). You need one, but can’t seem to create one.

To make things clear, first understand that a buffer is just an abstraction for an untyped byte array, and it exists because marshaling byte arrays between different layers in Windows and the language projections can get tricky. Having an abstract class makes such marshaling easier to work with in the API.

Next, there are two ways to create a new buffer with real data (you can always create an empty one with new, as you’d need when calling FileRandomAccessStream.readAsync). Some methods must exist, of course, because other WinRT APIs provide buffers with data themselves.

One way is to use the Windows.Storage.Streams.DataWriter class. You can create one of these with new, then use its Write* methods to populate it with whatever you want (including writeBytes, which takes an array). Once you’ve written those contents, call DataWriter.detachBuffer and you have your populated Buffer object.

The other way is super-secret. You have to look way down in Windows.Security.Cryptography.–wait for it!–CryptographicBuffer.createFromByteArray. This API has nothing to do with cryptography per se and simply creates a new buffer with a byte array you provide. This is simpler than using DataWriter if you have a byte array; DataWriter is better, though, if you have data in any other form, such as a string.

 

So how, then, do you get data out of a buffer? For that you use the Windows.Storage.Streams.DataReader class. You create an instance of DataReader with the static method Windows.Storage.Streams.DataReader.fromBuffer, after which you can call methods like readBytes, readString, and so forth. (If you use new with DataReader, you provide an IInputStream argument instead–with buffers you have to use the static method. The reason for this is that JavaScript cannot differentiate overloaded methods by arity only (number of arguments), thus the designers of WinRT have had to make these kinds of oddball choices here and there where the more common usage employs the constructor and a static method is used for the less common option.)

In short, the methods that you would normally expect to find on a class like Buffer are found instead within DataReader and DataWriter, because these reader/writer classes also work with streams. That is, instead of having completely separate abstractions for streams and byte arrays with their own read/write methods for different data types, those methods are centralized within the DataReader and DataWriter objects that are themselves initialized with either a stream or a buffer. DataReader and DataWriter also take care of the details of closing streams for you when appropriate.

 

In the end, this reduces the overall API surface area once you understand how they relate.

 


Comments are closed