-2

I'm setting my first steps in COM communication. I have an application, written in C#, using a byte array, and that is working fine.
When I try to do the exact same thing in C++, it does not work.

I have taken two Wireshark captures, where the differences can be seen clearly.

This is a screenshot of the good situation:
Screenshot_Good_Situation

This is a screenshot of the bad situation:
Screenshot_Bad_Situation

You can clearly see the two cccc bytes, added to the C++ data.

For your information: in the C# application I'm using the following source code to convert a byte array into an object for being sent:

byte[] requestCounters = new byte[] { 0xFF, 0xFF };
dataCounters = (object) requestCounters;

In the C++ application (the failing one), it goes as follows:

BYTE bData_Counters[] = {0xFF, 0xFF};
_variant_t data_Counters = from_ByteArray(bData_Counters); // new byte[] { 0xFF, 0xFF };

The from_ByteArray() function looks as follows:

_variant_t from_ByteArray(BYTE input[])
{
    long dataCount = sizeof(input) / sizeof(BYTE);

    // Create a SAFEARRAY to hold the bytes
    SAFEARRAYBOUND sabound;
    sabound.lLbound = 0;
    sabound.cElements = dataCount;

    SAFEARRAY* psa = SafeArrayCreate(VT_UI1, 1, &sabound); // VT_UI1 = unsigned byte

    // Copy the bytes into the SAFEARRAY
    void* pArrayData = nullptr;
    SafeArrayAccessData(psa, &pArrayData);
    memcpy(pArrayData, input, dataCount);
    SafeArrayUnaccessData(psa);

    // Wrap the SAFEARRAY in a VARIANT (via _variant_t)
    _variant_t data;
    data.vt = VT_ARRAY | VT_UI1;
    data.parray = psa;
    return data;
}

Why is the C++ way adding two bytes to the data?
Does anybody have an idea?

5
  • 3
    C style arrays, like your BYTE input[] have many tricky and misleading properties. That's why your long dataCount formula is wrong. Consider using a C++ solution, such as std::span<Byte>. Commented Nov 5 at 15:44
  • 3
    sizeof(input) / sizeof(BYTE) is sizeof(BYTE*) / sizeof(BYTE), a constant value... Commented Nov 5 at 15:51
  • 5
    don't use sizeof to determine the size of an array. When it would work then std::size does too, and when it doesnt it does silently the wrong thing while std::size creates a lovely error Commented Nov 5 at 15:55
  • 3
    Don't use _variant_t if you want to give the VARIANT back as the smart class will destroy it automatically when out of scope, just use VARIANT or do data.Detach(). PS: use one liner SafeArrayCreateVectorEx learn.microsoft.com/en-us/windows/win32/api/oleauto/… it's much easier to use for what you need to do Commented Nov 5 at 16:02
  • 1
    Another good article on safearrays : learn.microsoft.com/en-us/archive/msdn-magazine/2017/march/…. Commented Nov 5 at 16:23

2 Answers 2

0

In general I would stay away from using memcpy when copying data from C++ to the safearray (you cannot be sure C++ and COM use the same memory layout). And I would use ATL's helper classes for saferarray . Something like :

inline auto make_safe_array(const std::vector<int>& vector)
{
    ATL::CComSafeArray<int> sa;
    for (const auto& value : vector)
    {
       sa.Add(value);
    }
    return sa;
}

When returning the data to a COM client make sure to use Detach

Sign up to request clarification or add additional context in comments.

5 Comments

Isn't it more efficient to use CComSafeArray::Resize and then assign the elements (to avoid several reallocs if the vector is large) ?
At this moment I focused on correctness over efficiency, but yes probably setting a size first might help. Though honestly I never dug that deep into the implementation details of ATL::CComSafeArray , it for sure uses CoTaskMemAlloc at some point (COM data is not in the C++ heap)
You don't need ATL to program SAFEARRAYs especially in simple cases like this. Its license is bound to Visual Studio, and altough it's still supported, it's pretty frozen, Microsoft doesn't update it anymore. Also in this case, you will loop through a whole byte array for constructing a SAFEARRAY from it which brings bad performance if the array is big.
IMO Safearrays are clumsy anyway. If data is not too big, extra copying is not too expensive wrt actual RPC calls. And ofcourse you don't need ATL, but if you have it is much more convenient (RAII) and it might not be supported anymore (as in no new features) the same is kind of true for COM as a whole. But what's there will not suddenly stop working
"you cannot be sure C++ and COM use the same memory layout" - Either one is based on C, and C is the closest thing we have to an ABI. "COM data is not in the C++ heap" - COM is fundamentally based on interfaces. It places absolutely no restrictions on where implementations back their resources. "If data is not too big, extra copying is not too expensive" - True. But taking a heap allocation to construct a std::vector to carry a 2-byte array across a function call interface is unduly expensive.
-1

C++ is a minefield. Just because you use array syntax doesn't necessarily mean that the compiler will give you array semantics.

When you write

_variant_t from_ByteArray(BYTE input[])

input is a BYTE* (as opposed to an array of BYTEs). Consequently, sizeof(input) evaluates to the size of a pointer rather than the extent of the array it points to. The length information is irrecoverably lost in the process.

To fix that, there are several options to pass data of variable length:

  • Pointer/size pair:

    _variant_t from_ByteArray(BYTE const* input, size_t len)
    

    This is the conventional way in C to express arrays of arbitrary length in a function call interface. The responsibility of passing matching values lies with the caller.

  • Fixed-size array:

    _variant_t from_ByteArray(BYTE const (&input)[2])
    

    That's better by removing a dependent parameter, but it also limits the utility to arrays of a specific length. C++ supports function overloading, so you could provide functions for different array sizes. But that doesn't scale very well.

  • Function template:

    template<size_t N>
    _variant_t from_ByteArray(BYTE const (&input)[N])
    

    This template solves the scalability issue: It delegates stamping out functions for any given array size to the compiler.

Either of the above grants you access to the element count of the array (sz, 2, N, respectively) to properly calculate the count argument passed to the memcpy call.


As an aside: The value 0xCC is used as a fill pattern by Microsoft's CRT runtime (in debug mode) to mark uninitialized memory.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.