1
votes

I'm writing a CLI/C++ wrapper around a native C++ dll which can not be changed. One of the functions of the native DLL returns a vector of unmanaged objects. What would be the best way to wrap this vector in my CLI wrapper? The CLI wrapper is going to be used by a C# application.

class __declspec(dllexport) Instrument
{
  public:
        Instrument();
        ~Instrument();
        string _type;
        unsigned int _depth;
}

The native DLL has the function getInstruments() which is what I'm trying to wrap

 class __declspec(dllexport) InstrumentList
   {
       InstrumentList();
       ~InstrumentList();
       vector<Instrument*> getInstruments();
   }

So I need to wrap the instrument class in a managed class and wrap InstrumentList class in a managed class. I have the Instrument class wrapped, but need to convert the vector returned by getInstruments() into something equivalent that the CLI wrapper for InstrumentList can return.

2
Perhaps just convert the vector into a pointer and return the pointer?Seth Carnegie
When do you want the data to be marshaled? Each time Instrument::_type is accessed or once when InstrumentList::getInstruments() is called?ildjarn
@SethCarnegie : C# has no concept of what std::vector<> or std::string are, so that wouldn't be very useful.ildjarn
Your native DLL is broken, std::vector isn't guaranteed to have compatible definitions in different DLLs, so exporting it across DLL boundaries is a very bad idea. Ditto for std::string. Putting the native code and managed wrapper into a single DLL would be fine, the C++ details are hidden within a single DLL, and .NET objects are designed to have standard binary interfaces that can cross DLL boundaries.Ben Voigt
@ildjarn I mean take the pointer to the beginning of the elements the vector was storing (since it must store them in sequential memory locations) and give C# the pointer to the elements (and the number of elements there are). I wasn't talking about passing a pointer to the vector.Seth Carnegie

2 Answers

4
votes

You may not want to wrap the InstrumentList at all.

Just use one of the stock .NET collections (which you can access from C++/CLI) and make a collection of your Instrument wrappers. I've been using ObservableCollection since I want to databind to my collections.

Example:

public ref class MyManagedType
{
    public:
        MyManagedType(MyNativeType* pObject) { m_pObject = pObject };

    private:
        MyNativeType* m_pObject;
}

Then create the managed collection like this:

ObservableCollection<MyManagedType^>^ managedCollection = gcnew ObservableCollection<MyManagedType^>();

Finally, add objects to the collection:

managedCollection->Add(gcnew MyManagedType(pNativeObject));

It's a bit of effort to keep the native and managed collections in sync, but it works well.

4
votes

Unless you want to delay the marshaling of Instrument::_type until its managed facade is accessed, this should get you started:

public ref class InstrumentM
{
    String^ _type;
    unsigned _depth;

internal:
    explicit InstrumentM(Instrument const& i)
      : _type(gcnew String(i._type.c_str())),
        _depth(i._depth)
    { }

public:
    property String^ Type { String^ get() { return _type; } }
    property unsigned Depth { unsigned get() { return _depth; } }
};

public ref class InstrumentListM
{
    InstrumentList* _list;

public:
    InstrumentListM() : _list(new InstrumentList()) { }
    ~InstrumentListM() { this->!InstrumentListM(); }
    !InstrumentListM()
    {
        delete _list;
        _list = nullptr;
    }

    array<InstrumentM^>^ GetInstruments()
    {
        if (!_list)
            throw gcnew ObjectDisposedException(L"_list");

        vector<Instrument*> const& v = _list->getInstruments();
        array<InstrumentM^>^ ret = gcnew array<InstrumentM^>(v.size());
        for (int i = 0, i_max = ret->Length; i != i_max; ++i)
            if (v[i])
                ret[i] = gcnew InstrumentM(*v[i])
        return ret;
    }
};