4

I'm new to C++/CLI but have been coding managed code for many years... apparently too many years. :)

Attempting to write a wrapper for an unmanaged class provided by a 3rd party, and I'm seeing some strange stuff. I'm hoping you all can help me out weed what what is my noobishness and what is actually strange.

CLI Wrapper:

public ref class Wrapper
{
public:
    Wrapper(const float* flts, unsigned int fltlen, int offset)
    {
        _unmanagedClass = new UnmanagedClass(flts, fltlen, offset);
    }

    ~Wrapper()
    {
        delete _unmanagedClass;
    }

    String^ getSomeString()
    {
        string x = _unmanagedClass->getSomeString(); //1
        String^ ret = gcnew String(x.c_str()); //2
        return ret; //3
    }

private:
    UnmanagedClass* _unmanagedClass;
};

I should also note that I have these directives in the header;

#pragma managed(push, off)
#include "Unmanaged.h"
#pragma comment(lib, "lib\\Unmanaged_dll.lib")
#pragma managed(pop)

Here's Unmanaged.h;

class UNMANGED_API UnmanagedClass
{
public:
    UnmanagedClass(const float* flts, uint fltlen, int offset);
    string getSomeString() { return _someString; }

private:
    string _someString;
};

This all compiles. Then the strangness/lack of experience kicks in.

When debugging this in DEBUG configuration, UnmanagedClass::getSomeString() appears to be returning a resonable/expected string value. I can see this by setting a breakpoint on //2 and peeking the value of x. If I step to //3, I can see that ret has the value of x. However, if I attempt to step out/over //3, I get a couple of failed assertions (BLOCK_TYPE_IS_VALID and _CrtIsValidHeapPointer) and the debugger stalls, never returning to the managed implementation.

When debugging this in RELEASE configuration, I don't get the failed assertions and I return to my managed implementation, but the string value returned by getSomeString() is garbage where ever I peek it; //2, //3 as well as in the managed implementation.

I've massaged the code in a few different ways to no avail.

I think there is some Mashalling that needs to be done around //2, but I haven't been able to find any thing that really hits home as far as how to marshall that basic_string to a System::String^, or if it's even required. If so, then some help with explicit syntax would be greatly appreciated.

I've also narrowed the call that produces the failed assertions down to //1 by returning return ""; //3. These assertions point to trying to modify/delete memory that dosn't exist on the heap the current runtime has access to. Does that have to do with needing to marshall the return value of UnmangedClass::getSomeString()?

Hoping I'm just missing some simple concept here and there isn't a problem with the 3rd party code. Please let me know if I can provide any more detail, and apologies for my almost complete ignorance of the grand-daddy of all languages.

Thanks in advance for any information or "pointers";

EDIT: Adding C# Managed client implementation;

public unsafe string GetString(List<float> flts )
{
    float[] fltArr = flts.ToArray();

    Wrapper wrap;

    fixed (float* ptrFlts = fltArr)
    {
        wrap = new Wrapper(ptrFlts , fltArr.Length, 0);
    }

    var x = wrap.getSomeString();
    return x.ToString();
}

EDIT: Adding Dumpbin.exe signature of Unmanged.dll!UnmangedClass::getSomeString()

(public: class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > __thiscall Codegen::getSomeString(void))

Josh
  • 43
  • 1
  • 4

2 Answers2

3

This problem has nothing to do with .NET or C++/CLI, the problem is purely in the native code.

