Search code examples
arraysc++-cliclrmanaged

Managed C++/CLI Array Conversion Error


I am using a mixed assembly with the following code:

#include "stdafx.h"

#pragma managed

using namespace System::Security::Cryptography;

array<System::Byte, 1> ^ComputeHashS(array<System::Byte, 1> ^Data) {

    RIPEMD160Managed^ r = gcnew RIPEMD160Managed();

    return r->ComputeHash(Data);

}

#pragma unmanaged

BYTE *DoWork(BYTE *Data) {
    BYTE *n = ComputeHashS(Data);
    return DoSomething(n, 20);
}

where DoSomething(array, len) is an UNMANAGED C++ function. However, I get the following error:

argument of type "BYTE *" is incompatible with parameter of type "cli::array<unsigned char, 1> ^".

I am new to C++/CLI and especially mixed-mode assemblies, so how can I resolve this error?


Solution

  • In order for unmanaged code to reliably access the managed data you will need to "pin" the managed data first; before trying to pass it into an unmanaged API. Use pin_ptr for this purpose (and it must be pinned for the duration of the unmanaged call required).

    The pin_ptr has the added advantage that it can be used for where a native pointer is needed. From MSDN;

    A pin_ptr represents a superset of the functionality of a native pointer. Therefore, anything that can be assigned to a native pointer can also be assigned to a pin_ptr. An interior pointer is permitted to perform the same set of operations as native pointers, including comparison and pointer arithmetic.

    An object or sub-object of a managed class can be pinned, in which case the common language runtime will not move it during garbage collection. The principal use of this is to pass a pointer to managed data as an actual parameter of an unmanaged function call. During a collection cycle, the runtime will inspect the metadata created for the pinning pointer and will not move the item it points to.

    A basic code sample to illustrate;

    int main()
    {
        array<System::Byte>^ a = gcnew array<System::Byte>(10);
        cli::pin_ptr<System::Byte> p = &a[0];
        unsigned char* b = p;
    }
    

    Given the managed/unmanaged function and data requirements here, it may be more feasible to change the managed function ComputeHashS() to work with unmanaged data (mixed mode) and allow it to perform the appropriate conversions. The conversion/marshalling between System::Byte and BYTE here works as expected. Note; the pin_ptr here is not specifically required with these conversions since the unmanaged code never accesses the data in managed array (it is left in as commented for the more general case).

    cli::array<Byte>^ marshal_array(std::vector<BYTE> const& src)
    {
        cli::array<Byte>^ result = gcnew cli::array<Byte>((int)src.size());
        if (src.size()) {
            //cli::pin_ptr<Byte> pinned = &result[0];
            for (std::size_t i = 0; i < src.size(); ++i) {
                result[(int)i] = src[i];
            }
        }
        return result;
    }
    
    std::vector<BYTE> marshal_array(cli::array<Byte>^ const& src)
    {
        std::vector<BYTE> result(src->Length);
        if (src->Length) {
            //cli::pin_ptr<Byte> pinned = &src[0];
            for (int i = 0; i < src->Length; ++i) {
                result[(std::size_t)i] = src[i];
            }
        }
        return result;
    }
    
    void ComputeHashS(std::vector<BYTE> in, std::vector<BYTE>& out)
    {
        array<System::Byte, 1>^ Data = marshal_array(in);
        RIPEMD160Managed^ r = gcnew RIPEMD160Managed();
        array<System::Byte, 1>^ result = r->ComputeHash(Data);
        out = marshal_array(result);
    }