【问题标题】:How to get content of array from C++ dll in C#如何从 C# 中的 C++ dll 获取数组的内容
【发布时间】:2021-04-12 11:04:24
【问题描述】:

我想在 C++ 和 C# 中使用 DLL 中的函数。

我将字符串数据存储在向量中。

我的 C++ 文件包含:

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

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

我不知道如何在c#中继续浏览数组。 我不知道使用矢量是否最佳。如果您有其他解决方案,我准备好了。

请帮忙。

【问题讨论】:

  • 不要尝试从为其他项目编译的 DLL 中导出 C++ 数据结构。 C++ 没有明确定义的 ABI,这意味着根据定义这是未定义的行为。开始:stackoverflow.com/a/33801580/4043866 正如该线程中所说,您要么必须导出 C 接口,要么使用其他技术,如 COM 或 C++/CLI。
  • JulianH 所说的... 作为旁注,对于std::vectorstd::string,您可以获得内部“数据”的 C 指针,但如果您“混合”它们两者然后一切顺利......
  • 另一个问题:如果内存是从 C/C++ 端分配的,释放它通常很复杂......但最大的问题是 为什么 你要混合两种语言本身就是完整的......学习天空或学习滑冰。不要试图同时做这两件事,除非是真的 真的 真的必要
  • 请问有没有可能使用其他东西来存储字符串数据并在 C# 中获取它。我尝试创建另一个返回类型为 int* 的函数。我也使用 IntPtr 来获取 ptr 的数据。但是我没有到达字符串类型。
  • @TheRight 一切皆有可能。在风险和回报之间总是有交易要做。

标签: c# c++ pinvoke


【解决方案1】:

我最喜欢的传递字符串数组 C++-->C# 的方式是使用委托。

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

然后

var lst = new List<string>();

TestReturnArrayStrings(lst.Add);

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

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

这里的“技巧”是 C# 将委托传递给 C++ 到 List&lt;string&gt;.Add() 方法,而 C++ 直接“填充”C# List&lt;&gt;。 C++管理的内存留在C++端,C#管理的内存留在C#端。没有跨内存所有权的问题。可以想象,很容易将“技巧”扩展到任何其他.Add() 方法,例如HashSet&lt;string&gt;Dictionary&lt;string, string&gt;

作为旁注,我创建了一个github,其中包含许多关于 C/C++ 和 C#(.NET Framework 和 .NET Core/5.0)之间编组的示例。

【讨论】:

    【解决方案2】:

    一种方法是使用 COM 的 SAFEARRAY structure,因为它受 .NET 支持(P/Invoke 使用的 .NET 分配器是 COM 分配器),包括大多数关联的子类型,例如 BSTR

    因此,在 C/C++ 中,您可以这样定义:

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

    而在 C# 中,您可以这样定义:

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

    【讨论】:

    • 嗨,西蒙,感谢您的回答,但我在您的暗示中得到了 LPSAFEARRAY 未为 C++ 代码定义的错误。请问我可以包括什么来解决问题?
    • 没关系,我试试 oaidl.h。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-29
    • 2011-06-24
    • 1970-01-01
    相关资源
    最近更新 更多