You've violated the One Definition Rule for std::string, if your definition doesn't exactly match what Unmanaged_dll.dll uses, all hell breaks loose. And it sounds as if that DLL is used the debug definition/class layout.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Thanks for your reply. Am I understanding you correctly that the definition of std:string is changing between debug and release modes? I've edited the main post to provide the dumpbin signature of Unmanaged::getSomeString. Is there some way I can more closely match their string type? Do I need to always run my wrapper in Debug configuration if I am to use the native code as is? – Josh Apr 11 '11 at 20:05
  • @Josh: Do you have the code for `Unmanaged_dll.dll`? If so, the "right" fix is to use different names for debug and release configurations, just like the Microsoft CRT does (e.g. MSVCR90.dll vs MSVCR90D.dll), and then use `#if NDEBUG` together with the `#pragma comment(lib)` to pick the right one. If you can't build a release version of the DLL, then you can't use the release CRT for your code either. One of the reasons I recommend against sharing classes across DLL boundaries. – Ben Voigt Apr 11 '11 at 20:14
  • @Josh: It's also possible that the layout of `UnmanagedClass` itself is changing, so that the location where the DLL stored `_someString` isn't where your compiler goes looking for it. – Ben Voigt Apr 11 '11 at 20:18
  • @Ben, thanks again for your quick replies! I don't have access to the source, but I have some access to the author. Just so my ducks are in a row, is this as simple as having the author provide both debug and release bits? I'll also mention that I'm linking using a lib and not DLLImport... unfortunatly, I don't know if it matters... – Josh Apr 11 '11 at 20:24
  • @Josh: It can be made to work if both modules use the same exact layout for the standard library... that means compiler version (including service packs), debug vs release, _SCL_ITERATOR_DEBUGGING, etc. The heap must be shared, so you also need to use the DLL version of the CRT. That's bound to cause trouble when you want to move to the next great new compiler version. The less fragile fix is for you to provide a buffer as a `char*` and have him copy his string into it (a second function may be needed to get the size first, so you can allocate your buffer large enough). – Ben Voigt Apr 11 '11 at 20:30
  • @Ben, I think you've given me enough info to go back to the author and speak somewhat intelligently. Through my iterations and looking at source samples, the method of passing in a buffer seems to be the preffered way. I may also recommend to the author that he provide a non-class based interface sutibale for P/Invoke directly from C#. Thanks again; – Josh Apr 11 '11 at 20:37
1

You converted your native string to a Managed string just fine. This article on MSDN, has samples on how to convert between all the different string types that ships on Microsoft platforms:

Having said that, I took your code, and compiled it, and I couldn't get anything to fail. Of course I had to come up with my own way of initializing UnmanagedClass::_someString, which I did by just doing this:

UnmanagedClass::UnmanagedClass(const float* /*flts*/, unsigned int /*fltlen*/, int /*offset*/)
{
    _someString = "A few of my favorite things";
}

When I did that, and stepped through this code:

#include "stdafx.h"
#include "Wrapper.h"

int _tmain(int argc, _TCHAR* argv[])
{
    Wrapper^ w = gcnew Wrapper(NULL, 0, 0);
    System::String^ s = w->getSomeString();
    return 0;
}

It worked just fine. Here is the rest of what I did:

// UnmanagedClass.h
#pragma once
#pragma unmanaged
#include <vector>

class UnmanagedClass
{
public:
    UnmanagedClass(const float* flts, unsigned int fltlen, int offset);
    std::string getSomeString() { return _someString; }
private:
    std::string _someString;
};

And it's implementation:

// UnmanagedClass.cpp
#include "UnmanagedClass.h"
#include <tchar.h>

UnmanagedClass::UnmanagedClass(const float* /*flts*/, unsigned int /*fltlen*/, int /*offset*/)
{
    _someString = "A few of my favorite things";
}

And the managed class

// wrapper.h
#pragma once

#pragma unmanaged
#include "UnmanagedClass.h"

#pragma managed

public ref class Wrapper
{
public:
    Wrapper(const float* flts, unsigned int fltlen, int offset)
    {
        _unmanagedClass = new UnmanagedClass(flts, fltlen, offset);
    }

    ~Wrapper()
    {
        delete _unmanagedClass;
    }

    System::String^ getSomeString()
    {
        std::string x = _unmanagedClass->getSomeString(); //1
        System::String^ ret = gcnew System::String(x.c_str()); //2
        return ret; //3
    }
private:
    UnmanagedClass* _unmanagedClass;
};

I hope that helps a little.

C.J.
  • 15,637
  • 9
  • 61
  • 77
  • @C Johnson: Thanks for taking the time to replicate my senario and validate my native/managed transition, everything helps. (Sadly, I'm still too new to vote-up.) – Josh Apr 11 '11 at 20:25