2

I have a C++/CLI project using OpenCV. I compiled this version of OpenCV in VS 2010 myself and I can use it in unmanaged projects without an issue — the trouble started when I tried to use it in a managed one.

The function of interest is cv::imread(std::string&, int). Simply calling it from a managed module did not work at all, producing <invalid pointer> on the receiving end. I was sort of expecting it. After all, managed code has its own std::string implementation.

Things got a little more interesting when I created a separate C++ file, removed CLI support from its module, and placed my code in it. Now, imread was getting a valid pointer, but its contents were scrambled. Apparently, the string I was passing it contained the string pointer offset by 4 bytes, but it expected it to be at the 0 offset.

The unmanaged module is using the same CRT DLL as OpenCV and has all options set to the values appropriate for normal OpenCV use. Why would it have a different string layout? I am lost.

Sample code:

#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);
}
Don Reba
  • 13,814
  • 3
  • 48
  • 61
  • hmm, you're not showing any of that managed code, like where you pass in your filepath. maybe you can just sidestep the problem, by passing in a `const char *` instead of a string, and leave it to the string ctor invoked when calling imread ? – berak Feb 22 '13 at 12:58
  • The code snippet is for my unmanaged module. The managed->native transition occurs when the function `Run` is called. Unfortunately, `imread` takes a string reference and, as far as I can see, there is no way around constructing the string on my side. Moreover, this is not the only case — OpenCV uses strings and vectors throughout. – Don Reba Feb 22 '13 at 14:23

4 Answers4

5

Answering the question in the title: no, you can't directly marshal std::string from managed to unmanaged code. See answers to another SO question on the reasons. Main reason is that std::string is a template and not a "real" type.

Basically, you need to write a small unmanaged module which provides simple wrappers for the openCV functions, getting rid of STL types. With your example function, it can be as simple as that:

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

As for the problem with the string offset... Try creating a separate project, with "Unmanaged" type from the beginning. Switching the project to managed and back can produce a mess with project settings, having unpredictable consequences - at least, I've hit such pits twice...

Community
  • 1
  • 1
DarkWanderer
  • 8,739
  • 1
  • 25
  • 56
  • I actually can marshal an `std::string` from managed code to unmanaged. No problem there. The difficult part is in passing it from one unmanaged module to another. So, the relevant part of the answer is just "switching the project to managed and back can produce a mess with project settings," which is not very satisfactory. :) – Don Reba Feb 28 '13 at 13:37
  • I am sorry, I really don't understand the question then. – DarkWanderer Mar 02 '13 at 06:40
  • As for the "mess with settings", this was my fresh experience - I've run into the same issue (4 unknown symbols at the string start - most likely the UTF-8 BOM) and simple recreation of the project with the same code helped me. – DarkWanderer Mar 02 '13 at 06:43
2

You shouldn't (can't) pass a std::string& between different modules (DLLs) unless you are sure that all your modules were compiled the same way (release vs. debug etc).

For example: if you compile one DLL in release and another as debug - then the memory layout of std::string is likely to be different. Other compiler settings may influence the memory layout as well.

Try this - compile the code below as release vs. debug and run it. In debug you get 32 in relase 28.

#include <iostream>
#include <string>

int main()
{
   std::cout << "sizeof(std::string) : " << sizeof(std::string) << std::endl;

   return 0;
}

I suggest not to cross module boundaries using std::string.

Markus Schumann
  • 7,636
  • 1
  • 21
  • 27
0

Short answer: Yes, you can pass a STL string to a native C++ DLL from a C++/CLI app, if you use the same compiler settings for the DLL and the C++/CLI app.

Code:

#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);

See this page for more details: http://msdn.microsoft.com/en-us/library/bb384865.aspx

Chen
  • 1,654
  • 2
  • 13
  • 21
  • I don't see why I would want to use `marshal_as` instead of a simple constructor. The problem is that `std::string` has a different layout in the managed DLL despite using the same compiler settings. – Don Reba Feb 28 '13 at 10:48
  • Of course you can use a simple constructor, but above code is to demo how to marshal a managed `String^` to `std::string`. As to layout, if the compiler settings are the same, the layouts are also the same too. I'm pretty sure about this because I had created a .net wrapper dll for a native C++ dll in this way. If you got compile/link errors, how about post the error messages in the question? – Chen Feb 28 '13 at 14:32
0

The problem is that Visual Studio 2010 uses different toolsets for C++ and C++/CLI projects by default. This is why STL classes have different layouts despite identical settings. To fix the problem, make sure that Configuration Properties / General / Platform Toolset is set to v100 in the C++/CLI project.

Don Reba
  • 13,814
  • 3
  • 48
  • 61