从 C# windows 应用程序调用 C dll 导致 svchost.exe 崩溃
Calling a C dll from C# windows application causes the svchost.exe to crash
我创建了一个 C DLL
以便我可以在我的 C#
应用程序中使用它。
我在 C++
测试应用程序上测试了 DLL
,它工作正常,但它在 C# 应用程序中不起作用。
出于某种原因,我无法构建 DLL
的调试版本,因此我也无法在调试模式下 运行 C#
应用程序。
DLL
调试配置找不到 include directories
,而在发布模式下,它工作得很好!
我需要说的是,我在下面给出了一个导致崩溃的特定方法,从 DLL
调用其他方法很好并且按预期工作。
这是主要实现:
header 定义:
//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# 中的 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,您可以将字符串字符存储在 byte
的数组中,在 C# 中初始化并传递给 C 函数:
[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);
我看到您在 C# PInvoke 声明中将调用约定指定为 Cdecl
(CallingConvention = CallingConvention.Cdecl
);因为这也是 C++ 代码中的默认调用约定,所以在这种情况下你不应该有任何调用约定不匹配。不过,请注意 C 接口 DLL 的通用调用约定是 __stdcall
.
我看到的问题是你 return 来自 C 接口的字符串的方式 API
CDLL_API const char* Classify(const char * img_path, int N)
{
...
return ss.str().c_str();
}
(顺便说一句,我假设 ss
类似于 std::ostringstream
对象。)
你使用输出字符串流构建一个字符串(调用它的 str
方法),然后你得到一个原始的 C 风格字符串指针调用 c_str
。但是当函数退出时,字符串对象被销毁,所以C风格的原始字符串指针不再有效。
到return从C接口DLLAPIs到C#的字符串,可以考虑以下选项之一:
Return 来自 C 接口 DLL 的 BSTR
字符串 。使用 SysAllocString
从原始 C 风格字符串指针创建 BSTR
对象。请注意 BSTR
s "naturally" 存储 Unicode UTF-16 编码的字符串,因此请确保将您的字符串转换为此编码。 CLR 能够很好地管理 BSTR
字符串,因此您不必注意释放字符串内存:这将是 CLR 的工作。
向 C 接口 DLL 函数添加几个参数:指向缓冲区的指针和缓冲区大小。这将是一个 输出字符串缓冲区 ,由调用者(例如 C#)分配,并且从 DLL 导出的 C 接口 API 会将结果字符串写入该缓冲区调用者提供的缓冲区。这就是例如GetWindowText
Win32 API 可以(在 C# 端,输出字符串缓冲区可以用 StringBuilder
对象表示)。
我创建了一个 C DLL
以便我可以在我的 C#
应用程序中使用它。
我在 C++
测试应用程序上测试了 DLL
,它工作正常,但它在 C# 应用程序中不起作用。
出于某种原因,我无法构建 DLL
的调试版本,因此我也无法在调试模式下 运行 C#
应用程序。
DLL
调试配置找不到 include directories
,而在发布模式下,它工作得很好!
我需要说的是,我在下面给出了一个导致崩溃的特定方法,从 DLL
调用其他方法很好并且按预期工作。
这是主要实现:
header 定义:
//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# 中的 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,您可以将字符串字符存储在 byte
的数组中,在 C# 中初始化并传递给 C 函数:
[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);
我看到您在 C# PInvoke 声明中将调用约定指定为 Cdecl
(CallingConvention = CallingConvention.Cdecl
);因为这也是 C++ 代码中的默认调用约定,所以在这种情况下你不应该有任何调用约定不匹配。不过,请注意 C 接口 DLL 的通用调用约定是 __stdcall
.
我看到的问题是你 return 来自 C 接口的字符串的方式 API
CDLL_API const char* Classify(const char * img_path, int N) { ... return ss.str().c_str(); }
(顺便说一句,我假设 ss
类似于 std::ostringstream
对象。)
你使用输出字符串流构建一个字符串(调用它的 str
方法),然后你得到一个原始的 C 风格字符串指针调用 c_str
。但是当函数退出时,字符串对象被销毁,所以C风格的原始字符串指针不再有效。
到return从C接口DLLAPIs到C#的字符串,可以考虑以下选项之一:
Return 来自 C 接口 DLL 的
BSTR
字符串 。使用SysAllocString
从原始 C 风格字符串指针创建BSTR
对象。请注意BSTR
s "naturally" 存储 Unicode UTF-16 编码的字符串,因此请确保将您的字符串转换为此编码。 CLR 能够很好地管理BSTR
字符串,因此您不必注意释放字符串内存:这将是 CLR 的工作。向 C 接口 DLL 函数添加几个参数:指向缓冲区的指针和缓冲区大小。这将是一个 输出字符串缓冲区 ,由调用者(例如 C#)分配,并且从 DLL 导出的 C 接口 API 会将结果字符串写入该缓冲区调用者提供的缓冲区。这就是例如
GetWindowText
Win32 API 可以(在 C# 端,输出字符串缓冲区可以用StringBuilder
对象表示)。