【问题标题】:Conversion of C++/CLI array of strings to native C++ char**将 C++/CLI 字符串数组转换为本机 C++ char**
【发布时间】:2011-08-25 13:14:19
【问题描述】:

在 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: http://stackoverflow.com/questions/6596242/
        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 的参数。

谢谢。

【问题讨论】:

  • “可以改进的地方”——可能根本不使用手动内存管理和指针,而是在本机端使用 std::strings。
  • 一个主要问题是您的pin_ptrs 在您使用它们之前就超出了范围。
  • 我对你为什么需要 tokenArray 有点困惑。既然你不使用它,为什么不直接让 ptr 指向 C 风格的字符串呢?即使你在使用它,既然它看起来不是动态的,而且很小,那你为什么不单独创建ptr呢?
  • 重申 DarkFalcon 所说的,您将 someNativeFunction 指针传递给未初始化的内存,这将导致内存损坏。
  • @OG :不,这不会改变任何事情——你的pin_ptrs 在for 循环的每次迭代中都超出范围,取消固定它们的内容,所以当你打电话的时候someNativeFunction ptr 的每个元素都指向随机数据。

标签: c++ interop c++-cli


【解决方案1】:

除了固定指针在传递给someNativeFunction() 之前超出范围的问题外,还可以简化代码以提高清晰度,尤其是在您使用 MSVC2008 或更新版本时。有关如何转换单个字符串的信息,请参阅this page(扩展为数组应该很简单)。

已编辑:

如果您需要 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-ing。

如果您愿意处理someNativeFunction() 中的宽字符串const wchar_t*,则可以直接使用(固定的)内部数据,但是,您必须确保指针保持固定,直到someNativeFunction() 返回哪个,正如 cmets 中所指出的,可能会对 GC 性能产生负面影响。

如果您要编组 许多 字符串并且性能是最重要的问题,您可以 在将所有内容传递给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) 刚刚修正了一些小错别字。

【讨论】:

  • gcnew marshal_context 令人畏缩——使用堆栈语义,不需要delete
  • @ildjarn:确实如此。我刚刚从支持页面复制了它。
  • @OG :封送处理是否比固定数据更便宜或更昂贵取决于您的应用程序的整体分配率;如果已经有很多分配流失,固定托管对象会严重阻碍 GC,因此在某些情况下,封送实际上更便宜。
  • 是的,如果您希望 const char* 字符串采用 UTF-8 编码,那么您的方式很好。您只需将字符复制到另一个缓冲区或保持数据固定(例如,通过将固定指针放置到容器中)直到您完成它们。
  • 是的,这就是我要做的,仅将指针固定到 memcpy-ed 到自己的缓冲区。链接的 atricle 处理另一个方向,但您可以交换 memcpy 参数,无论如何它们现在都是本机缓冲区。我已经在答案中添加了代码。
猜你喜欢
  • 1970-01-01
  • 2016-04-20
  • 1970-01-01
  • 2016-08-13
  • 2018-09-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-16
相关资源
最近更新 更多