__declspec(dllexport) ::vector<std::string>

本文关键字:std gt string lt vector declspec dllexport | 更新日期: 2023-09-27 18:07:19

我一直在努力找出如何从c++ dll返回字符串数组到c#应用程序,但我被困在如何做到这一点或在一个非常基本的水平上找到一篇文章。

假设我有下面的代码。如何修复粗体行:
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;
}

感谢马特

__declspec(dllexport) ::vector<std::string>

你可以使用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();

你不能直接做——你需要一个额外的间接层次。对于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函数销毁分配的对象。

从c++到c#有两种"标准"方法。

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

另一个是COM。在这里创建一个COM接口,返回一个包含BSTR字符串的SAFEARRAY。这个COM接口然后通过c#中的System.Runtime.InteropServices实例化。SAFEARRAY是一个Object[],它可以被分隔成单个字符串对象。

将C接口加载到c#中的功能基本上仅限于C。任何c++都会失败,而Pete提供了这种"非标准"的方法。(它工作得很好,只是不是微软想让你做的。)