从 C++/CLI 应用程序将 STL 字符串传递给 C++ DLL

Posted

技术标签:

【中文标题】从 C++/CLI 应用程序将 STL 字符串传递给 C++ DLL【英文标题】:Passing an STL string to a C++ DLL from a C++/CLI app 【发布时间】:2013-02-20 03:02:49 【问题描述】:

我有一个使用 OpenCV 的 C++/CLI 项目。我自己在 VS 2010 中编译了这个版本的 OpenCV,我可以在非托管项目中毫无问题地使用它——当我尝试在托管项目中使用它时,问题就开始了。

感兴趣的函数是cv::imread(std::string&, int)。简单地从托管模块调用它根本不起作用,在接收端产生 。我有点期待它。毕竟,托管代码有自己的std::string 实现。

当我创建一个单独的 C++ 文件、从其模块中删除 CLI 支持并将我的代码放入其中时,事情变得更有趣了。现在,imread 得到了一个有效的指针,但它的内容被打乱了。显然,我传递的 string 包含偏移 4 个字节的字符串指针,但它希望它位于 0 偏移处。

非托管模块使用与 OpenCV 相同的 CRT DLL,并将所有选项设置为适合正常 OpenCV 使用的值。为什么会有不同的string 布局?我迷路了。

示例代码:

#include <opencv/cv.h>
#include <opencv/highgui.h>

#include <string>

using namespace cv;
using namespace std;

void Run()

    string path("C:\\Users\\Don Reba\\Pictures\\Merlin 1D.jpg");

    Mat image(imread(path, CV_LOAD_IMAGE_GRAYSCALE));
    imwrite("image.jpg", image);

【问题讨论】:

嗯,您没有显示任何托管代码,例如您在文件路径中传递的位置。也许您可以通过传入 const char * 而不是字符串来回避问题,并将其留给调用 imread 时调用的字符串 ctor ? 代码 sn-p 用于我的非托管模块。当调用函数Run 时,会发生托管-> 本机转换。不幸的是,imread 需要一个字符串引用,据我所知,没有办法在我这边构建字符串。此外,这不是唯一的情况——OpenCV 始终使用字符串和向量。 【参考方案1】:

回答标题中的问题:不,您不能直接将 std::string 从托管代码编组为非托管代码。有关原因,请参阅another SO question 的答案。主要原因是 std::string 是模板而不是“真实”类型。

基本上,您需要编写一个小型非托管模块,它为 openCV 函数提供简单的包装器,摆脱 STL 类型。使用您的示例函数,它可以很简单:

declspec(__dllexport) imread(char* c, int i) 
    string s = c;
    cv::imread(s, i);

至于字符串偏移的问题...尝试创建一个单独的项目,从一开始就使用“Unmanaged”类型。将项目切换到托管和返回可能会导致项目设置混乱,产生不可预测的后果 - 至少,我已经两次遇到这样的坑......

【讨论】:

我实际上可以将 std::string 从托管代码编组为非托管代码。那里没问题。困难的部分是将它从一个非托管模块传递到另一个。因此,答案的相关部分只是“将项目切换到托管并返回可能会导致项目设置混乱”,这并不是很令人满意。 :) 对不起,我真的不明白这个问题。 至于“设置混乱”,这是我的新鲜体验 - 我遇到了同样的问题(字符串开头有 4 个未知符号 - 很可能是 UTF-8 BOM)而且很简单用相同的代码重新创建项目对我有帮助。【参考方案2】:

您不应该(不能)在不同模块 (DLL) 之间传递 std::string& ,除非您确定所有模块都以相同的方式编译(发布与调试等)。

例如:如果您在 release 中编译一个 DLL 而在 debug 中编译另一个 - 那么 std::string 的内存布局可能会有所不同。 其他编译器设置也可能会影响内存布局。

试试这个 - 将下面的代码编译为 release vs. debug 并运行它。 在调试中,您在版本 28 中获得 32。

#include <iostream>
#include <string>

int main()

   std::cout << "sizeof(std::string) : " << sizeof(std::string) << std::endl;

   return 0;

我建议不要使用 std::string 跨越模块边界。

【讨论】:

【参考方案3】:

简短回答,如果您对 DLL 使用相同的编译器设置,您可以将 STL 字符串从 C++/CLI 应用程序传递给本机 C++ DLL和 C++/CLI 应用程序。

代码

#include <msclr/marshal_cppstd.h> // header for marshal utilities

...

String^ path = "C:\\Users\\Don Reba\\Pictures\\Merlin 1D.jpg"; // Managed string
std::string s = msclr::interop::marshal_as<std::string>(path); // To stl string
cv::imread(s, CV_LOAD_IMAGE_GRAYSCALE);

查看此页面了解更多详情:http://msdn.microsoft.com/en-us/library/bb384865.aspx

【讨论】:

我不明白为什么要使用marshal_as 而不是简单的构造函数。问题是std::string 在托管 DLL 中的布局不同,尽管使用了相同的编译器设置。 当然你可以使用一个简单的构造函数,但是上面的代码是为了演示如何将一个托管的String^编组为std::string。至于布局,如果编译器设置相同,则布局也相同。我很确定这一点,因为我以这种方式为原生 C++ dll 创建了一个 .net 包装器 dll。如果您遇到编译/链接错误,如何在问题中发布错误消息?【参考方案4】:

问题在于 Visual Studio 2010 默认使用不同的工具集用于 C++ 和 C++/CLI 项目。这就是为什么 STL 类尽管设置相同但布局不同的原因。要解决此问题,请确保在 C++/CLI 项目中将 Configuration Properties / General / Platform Toolset 设置为 v100。

【讨论】:

以上是关于从 C++/CLI 应用程序将 STL 字符串传递给 C++ DLL的主要内容,如果未能解决你的问题,请参考以下文章

将字符串从 C++/CLI 类库传递到 C#

使用 C++/CLI 包装器将二维数组从 C# 传递到非托管 C++

如何将 C++ 本机对象编组到托管 C++ CLI

如何将 void* 的引用从 C++/CLI 传递给本机 C 函数

将 C# 函数指针传递到 C++/CLI 互操作 dll

从 C++ 传递函数指针以由 C# 调用 - 函数参数包括宽字符字符串 (LPCWSTR)