如何在C++中将char*缓冲区包装成WinRT IBuffer

9
我希望实现一个C++ WinRT的IBuffer,将char*缓冲区包装起来,这样我就可以将其与接受IBuffer^参数的WinRT WriteAsync/ReadAsync操作一起使用。
注1:我希望避免数据复制。

如果该函数也可以接受数组作为参数,那么这是一种替代(也许更容易)的方法:https://dev59.com/JWct5IYBdhLWcg3wvf_K#16645877 - Wayne Uroda
2个回答

9

大部分内容来自http://jeremiahmorrill.wordpress.com/2012/05/11/http-winrt-client-for-c/,但经过改编以直接包装我的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;
};

创建 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;
}

读取数据的调用(lpBuffer是字节数组):

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

我不确定ComPtr是否需要清理,因此欢迎提出与内存管理相关的任何建议。


m_buffer = new byte[totalSize]; 我看到了 new,但没有看到任何 delete。最好将其封装起来,这样当底层缓冲区被销毁时,通过 NativeBuffer 访问它的未来尝试将失败(如何实现取决于它的使用方式)。nativeBuffer.Get()IInspectable*reinterpret_cast 应该是不必要的。 - James McNellis
是的,抱歉,那部分不应该存在。我更新了答案以反映这一点,使用COM构造函数将byte[]设置为它应该的样子。 - pfo
(IInspectable *)reinterpret_cast<IInspectable *>(...); 是多余的。可以摆脱 C 风格的转换,因为 reinterpret_cast 已经执行了 C 风格的转换所做的操作,并且结果类型已经正确。 - Merlyn Morgan-Graham

6
这应该可以运作:

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

1
原始问题已被编辑为不需要任何副本(可能也没有分配等),但我有一个一次性的需求,我并不特别关心性能,并且没有真正需要像上面回答中实现的那样可重用的实用程序类,所以对我来说,你的答案是完美的 - 四行代码的优雅,甚至在WinRT中更加紧凑。 - cycollins
只有一个评论。在WriteBytes之后不应该有writer->StoreAsync()吗?或者如果你只需要分离的缓冲区,那是不是多余的? - cycollins
1
@cycollins 不适用于此情况,因为底层存储是一个缓冲区。从 StoreAsync() 文档中可以看到:"将缓冲区内的数据提交到输出流。仅当 DataWriter 正在写入流时才应调用此方法;如果底层存储是缓冲区,则会失败。" - Robin R
谢谢留言。我在文档中最终找到了答案。我一直在处理IBuffers和流等加密工作,它们需要StoreAsync和flush等操作。 - cycollins

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接