As others have mentioned in the comments, you cannot pass a raw array this way. Minimally, you have to copy the byte array into a SAFEARRAY of bytes (SAFEARRAY(BYTE) in IDL). I just tested the code below with a custom proxy/stub (compiled from the P/S code generated by midl.exe), and I was able to get my data across the wire.
If you want to use a standard P/S such as PSDispatch ({00020420-0000-0000-C000-000000000046}) or PSOAInterface ({00020424-0000-0000-C000-000000000046}), or if you want to use VBA as a client, then you may have to convert this to a SAFEARRAY(VARIANT) and/or put the resulting safearray into a VARIANT. Try the simplest approach of just using SAFEARRAY(BYTE) first, because that's the one with the least overhead. (A SAFEARRAY(VARIANT) uses 16x more memory than a SAFEARRAY(BYTE) because a VARIANT is 16 bytes long. And you would have to manually convert each byte to/from a VARIANT, as opposed to the simple memcpy calls show below.)
Packing the byte array into a SAFEARRAY: (Note that this copies the byte array into the SAFEARRAY. You could muck around with the internals of the SAFEARRAY struct to prevent the copy, but you'd be doing things in a non-standard way.)
/// <summary>Packs an array of bytes into a SAFEARRAY.</summary>
/// <param name="count">The number of bytes.</param>
/// <param name="pData">A reference to the byte array. Not read if count is 0.</param>
/// <param name="pResult">Receives the packed LPSAFEARRAY on success.</param>
HRESULT PackBytes(ULONG count, const BYTE* pData, /*[ref]*/ LPSAFEARRAY* pResult)
{
// initialize output parameters
*pResult = NULL;
// describe the boundaries of the safearray (1 dimension of the specified length, starting at standard index 1)
SAFEARRAYBOUND bound{ count, 1 };
// create the safearray
LPSAFEARRAY safearray = SafeArrayCreate(VT_UI1, 1, &bound);
if (!safearray)
return E_OUTOFMEMORY;
// when there is actually data...
if (count > 0)
{
// begin accessing the safearray data
BYTE* safearrayData;
HRESULT hr = SafeArrayAccessData(safearray, reinterpret_cast<LPVOID*>(&safearrayData));
if (FAILED(hr))
{
SafeArrayDestroy(safearray);
return hr;
}
// copy the data into the safearray
memcpy(safearrayData, pData, count);
// finish accessing the safearray data
hr = SafeArrayUnaccessData(safearray);
if (FAILED(hr))
{
SafeArrayDestroy(safearray);
return hr;
}
}
// set output parameters
*pResult = safearray;
// success
return S_OK;
}
Unpacking the byte array from the SAFEARRAY: (Note that this copies the byte array from the SAFEARRAY. You could muck around with the internals of the SAFEARRAY struct to prevent the copy, but you'd be doing things in a non-standard way. Also, you could choose to use the data directly from the SAFEARRAY by putting the consuming code between SafeArrayAccessData and SafeArrayUnaccessData.)
/// <summary>Unpacks an array of bytes from a SAFEARRAY.</summary>
/// <param name="safearray">The source SAFEARRAY.</param>
/// <param name="pCount">A pointer to a ULONG that will receive the number of bytes.</param>
/// <param name="ppData">A pointer to a BYTE* that will receive a reference to the new byte array.
/// This array must be deallocated (delete []) when the caller is done with it.</param>
HRESULT UnpackBytes(LPSAFEARRAY safearray, /*[out]*/ ULONG* pCount, /*[out]*/ BYTE** ppData)
{
// initialize output parameters
*pCount = 0;
*ppData = NULL;
// validate the safearray element type (must be VT_UI1)
VARTYPE vartype;
HRESULT hr = SafeArrayGetVartype(safearray, &vartype);
if (FAILED(hr))
return hr;
if (vartype != VT_UI1)
return E_INVALIDARG;
// validate the number of dimensions (must be 1)
UINT dim = SafeArrayGetDim(safearray);
if (dim != 1)
return E_INVALIDARG;
// get the lower bound of dimension 1
LONG lBound;
hr = SafeArrayGetLBound(safearray, 1, &lBound);
if (FAILED(hr))
return hr;
// get the upper bound of dimension 1
LONG uBound;
hr = SafeArrayGetUBound(safearray, 1, &uBound);
if (FAILED(hr))
return hr;
// if the upper bound is less than the lower bound, it's an empty array
if (uBound < lBound)
return S_OK;
// calculate the count of the bytes
ULONG count = uBound - lBound + 1;
// create buffer
BYTE* pData = new BYTE[count];
if (!pData)
return E_OUTOFMEMORY;
// begin accessing the safearray data
BYTE* safearrayData;
hr = SafeArrayAccessData(safearray, reinterpret_cast<LPVOID*>(&safearrayData));
if (FAILED(hr))
{
delete[] pData;
return hr;
}
// copy the data
memcpy(pData, safearrayData, count);
// finish accessing the safearray data
hr = SafeArrayUnaccessData(safearray);
if (FAILED(hr))
{
delete[] pData;
return hr;
}
// set output parameters
*pCount = count;
*ppData = pData;
// success
return S_OK;
}
SAFEARRAYinstead, as @RamblinRose correctly notes. Alternatively, instead of a structure, implement a COM object wrapping its fields, and return its interface pointer.datawould then be one of the properties on that object.