【问题标题】:C++/CLI string interopC++/CLI 字符串互操作
【发布时间】:2012-08-24 15:04:17
【问题描述】:

我正在为原生 c++ 类开发 C++/CLI 包装器。 C++/CLI 包装器正在 WPF 应用程序中使用。我在尝试编组字符串时遇到了一个奇怪的问题。

WPF 应用程序将System::String 对象传递给我的包装器。然后包装器将 System::String 转换为原生类所期望的 std::string。这一切都很好,但是一旦我将字符串传递给本机对象,它就是空的。

这是一些相关的代码

WPF 事件处理程序 (C#)

private void tbInputConfig_TextChanged(object sender, TextChangedEventArgs e)
{
    _OrionBasicApp.ConfigTemplateFile = tbInputConfig.Text;
}

包装类中的属性 (C++/CLI)

void BasicApp::ConfigTemplateFile::set(String ^value)
{
    std::string val = marshal_as<std::string>(value);
    _NativeApp->setConfigTemplateFile(val);
}

本机代码 (C++)

void Basic_App::setConfigTemplateFile(const std::string& template_file)
{
   m_gParams.configTemplateFile = template_file;
}

因此,当我中断 WPF 应用程序并使用调试器进行跟踪时,String 对象看起来不错,std::string val 封送看起来不错,但 setConfigFile 函数中的参数 template_file 是一个空字符串。当我退出本机函数时,我可以看到 std::string val 变量看起来仍然很好。

我尝试过使用Marshal::StringToHGlobalAnsi 函数,它产生相同的结果。我尝试更改本机函数以获取字符串的副本而不是引用,这会产生有关无效内存块的异常(如果需要,我将发布确切的消息)。我试过在堆上分配字符串,没有运气。

现在最重要的是:本机代码是用 Microsoft Visual Studio 2008 编译的,而 wrapper + wpf 代码是用 2010 编译的。我希望这不是问题,因为我们将任何一个代码库迁移到都不容易另一个版本。

有什么想法吗?

更新

我能够将本机代码切换到 Visual Studio 2010,这确实解决了问题。 (为什么微软必须让我的生活变得如此困难?)虽然我确实构建了系统,但该项目的负责人给我带来了这个解决方案的主要麻烦(他担心它可能无法正确运行或者我们将不得不切换依赖库)。

那么,这个问题有没有不强制我切换 Visual Studio 版本的解决方案?

【问题讨论】:

  • 您的 C++ 是否被编译为 UNICODE、MBCS 或 ANSI ?
  • 好问题,我忘了添加此信息。本机代码使用 MultiByte 字符集。我已经在 Unicode 和 MultiByte 中尝试过 C++/CLI 层,这对我的问题没有影响
  • 你是如何包装原生代码的?本机代码是在一个 DLL 中,而包装器是在一个单独的 DLL 中,还是链接到一个 DLL 中?
  • 本机代码位于 Visual Studio 2009 中内置的单独 dll 中。C++/CLI 从 VS2010 编译为自己的程序集 dll。 WPF 应用程序也从 2010 年开始编译为自己的程序集 exe。理论上,这样每个组件都可以很容易地被不同的来源使用(例如,一些完全基于命令行的原生测试是为原生层编写的)

标签: c# wpf string interop c++-cli


【解决方案1】:

跨 DLL 边界传递 C++ 对象是个坏主意。 std::string 不是相同的类型、布局或实现,这会导致您的问题。

如果您通过 DLL 边界传递 BSTR(SysStringAlloc 等)或原始 char*,并在本机代码而不是包装器中转换为 std::string,您的问题就会消失。

此问题并非特定于 .NET 或 C++/CLI。任何将std::string 实例从任何其他编译器版本传递到您的本机 DLL 的尝试都将失败。

修复示例:

void BasicApp::ConfigTemplateFile::set(String ^value)
{
    std::string val = marshal_as<std::string>(value);
    _NativeApp->setConfigTemplateFile(val.c_str(), val.size());
}

void Basic_App::setConfigTemplateFile(const char* template_file_content, size_t template_file_length)
{
   m_gParams.configTemplateFile = std::string(template_file_content, template_file_length);
}

【讨论】:

  • 有趣的是,std::string 在版本之间发生了如此大的变化。我最终做了类似的解决方法,因为我似乎没有其他方法可以给你答案
  • @Max,这并不意味着std::string 完全改变了。允许不同的编译器以不兼容的方式编译完全相同的代码,只要在孤立的每个模块中满足整体行为。只有 POD(或 C++11 中的标准布局)类型可以在编译器之间安全地传递。
  • 但是,是的,特别是对于 std::string,有一些东西,比如小字符串优化,或者使用不同的分配器,会在编译器之间发生变化。
  • 对对对,这很有道理。我想我的(显然是错误的)假设是接近的 Visual Studio 版本是兼容的
  • @Max:啊,这是一个合理的问题,但绝对值得一问,而不是仅仅假设。现实情况更糟……即使是具有不同编译选项的相同编译器版本也可能不兼容。并且使用某些编译选项,您根本无法在 DLL 之间传递对象,即使它们都使用相同的编译器和选项(DLL 本地分配器)。
猜你喜欢
  • 1970-01-01
  • 2010-10-13
  • 1970-01-01
  • 1970-01-01
  • 2010-10-15
  • 2010-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多