从 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++