4

我一直在尝试研究如何将字符串数组从 c++ dll 返回到 ac# 应用程序,但我一直坚持如何做到这一点,或者在非常基本的层面上找到一篇文章。

假设我有下面的代码。如何修复粗体线:

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

谢谢

马特

4

3 回答 3

6

您可以使用 COM 自动化SAFEARRAY类型,即使不使用完整的 COM(无对象、无类、无接口、无 TLB、无注册表等),只需使用 DLL 导出,因为 .NET 通过 P/Invoke 原生支持它,像这样的东西:

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();
于 2013-04-04T16:40:10.937 回答
2

你有两种从 C++ 到 C# 的“标准”方式。

第一个是 C++/CLI。在这种情况下,您将构建一个 C++/CLI 库,该库接受std::vector<std::string>并将其转换为System::vector<System::string>. System.String[]然后你可以像在 C# 中一样自由地使用它。

另一个是COM。在那里,您创建了一个返回SAFEARRAY包含BSTR字符串的 COM 接口。然后通过 C# 中的 System.Runtime.InteropServices 实例化此 COM 接口。然后SAFEARRAY是一个 Object[] ,它可以被转换为单个字符串对象。

将 C 接口加载到 C# 中的工具基本上仅限于 C。任何 C++ 都会失败,Pete 提供了“非标准”方法。(它工作得很好,只是不是 MS 想要你做的。)

于 2013-04-04T12:07:04.047 回答
2

你不能直接做——你需要一个额外的间接级别。对于 C 风格的兼容接口,您需要返回原始类型。忘记使用来自任何其他编译器的 C++ DLL - 没有严格的 C++ ABI。

因此,您需要将一个不透明的指针返回到分配的字符串向量,例如

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

和实施明智:

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

完成所有这些之后,您可以在 C# 端提供一个更干净的包装器。当然,不要忘记通过 DestroyStringList 函数销毁分配的对象。

于 2013-04-04T11:53:54.693 回答