从 C# Windows 应用程序调用 C dll 会导致 svchost.exe 崩溃

Calling a C dll from C# windows application causes the svchost.exe to crash

本文关键字:svchost exe 崩溃 dll Windows 应用程序 调用      更新时间:2023-10-16

我创建了一个C DLL,以便我可以在C#应用程序中使用它。
我在C++测试应用程序上测试了DLL,它工作正常,但它在 C# 应用程序中不起作用。
由于某种原因,我无法构建DLL的调试版本,因此我也无法在调试模式下运行C#应用程序。
DLL调试配置找不到include directories,就像在发布模式下一样,它工作得很好!
我需要说的是,我在下面给出了一个特定的方法,它会导致崩溃,从DLL调用其他方法很好,并且可以按预期工作。 这是主要的实现:标头定义:

//use this function to classify an image
CDLL_API const char* Classify(const char* img_path, int N = 2);

.cpp实施

CDLL_API const char* Classify(const char * img_path, int N)
{
auto classifier = reinterpret_cast<Classifier*>(GetHandle());
std::vector<PredictionResults> result = classifier->Classify(std::string(img_path), N);
std::string str_info = "";
std::stringstream ss;
for (size_t i = 0; i <result.size(); ++i)
{
auto label = result[i].label;
auto acc = result[i].accuracy;
ss << "label=" << label << ",acc=" << acc << "|";
}
return ss.str().c_str();
}

C# 代码:

[DllImport(@"CDll.dll", CallingConvention = CallingConvention.Cdecl)]
static extern string Classify([MarshalAs(UnmanagedType.LPStr)]string img_path,int N = 2);
//...
var s = Classify(txtFilePath.Text, 2);
MessageBox.Show(s);

所以我完全没有想到真正的原因是什么。

我看到您在 C# PInvoke 声明中指定了要Cdecl(CallingConvention = CallingConvention.Cdecl( 的调用约定;由于这也是C++代码中的默认调用约定,因此在这种情况下不应有任何调用约定不匹配。虽然,请注意,C 接口 DLL 的常见调用约定是__stdcall

我看到的问题是你从 C 接口 API 返回字符串的方式

CDLL_API const char* Classify(const char * img_path, int N)
{
...
return ss.str().c_str();
}

(顺便说一句,我认为ss类似于std::ostringstream对象。

使用输出字符串流(调用其str方法(生成字符串,然后获得调用c_str的原始 C 样式字符串指针。但是当函数退出时,字符串对象将被销毁,因此 C 样式的原始字符串指针不再有效。

若要将字符串从 C 接口 DLL API 返回到 C#,可以考虑以下选项之一:

  1. 从 C 接口 DLL返回BSTR字符串。使用SysAllocString从原始 C 样式字符串指针创建BSTR对象。请注意,BSTR"自然"存储Unicode UTF-16编码的字符串,因此请确保将字符串转换为此编码。CLR 能够很好地管理BSTR字符串,因此您不必注意释放字符串内存:这将是 CLR 的工作。

  2. 向 C 接口 DLL 函数添加几个参数:指向缓冲区的指针缓冲区大小。这将是一个输出字符串缓冲区,由调用方(例如 C#(分配,从 DLL 导出的 C 接口 API 会将结果字符串写入该调用方提供的缓冲区。例如,这就是 Win32 APIGetWindowText所做的(在 C# 端,输出字符串缓冲区可以由StringBuilder对象表示(。

C# 中的string类型与 C 中的const char *不兼容。您必须使用StringBuilder

[DllImport("aCDLL.dll")]
public extern static void getabuilder(StringBuilder abuilder);

在 C dll 中:

extern "C" void __declspec(dllexport) __stdcall getabuilder(char *abuilder);

如果您不喜欢 StringBuilder,可以将字符串字符存储在用 C# 初始化并传递给 C 函数的byte数组中:

[DllImport("aCDLL.dll")]
public extern static void getastring(byte[] data, ref int datalength);

在 C 中:

extern "C" void __declspec(dllexport) __stdcall getastring(const char *data, int *datalength);