【问题标题】:Convert a String[] in C# to a Char *[] in C++将 C# 中的 String[] 转换为 C++ 中的 Char *[]
【发布时间】:2012-01-11 18:23:55
【问题描述】:

我目前正在使用 Visual Studio 2010。这是我第一次为 C++ 类编写包装器。 C++ 中的类如下所示:

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

我创建的头文件看起来像:

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

.cpp 文件类看起来像:

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

我想出了如何编组 regularString 数据,但我不确定如何将 String 数组转换为 char*[]。任何帮助将不胜感激。

【问题讨论】:

  • 本机函数是清理它传递的内存还是将其留给调用者?
  • 我相信本机函数会清理它传递的内存,但我不是 100%,因为我是 C++/CLI 编程新手。
  • 好的,那么接下来的问题是如何原生函数清理内存? free? delete[]? CoTaskMemFree? LocalFree? GlobalFree?
  • 其实没关系,native函数不会删除内存。看来这是由调用者决定的。对此感到抱歉。
  • 我发布答案之前的最后澄清:您是否肯定 exampleCode 接受 char* 参数而不是 const char*/char const* 参数? const 的可能存在极大地改变了答案。此外,如果const 真的不存在,那么本机函数是否可以修改其参数,或者假定不变性?

标签: .net visual-studio-2010 visual-c++ c++-cli wrapper


【解决方案1】:

以下是理想的高效(最少复制)和异常安全:

#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());
}

(请注意,number 不需要传递给托管函数,因为它可以从数组的长度中推断出来。)

或者,结合 Ben Voigt 的建议,使用 std::string 而不是 Marshal 类:

#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]);
}

【讨论】:

  • 我不确定StringToHGlobalAnsi 是否“非常有效”。它确实有效,但我原则上拒绝使用它,因为它的名称具有很强的误导性——它实际上并没有创建一个 HGLOBAL 并且结果不能传递给 GlobalFree (这是 @ 的常用释放函数987654330@s)。
  • @Ben :我也不确定,但简单的替代方法是使用marshal_as&lt;std::string&gt;marshal_as&lt;char const*&gt;,除非使用const_cast&lt;&gt;,否则需要额外的数据副本,考虑到所有因素,我宁愿避免const_cast&lt;&gt;
  • std::string 完全可用,只需执行&amp;s[0]。但我想我更喜欢单个vector&lt;char&gt;,通过调用Encoding::GetBytes 填充(端到端打包所有字符串)。一次分配,自动免费,没有自定义删除器。
  • 在尝试编写“首选”版本时,我发现:(1) Encoding::GetBytes 只接受 pin_ptr,而不接受 interior_ptr,并且 (2) 它复制所有数据在进出的路上。所以决定毕竟不使用它。
【解决方案2】:

这是一个易于重复使用的版本(带有演示),它通过减少分配数量和改善局部性来改进 @ildjarn 的答案。它还使用您选择的编码,例如 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;
}

【讨论】:

    猜你喜欢
    • 2014-11-19
    • 1970-01-01
    • 1970-01-01
    • 2016-12-28
    • 2011-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多