9

I want to implement a C++ WinRT IBuffer that wraps a char* buffer, so i can use it with WinRT WriteAsync/ReadAsync operations that accept an IBuffer^ parameter.

EDIT 1 (clarification)

I want to avoid data copy.

José
  • 3,041
  • 8
  • 37
  • 58
  • If the function can take an Array also, this is an alternate (and maybe easier) route: http://stackoverflow.com/a/16645877/588476 – Wayne Uroda Feb 19 '15 at 23:04

2 Answers2

9

Mostly copied from http://jeremiahmorrill.wordpress.com/2012/05/11/http-winrt-client-for-c/ but adapted to directly wrap my own byte[]:

NativeBuffer.h:

#pragma once

#include <wrl.h>
#include <wrl/implements.h>
#include <windows.storage.streams.h>
#include <robuffer.h>
#include <vector>

// todo: namespace

class NativeBuffer : 
    public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::RuntimeClassType::WinRtClassicComMix>,
    ABI::Windows::Storage::Streams::IBuffer,
    Windows::Storage::Streams::IBufferByteAccess>
{
public:
    virtual ~NativeBuffer()
    {
    }

    STDMETHODIMP RuntimeClassInitialize(byte *buffer, UINT totalSize)
    {
        m_length = totalSize;
        m_buffer = buffer;

        return S_OK;
    }

    STDMETHODIMP Buffer(byte **value)
    {
        *value = m_buffer;

        return S_OK;
    }

    STDMETHODIMP get_Capacity(UINT32 *value)
    {
        *value = m_length;

        return S_OK;
    }

    STDMETHODIMP get_Length(UINT32 *value)
    {
        *value = m_length;

        return S_OK;
    }

    STDMETHODIMP put_Length(UINT32 value)
    {
        m_length = value;

        return S_OK;
    }

private:
    UINT32 m_length;
    byte *m_buffer;
};

To create the IBuffer:

Streams::IBuffer ^CreateNativeBuffer(LPVOID lpBuffer, DWORD nNumberOfBytes)
{
    Microsoft::WRL::ComPtr<NativeBuffer> nativeBuffer;
    Microsoft::WRL::Details::MakeAndInitialize<NativeBuffer>(&nativeBuffer, (byte *)lpBuffer, nNumberOfBytes);
    auto iinspectable = (IInspectable *)reinterpret_cast<IInspectable *>(nativeBuffer.Get());
    Streams::IBuffer ^buffer = reinterpret_cast<Streams::IBuffer ^>(iinspectable);

    return buffer;
}

And the call to read data (lpBuffer is the byte[]):

Streams::IBuffer ^buffer = CreateNativeBuffer(lpBuffer, nNumberOfbytes);
create_task(randomAccessStream->ReadAsync(buffer, (unsigned int)nNumberOfBytesToRead, Streams::InputStreamOptions::None)).wait();

I am not so sure if the ComPtr needs some cleanup, so any suggestions regarding memory management are welcome.

pfo
  • 334
  • 4
  • 5
  • `m_buffer = new byte[totalSize];` I see a `new`; I don't see any `delete`. It would be good to wrap this so that when the underlying buffer is destroyed, future attempts to access it via the `NativeBuffer` would fail (how to do this depends on how it is used). The `reinterpret_cast` of `nativeBuffer.Get()` to `IInspectable*` should be unnecessary. – James McNellis Aug 25 '12 at 20:55
  • Yes, sorry, that part should not have been there. I updated the answer to reflect this, using the COM constructor to set the byte[] as it should have been. – pfo Aug 26 '12 at 14:12
  • `(IInspectable *)reinterpret_cast(...);` is redundant. Can just get rid of the c-style cast because `reinterpret_cast` already does what the c-style cast does and the resulting type is already correct. – Merlyn Morgan-Graham Jan 01 '20 at 01:12
6

This should work:

// Windows::Storage::Streams::DataWriter
// Windows::Storage::Streams::IBuffer
// BYTE = unsigned char (could be char too)
BYTE input[1024] {};

DataWriter ^writer = ref new DataWriter();
writer->WriteBytes(Platform::ArrayReference<BYTE>(input, sizeof(input));
IBuffer ^buffer = writer->DetachBuffer();
Robin R
  • 1,037
  • 10
  • 6
  • 1
    The original question was edited to say that no copies (and presumably no allocations, and so forth) was the goal in mind, but I had a one-off need, I didn't care especially about performance, and there was no real need for a reusable utility class like the admittedly nice one implemented in the answer above, so for me, yours is the perfect answer - four lines of elegance, and even tighter in WinRT. – cycollins Aug 06 '20 at 03:17
  • One comment though. Isn't there supposed to be a writer->StoreAsync() after the WriteBytes? Or is that superfluous if all you need is the detached buffer? – cycollins Aug 06 '20 at 05:52
  • 1
    @cycollins Not in this case because the underlying store is a buffer. From the docs on StoreAsync(): "Commits data in the buffer to the output stream. This method should only be called when the DataWriter is writing into a stream; it will fail when the underlying store is a buffer." – Robin R Sep 10 '20 at 17:49
  • Thanks for the note. I found that eventually in the docs. I had been dealing with IBuffers and streams and so forth, doing encryption work and THEY require the StoreAsync and flush, and so forth. – cycollins Sep 18 '20 at 06:03