4

I've been trying to work out how to return an array of strings from a c++ dll to a c# application but am stuck on how to do this or find an article at a very basic level.

Suppose I have the code below. How do I fix the bolded line:

extern "C" {
    __declspec(dllexport) int GetANumber();

//unsure on this line:
    **__declspec(dllexport) ::vector<std::string> ListDevices();**

}

extern::vector<std::string> GetStrings()
{
    vector<string> seqs;
    return seqs;
}

extern int GetANumber()
{
    return 27;
}

thanks

Matt

Abhijit-K
  • 3,569
  • 1
  • 23
  • 31
mattpm
  • 1,310
  • 2
  • 19
  • 26
  • Not sure how easy it is to get the vector from unmanaged to managed. May be you need to wrap the function under another that returns a simple array of struct and expose that. The params have to be marshalled from native to .net runtime (PInvoke). Hence we need to define equivalent struct with proper padding (layout) at the c# side. – Abhijit-K Apr 04 '13 at 11:49

3 Answers3

6

You could use the COM Automation SAFEARRAY type, even without doing full COM (no object, no class, no interface, no TLB, no registry, etc.), just with DLL exports, as .NET supports it natively with P/Invoke, something like this:

C++:

extern "C" __declspec(dllexport) LPSAFEARRAY ListDevices();

LPSAFEARRAY ListDevices()
{
    std::vector<std::string> v;
    v.push_back("hello world 1");
    v.push_back("hello world 2");
    v.push_back("hello world 3");

    CComSafeArray<BSTR> a(v.size()); // cool ATL helper that requires atlsafe.h

    std::vector<std::string>::const_iterator it;
    int i = 0;
    for (it = v.begin(); it != v.end(); ++it, ++i)
    {
        // note: you could also use std::wstring instead and avoid A2W conversion
        a.SetAt(i, A2BSTR_EX((*it).c_str()), FALSE);
    }
    return a.Detach();
}

C#:

static void Main(string[] args)
{ 
    foreach(string s in ListDevices())
    {
        Console.WriteLine(s);
    }
}


[DllImport("MyUnmanaged.dll")]
[return: MarshalAs(UnmanagedType.SafeArray)] 
private extern static string[] ListDevices();
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
2

You can't do it directly - you need an extra level of indirection. For a C-style compatible interface you'll need to return a primitive type. Forget about using C++ DLLs from any other compiler - there is no strict C++ ABI.

So, you'd need to return a opaque pointer to an allocated string vector, e.g.

#define MYAPI __declspec(dllexport)
extern "C" {
    struct StringList;

    MYAPI StringList* CreateStringList();
    MYAPI void DestroyStringList(StringList* sl);
    MYAPI void GetDeviceList(StringList* sl);
    MYAPI size_t StringList_Size(StringList* sl);
    MYAPI char const* StringList_Get(StringList* v, size_t index);
}

And implementation wise:

std::vector<std::string>* CastStringList(StringList* sl) {
    return reinterpret_cast<std::vector<std::string> *>(sl);
}

StringList* CreateStringList() {
     return reinterpret_cast<StringList*>(new std::vector<std::string>);
}

void DestroyStringList(StringList* sl) {
     delete CastStringList(sl);
}
void GetDeviceList(StringList* sl) {
     *CastStringList(sl) = GetStrings(); // or whatever
}
size_t StringList_Size(StringList* sl) {
    return CastStringList(sl)->size();
}
char const* StringList_Get(StringList* v, size_t index) {
    return (*CastStringList(sl))[index].c_str();
}

After doing all of this you can then provide a cleaner wrapper on the C# end. Don't forget to destroy the allocated object via the DestroyStringList function, of course.

Pete
  • 4,784
  • 26
  • 33
  • Thanks Pete, that's just what I was looking for. It's a bit of a quagmire trying to get orientated :) – mattpm Apr 05 '13 at 09:41
2

You have two "standard" ways to get from C++ to C#.

The first is C++/CLI. In this case you will build a C++/CLI library that takes the std::vector<std::string> and converting that into a System::vector<System::string>. Then you can use it freely as a System.String[] in C#.

The other is COM. There you create a COM interface that returns a SAFEARRAY containing BSTR string. This COM interface is then instantiated though the System.Runtime.InteropServices in C#. The SAFEARRAY is then a Object[] which can be cased to single string objects.

The facility to load C interfaces into C# is basically restricted to C. Any C++ will fail and Pete provides that "non standard" approach. (It works very well, just not what MS wants you to do.)

rioki
  • 5,988
  • 5
  • 32
  • 55
  • Both also good ways to do it. I don't think my approach is "non standard" though - it is very common and highly portable. – Pete Apr 04 '13 at 12:12
  • It is non standard in the fashion, that the these two approaches was always shown by MS training and docs. For reasons unknown from each and every MS person I spoke to strongly discouraged us to use `DllImport`. The most cited version was that this was "unsafe", whatever that means. I was happy the moment I switched projects to a fully native C++ environment. I like my unsafe language. P.S. You got an upvote from me... – rioki Apr 04 '13 at 12:23
  • And you got an upvote from me. I don;t use .net very much, but isn't 'unsafe' is the .net term for anything that is not CLR managed? Does not calling to a COM interface or C++/CLI result in the execution of 'unsafe' code, so where is the distinction? – Pete Apr 04 '13 at 13:27
  • Don't ask me. I think it refers to the fact that if you do a mistake in the signature it will crash the core .net framework. Crashing in your code is Ok, then you get the blame; but crashing in the .net code... evil... – rioki Apr 04 '13 at 18:39