将C++/CLI字符串数组转换为本地C++ char**

4
在C++/CLI中,将字符串数组转换为本地char**的最有效方法是什么?
我正在执行以下操作:
array<String^>^ tokenArray = gcnew array<String^> {"TokenONE", "TokenTWO"};
int numTokens = tokenArray->Length;
char** ptr = new char* [numTokens];
for(int i = 0; i < numTokens; i++)
    {
        // See: https://dev59.com/Z1jUa4cB1Zd3GeqPWPVG
        array<Byte>^ encodedBytes = Text::Encoding::UTF8->GetBytes(tokenArray[i]);
        pin_ptr<Byte> pinnedBytes = &encodedBytes[0];
        ptr[i] = reinterpret_cast<char*>(pinnedBytes);
    }
int myResult = someNativeFunction(ptr, numTokens);
delete ptr;
// ...

有什么需要改进的吗?从内存管理的角度来看,这个是否可以?如果需要,我可以更改someNativeFunction的参数。

谢谢。


3
“可能需要改进的是”——完全不使用手动内存管理和指针,而是在本地端使用 std::string - Kerrek SB
5
一个主要问题是你的 pin_ptr 在使用前失效。 - Dark Falcon
@Adam:那么你可以编写一个清晰定义的接口来调用那个遗留函数 :-) - Kerrek SB
1
@OG:不,这并没有改变任何事情——你的 pin_ptr 在每次 for 循环迭代时都会超出作用域,从而取消固定它们的内容,因此在调用 someNativeFunction 时,ptr 的每个元素都指向随机数据。 - ildjarn
@ildjarn:好的,我明白了。有没有不超出作用域的方法?我尝试创建一个托管数组,使用array<pin_ptr<Byte>>^来保存我在每次迭代中固定的指针,但是这是不允许的。 - OG Dude
显示剩余8条评论
1个回答

4
除了固定指针在传递给someNativeFunction()之前就超出作用域的问题外,如果您使用MSVC2008或更新版本,则可以简化代码以获得更好的清晰度。有关如何将单个字符串转换为数组的信息,请参见此页面编辑: 如果需要ANSI字符串const char*,则必须进行复制,因为.NET字符串是Unicode(UTF-16)。 在MSVC2008及更高版本中,您的代码可能如下所示:
#include <msclr/marshal.h>
using namespace msclr::interop;

marshal_context context;
array<String^>^ tokenArray = gcnew array<String^> {"TokenONE", "TokenTWO"};
char** tokensAsAnsi = new char* [tokenArray->Length];

for(int i = 0; i < tokenArray->Length; i++)
{
    tokensAsAnsi[i] = context.marshal_as<const char*>(tokenArray[i]);
}
int myResult = someNativeFunction(ptr, tokensAsAnsi);

// The marshalled results are freed when context goes out of scope
delete[] tokensAsAnsi;    // Please note you must use delete[] here!

这段代码与您的示例代码类似,但不需要指针固定和 reinterpret_cast 转换。

如果您想要处理在 someNativeFunction() 中使用的宽字符串 const wchar_t*,您可以直接使用(固定)内部数据。但是,您必须确保指针保持固定直到 someNativeFunction() 返回,否则可能会对垃圾回收性能产生负面影响,正如评论中所指出的那样。

如果您将要传递很多字符串并且性能至关重要,您可以在将所有内容传递给 someNativeFunction() 之前将转换过程拆分到几个线程中。在执行此操作之前,我建议您对应用程序进行分析,以确定是否真的需要优化转换或者应该将精力集中在其他方面。

编辑 #2:

为了获得以 UTF-8 编码的本地字符串,您可以使用修改后的代码:

array<String^>^ tokenArray = gcnew array<String^> {"TokenONE", "TokenTWO"};
char** tokensAsUtf8 = new char* [tokenArray->Length];

for(int i = 0; i < tokenArray->Length; i++)
{
    array<Byte>^ encodedBytes = Text::Encoding::UTF8->GetBytes(tokenArray[i]);

    // Probably just using [0] is fine here
    pin_ptr<Byte> pinnedBytes = &encodedBytes[encodedBytes->GetLowerBound(0)];

    tokensAsUtf8[i] = new char[encodedBytes->Length + 1]; 
    memcpy(
        tokensAsUtf8[i], 
        reinterpret_cast<char*>(pinnedBytes),
        encodedBytes->Length
        );

    // NULL-terminate the native string
    tokensAsUtf8[i][encodedBytes->Length] = '\0'; 

}
int myResult = someNativeFunction(ptr, tokensAsAnsi);

for(int i = 0; i < tokenArray->Length; i++) delete[] tokensAsUtf8[i];
delete[] tokensAsUtf8;    

如果您关注速度,可以预先分配一个大缓冲区用于本地字符串(如果您知道只有有限的数量),或使用池存储。

编辑#3:(OG Dude) 修正了一些小错别字。


1
使用 gcnew marshal_context 会让人感到尴尬 -- 使用堆栈语义,不需要 delete - ildjarn
1
@OG:无论是使用封送还是固定数据,哪种方式更便宜或更昂贵取决于应用程序的总分配率;如果已经有大量的分配变化,固定托管对象可能会严重阻碍GC,因此在某些情况下,实际上封送更便宜。 - ildjarn
1
如果你想让 const char* 字符串编码为 UTF-8,那么你的方法是正确的。你只需要将字符复制到另一个缓冲区或者保持数据固定(例如通过将固定指针放入容器中),直到你完成使用它们为止。 - Martin Gunia
@Martin Gunia:好的。关于指针作用域问题,我尝试将固定指针存储在托管数组中(请参见原始帖子的评论部分),但是这是不允许的。哪种容器可以做到? - OG Dude
1
是的,这就是我会做的,只将指针固定到memcpy到自己的缓冲区为止。链接的文章处理了另一个方向,但你可以交换memcpy参数,它们现在都是本地缓冲区。我已经在答案中添加了代码。 - Martin Gunia
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接