3

I want to use functions from DLL in C++ with C#.

I store string data in a vector.

My C++ file contains:

#include "stdafx.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

extern "C" __declspec(dllexport) std::vector<std::string> GetProduct();

std::vector<std::string> GetProduct()
{
  std::vector<std::string> vectProduct;
  vectProduct.push_back("Citroen");
  vectProduct.push_back("C5");
  vectProduct.push_back("MOP-C5");
  return vectProduct;
}

In C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Runtime.InteropServices;
namespace ConsoleApplication
{
    class Program
    {
        [DllImport("ProductLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern StringBuilder GetProduct();

        static void Main(string[] args)
        {
            StringBuilder vectProduct_impl = GetProduct();
        }
    }
}

I don't know how to continue to browse the array in c#. I don't know if the use of vector is optimal. if you have other solution I'm ready.

Please help.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • 3
    Don't try to eport C++ data structures from a DLL, compiled for other projects. C++ does not have a well defined ABI, what means, that this is by definition undefined behaviour. For a kick off: https://stackoverflow.com/a/33801580/4043866 As said in this thread, you either have to export a C interface, or use some other techinque like a COM or C++/CLI. – JulianW Jan 06 '21 at 17:14
  • What JulianH said... As a sidenote, both for `std::vector` and for `std::string` you can get a C pointer for the internal "data", but if you "mix" both of them then everything goes kaboom... – xanatos Jan 06 '21 at 17:18
  • Another problem: if the memory is allocated from the C/C++ side it is often complex to free it... But the big question is **why** are you trying to mix two languages that are complete in themselves... Learn to sky or learn to skate. Don't try to do both at the same time, unless it is _really_ _really_ _really_ necessary – xanatos Jan 06 '21 at 17:20
  • Is there any possiblity to use another thing to store string data and get it after in C# please. I try to create another function which have a return type int*.I arrived also to fetch data of ptr using IntPtr. But I did not arrive for string type. –  Jan 06 '21 at 17:32
  • @TheRight Everything can be done. There is always a trade to be done between risk and reward. – xanatos Jan 06 '21 at 17:33
  • An explanation of why this doesn't work even between C++ and other C++: https://stackoverflow.com/q/5347355/103167 – Ben Voigt Jan 06 '21 at 17:40

2 Answers2

7

My favourite way for passing an array of strings C++-->C# is by using a delegate.

C#:

// If possible use UnmanagedType.LPUTF8Str
// or under Windows rewrite everything to use 
// wchar_t, std::wstring and UnmanagedType.LPWStr
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void AddAnsi([MarshalAs(UnmanagedType.LPStr)] string str);

[DllImport("CPlusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void TestReturnArrayStrings(AddAnsi add);

and then

var lst = new List<string>();

TestReturnArrayStrings(lst.Add);

foreach (string str in lst)
{
    Console.WriteLine(str);
}

And C++:

#include <string>
#include <vector>

extern "C"
{
    __declspec(dllexport) void TestReturnArrayStrings(void (add)(const char* pstr))
    {
        std::string str1 = "Hello";
        std::string str2 = "World";

        add(str1.data());
        add(str2.data());

        // Example with std::vector

        add("--separator--"); // You can even use C strings

        std::vector<std::string> v = { "Foo", "Bar" };

        // for (std::vector<std::string>::iterator it = v.begin(); it != v.end(); ++it)
        for (std::vector<std::string>::const_iterator it = v.begin(); it != v.end(); ++it)
        {
            add(it->data());
        }

        add("--separator--"); // You can even use C strings

        // With C++ 11

        // for (auto& it: v)
        for (const auto& it: v)
        {
            add(it.data());
        }
    }
}

Here the "trick" is that C# passes to C++ a delegate to the List<string>.Add() method, and C++ "fills" directly the C# List<>. The memory managed by C++ remains in the C++ side, the memory managed by the C# remains in the C# side. No problems of cross-memory ownership. As you can imagine, it is quite easy to expand the "trick" to any other .Add() method, like HashSet<string>, or Dictionary<string, string>.

As a sidenote, I've created a github with many examples about marshaling between C/C++ and C# (both .NET Framework and .NET Core/5.0).

xanatos
  • 109,618
  • 12
  • 197
  • 280
1

One way to do it is to use COM's SAFEARRAY structure as it's supported by .NET (the .NET Allocator used by P/Invoke is the COM allocator), including most of associated sub types, like BSTR.

So, in C/C++, you can define this:

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

LPSAFEARRAY GetProduct()
{
    LPSAFEARRAY psa = SafeArrayCreateVector(VT_BSTR, 0, 3);
    LONG index = 0;

    // _bstr_t is a smart class that frees allocated memory automatically
    // it needs #include <comdef.h>
    // but you can also use raw methods like SysAllocString / SysFreeString

    _bstr_t s0(L"Citroen"); // could use "Citroen" if you really want ANSI strings

    // note SafeArrayPutElement does a copy internally
    SafeArrayPutElement(psa, &index, s0.GetBSTR());
    index++;

    _bstr_t s1(L"C5");
    SafeArrayPutElement(psa, &index, s1.GetBSTR());
    index++;

    _bstr_t s2(L"MOP - C5");
    SafeArrayPutElement(psa, &index, s2.GetBSTR());
    index++;
    return psa;
}

And in C#, you can define this:

[DllImport("ProductLibrary.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.SafeArray)]
public static extern string[] GetProduct();
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • Hi Simon, thank's for your answer but i get in your implimentation an error of LPSAFEARRAY not defined for C++ Code. What i can include to solve the problem please ? –  Jan 07 '21 at 08:56
  • it's ok i try with oaidl.h. –  Jan 07 '21 at 08:58