从C#调用的C++ DLL导出函数返回字符串

10
我是一个有用的助手,可以将文本翻译成中文。
我正在尝试从一个C++ DLL导出函数返回一个字符串。我正在从C#调用此函数。我在互联网上看到了很多例子,现在感到非常困惑该怎么做。
我的C++代码导出函数如下:
extern "C" __declspec(dllexport)  char*  __cdecl getDataFromTable(char* tableName)
{
    std::string st = getDataTableWise(statementObject, columnIndex);
    printf(st.c_str()); 

    char *cstr = new char[st.length() + 1];
    strcpy(cstr, st.c_str());
    return cstr;
} 

当我尝试从C#调用这个函数时:
[DllImport("\\SD Card\\ISAPI1.dll")]
private static extern string getDataFromTable(byte[] tablename);
static void Main(string[] args)
{
    string str = getDataFromTable(byteArray);
    Console.writeLine(str);
}

我在调用它时遇到了一个错误。我正在为WinCE 6.0创建它。
编辑------------------------
有没有类似的东西,我可以从C#向C++传递一个空缓冲区,然后C++函数将填充数据,我可以在C#中重复使用它。

在 console.writeLine(String value) 处发生了 OutOfMemory 异常。 - Rawat
1
当然,没有人会调用delete[]来释放字符串缓冲区空间。这最终会导致OOM。你不能这样做。首先尝试使用CoTaskMemAlloc()。接下来通过允许调用者提供缓冲区来取得进展,例如int getDataFromTable(char* tableName, char* buffer, int buflen)。在C#端必须使用具有足够容量的StringBuilder。 - Hans Passant
5个回答

9

我也最近遇到了这个问题,虽然我有一个解决方案可以提供给你,但是很遗憾我无法进行解释。我还没有找到一个合理的解释。

我用于检索字符串的c++代码如下:

extern "C" { __declspec(dllexport) void __GetValue__(char* str, int strlen); }

并且这是我的C#代码:

[DllImport("MyDLL.dll", CallingConvention = CallingConvention.Cdecl)]
    private static extern void __GetValue__(StringBuilder str, int strlen);

正如您所见,不必返回一个值,可以使用StringBuilder提供一个字符串,然后让C++填充数据,如下:

void __GetValue__(char* str, int strlen) {
    std::string result = "Result";

    result = result.substr(0, strlen);

    std::copy(result.begin(), result.end(), str);
    str[std::min(strlen-1, (int)result.size())] = 0;
}

为了完整起见,以下是请求字符串的C#代码:
public String GetValue() {
    StringBuilder str = new StringBuilder(STRING_MAX_LENGTH);

    __GetValue__(str, STRING_MAX_LENGTH);

    return str.ToString();
}

7
这个怎么样(注意,它假设长度正确 - 你应该传入缓冲区长度并防止溢出等):
extern "C" __declspec(dllexport)  void  __cdecl getDataFromTable(char* tableName, char* buf)
{
    std::string st = getDataTableWise(statementObject, columnIndex);
    printf(st.c_str()); 

    strcpy(buf, st.c_str());
} 

然后在C#中:

[DllImport("\\SD Card\\ISAPI1.dll")]
private static extern string getDataFromTable(byte[] tablename, byte[] buf);
static void Main(string[] args)
{
    byte[] buf = new byte[300];
    getDataFromTable(byteArray, buf);
    Console.writeLine(System.Text.Encoding.ASCII.GetString(buf));
}

注意,这是基于你的C++应用程序字符编码不是unicode的一些假设。如果它们是unicode,请使用UTF16代替ASCII。


7
“private static extern string getDataFromTable” 应该改为 “private static extern void getDataFromTable” 吗? - orion elenzil

4
在Windows系统中,您可以直接使用BSTR (从windows.h包含的wtypes.h中)返回字符串。可以通过SysAllocString函数将标准宽字符串创建为BSTR
_declspec(dllexport) BSTR getDataFromTable(const char* tableName)
{
    return SysAllocString(L"String to return");
}

然后在 C# 中,您将返回类型调整为 BStr

[DllImport("\\SD Card\\ISAPI1.dll", CallingConvention = CallingConvention.Cdecl )]
[return: MarshalAs(UnmanagedType.BStr)]
static extern string getDataFromTable(string tableName);

MarshalAs(UnmanagedType.BStr) 对我很有效。谢谢。 - Joe Lau

1

.NET运行时使用Unicode(wchar_t)字符串,而非ASCII(char),因此需要进行一些更改。 此外,您还应考虑到.NET无法释放由C / C ++应用程序分配的字符串,因此预先在C#中分配缓冲区并传递它是管理此过程的唯一安全方法,以避免内存泄漏或更糟。


这并不完全准确 - 在C/C++中有几种分配和返回字符串的方法,如果你在.NET中正确地进行了封送,它们将被正确释放,例如在Bohdan的回答中使用MarshalAs(UnmanagedType.BStr)。 - undefined

0

1
你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心找到有关如何编写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接