Search code examples
c++windowscomarraybufferiwebbrowser2

IWebbrowser2: Using a Uint8array (filling without looping)


I can create a Uint8array in an IWebbrowser2 window:

IHTMLWindow2 window = ...;
DISPID dispid_uint8array = ...;

VARIANT self;
self.vt = VT_NULL;

VARIANT length;
length.vt = VT_I4;
length.lVal = 100;

VARIANT args[2] = { self, length };
DISPID named_args[1] = { DISPID_THIS };

DISPPARAMS params;
params.rgvarg = args;
params.rgdispidNamedArgs = named_args;
params.cArgs = 2;
params.cNamedArgs = 1;

VARIANT result;
result.vt = VT_EMPTY; 
HRESULT hr = container->Invoke(
    dispid_uint8array, IID_NULL, LOCALE_USER_DEFAULT,
    DISPATCH_METHOD, &params, &result, nullptr, nullptr
);

assert(hr == S_OK);
assert(result.vt == VT_DISPATCH);

IDispatch my_new_uint8array = result.pdispVal;

Now I can set items fields of my_new_uint8array using IDispatch::Invoke(..., DISPATCH_PROPERTYPUT, ...) in a loop.

But isn't there a proper proper interface to fill the whole buffer with just one call? E.g. can I somehow retrieve the contained buffer?

Is there a definition of the interface somewhere, something like IUint8Array?


Solution

  • I came up with an incredibly ugly hack: Using the ArrayBuffer of a <canvas>. I'll only post the pseudo code, because the full C++, including error checking, unwrapping etc, is just too long:

    Prelude:

    • FEATURE_BROWSER_EMULATION was set to 11001
    • IWebBrowser2::get_ReadyState returns READYSTATE_COMPLETE
    • IHTMLDocument6::get_documentMode returns 11
    • IHTMLDocument5::get_compatMode returns "CSS1Compat"
    • IHTMLDocument2 *doc and IHTMLWindow2 *win are set.
    • I use simple strings in my pseudo code, but you have to use BSTRs of course!
    • You might have to queryInterface once or twice, so that the results fit.
    • Don't ignore errors!

    How to get a "Uint8Array":

    ULONG len_in_dwords = (LENGTH_I_WANT + 3) / 4;
    
    IHTMLCanvasElement *canvas = doc->createElement("canvas");
    ICanvasRenderingContext2D *context = canvas->getContext("2d");
    ICanvasPixelArrayData *array_data = context->createImageData(len_in_dwords, 1);
    
    // use this variable for interfaces that accept an ArrayBuffer
    IDispatch *array_buffer = Get property "buffer" of array_data;
    
    // use this variable to edit the content:
    BYTE *byte_buffer;
    ULONG buffer_length_in_bytes;
    array_data->GetBufferPointer(&byte_buffer, &buffer_length_in_bytes);
    
    // no need for that anymore:
    canvas->Release();
    context->Release();
    array_data->Release();
    

    The ArrayBuffer array_buffer has always a size divisible by four. That works for me, but might not work for other use cases. You can use array_buffer's method slice(0, LENGTH_I_WANT) to remove the extra bytes after memcpy'ing to byte_buffer.