Search code examples
c#c++pinvoke

How to get content of array from C++ dll in C#


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.


Solution

  • 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).