avtoader
avtoader

Reputation: 63

Convert a String[] in C# to a Char *[] in C++

I am currently using Visual Studio 2010. This is my first time writing a wrapper for a C++ class. The class in C++ looks like this:

bool exampleCode(char* arrayOfStrings[], int number, char* regularString)

I created the header file which looks like:

bool exampleCode(array<String^>^ arrayOfStrings, int number, String^ regularString)

and the .cpp file class looks like:

bool exampleCode(array<String^>^ arrayOfStrings, int number, System::String^ regularString)

I figured out how to marshal the regularString data but I'm not sure how to convert the String array into a char*[]. Any help would be appreciated.

Upvotes: 0

Views: 1818

Answers (2)

Ben Voigt
Ben Voigt

Reputation: 283684

Here's an easily reusable version (complete with demonstration) that improves on @ildjarn's answer by reducing the number of allocations and improving locality. It also uses your choice of encoding such as UTF-8.

#include <iostream>
#include <vector>

#include <windows.h>
#include <vcclr.h>

using namespace System;

template <typename T>
struct advanced_marshal;

template <>
struct advanced_marshal<char*[]>
{
    char** get() { return &m_strings[0]; }

    advanced_marshal( array<System::String^>^ strings, UINT code_page = CP_ACP ) : m_strings(strings->Length)
    {
        if (int count = strings->Length) {
            int i;
            size_t total_length_estimate = count; // one NUL byte per string
            for( i = 0; i < count; ++i ) {
                total_length_estimate += strings[i]->Length * 4;
            }

            m_buffer.resize(total_length_estimate);
            auto tail = m_buffer.begin(), end = m_buffer.end();
            i = 0;
            do {
                m_strings[i] = &*tail;
                pin_ptr<const WCHAR> pwsz = PtrToStringChars(strings[i]);
                tail += 1 + WideCharToMultiByte(code_page, 0, pwsz, strings[i]->Length, &*tail , end - tail, nullptr, nullptr);

                ++i;
            } while (i < count);
        }
    }

    advanced_marshal(advanced_marshal<char*[]>&& other) { m_buffer.swap(other.m_buffer); m_strings.swap(other.m_strings); }

private:
    advanced_marshal(const advanced_marshal<char*[]>&); // = delete
    void operator=(const advanced_marshal<char*[]>&); // = delete

    std::vector<char> m_buffer;
    std::vector<char*> m_strings;
};

void print_some_strings( char* strings[], int num )
{
    for( int i = 0; i < num; ++i )
        std::cout << strings[i] << "\n";

    std::cin.get();
}

int main(array<System::String ^> ^args)
{
    print_some_strings(advanced_marshal<char*[]>(args).get(), args->Length);
    return 0;
}

Upvotes: 0

ildjarn
ildjarn

Reputation: 62975

The following is ideally efficient (minimal copying) and exception-safe:

#include <algorithm>
#include <memory>
#include <vector>

using System::IntPtr;
using System::String;
using System::Runtime::InteropServices::Marshal;

bool exampleCodeManaged(array<String^>^ arrayOfStrings, String^ regularString)
{
    auto deleter = [](char* p) { Marshal::FreeHGlobal(IntPtr(p)); };
    typedef std::unique_ptr<char[], decltype(deleter)> cstr_t;
    auto make_cstr = [&deleter](String^ s)
    {
        return cstr_t(
            static_cast<char*>(Marshal::StringToHGlobalAnsi(s).ToPointer()),
            deleter
        );
    };

    std::vector<cstr_t> cstrs;
    cstrs.reserve(arrayOfStrings->Length);
    for each (String^ s in arrayOfStrings)
        cstrs.push_back(make_cstr(s));

    std::vector<char*> ptrs;
    ptrs.reserve(cstrs.size());
    std::for_each(
        cstrs.begin(),
        cstrs.end(),
        [&ptrs](cstr_t& cstr) { ptrs.push_back(cstr.get()); }
    );

    auto reg_cstr = make_cstr(regularString);

    return exampleCode(ptrs.data(), arrayOfStrings->Length, reg_cstr.get());
}

(Note that number does not need to be passed in to the managed function as it can be deduced from the array's length.)

Alternatively, incorporating Ben Voigt's suggestion to use std::string instead of the Marshal class:

#include <algorithm>
#include <string>
#include <vector>
#include <msclr/marshal_cppstd.h>

using System::String;

bool exampleCodeManaged(array<String^>^ arrayOfStrings, String^ regularString)
{
    using msclr::interop::marshal_as;

    std::vector<std::string> strs;
    strs.reserve(arrayOfStrings->Length);
    for each (String^ s in arrayOfStrings)
        strs.push_back(marshal_as<std::string>(s));

    std::vector<char*> ptrs;
    ptrs.reserve(strs.size());
    std::for_each(
        strs.begin(),
        strs.end(),
        [&ptrs](std::string& s) { ptrs.push_back(&s[0]); }
    );

    auto reg = marshal_as<std::string>(regularString);

    return exampleCode(ptrs.data(), arrayOfStrings->Length, &reg[0]);
}

Upvotes: 1

Related Questions