So erhalten Sie den Inhalt des Arrays aus der C ++ - DLL in C #

Jan 07 2021

Ich möchte Funktionen aus der DLL in C ++ mit C # verwenden.

Ich speichere String-Daten in einem Vektor.

Meine C ++ - Datei enthält:

#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();
        }
    }
}

Ich weiß nicht, wie ich das Array in c # weiter durchsuchen soll. Ich weiß nicht, ob die Verwendung von Vektor optimal ist. Wenn Sie eine andere Lösung haben, bin ich bereit.

Bitte helfen Sie.

Antworten

6 xanatos Jan 07 2021 at 00:45

Meine bevorzugte Methode zum Übergeben eines Arrays von Zeichenfolgen C ++ -> C # ist die Verwendung eines Delegaten.

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);

und dann

var lst = new List<string>();

TestReturnArrayStrings(lst.Add);

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

Und 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());
        }
    }
}

Hier ist der "Trick", dass C # einen Delegaten an die List<string>.Add()Methode an C ++ übergibt und C ++ das C # direkt "füllt" List<>. Der von C ++ verwaltete Speicher verbleibt auf der C ++ - Seite, der von C # verwaltete Speicher auf der C # -Seite. Keine Probleme mit speicherübergreifendem Besitz. Wie Sie sich vorstellen können, ist es ziemlich einfach, den "Trick" auf eine andere .Add()Methode wie HashSet<string>oder zu erweitern Dictionary<string, string>.

Als Nebenbemerkung habe ich einen Github mit vielen Beispielen zum Marshalling zwischen C / C ++ und C # (sowohl .NET Framework als auch .NET Core / 5.0) erstellt.

2 SimonMourier Jan 07 2021 at 05:47

Eine Möglichkeit besteht darin, die SAFEARRAY-Struktur von COM zu verwenden , die von .NET unterstützt wird (der von P / Invoke verwendete .NET-Allokator ist der COM-Allokator), einschließlich der meisten zugeordneten Untertypen wie BSTR .

In C / C ++ können Sie Folgendes definieren:

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;
}

Und in C # können Sie Folgendes definieren:

[DllImport("ProductLibrary.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.SafeArray)]
public static extern string[] GetProduct();