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

时间:2017-08-04 09:42:01

标签: c# c++ string dll pinvoke

我创建了一个C DLL,以便我可以在C#应用程序中使用它 我在DLL测试应用程序上测试了C++,它工作正常,但在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);

所以我完全没有想到可能是什么原因。

2 个答案:

答案 0 :(得分:2)

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#中初始化的byte数组中并传递给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);

答案 1 :(得分:2)

我看到您在C#PInvoke声明中将调用约定指定为CdeclCallingConvention = 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 s&#34;自然&#34;存储 Unicode UTF-16 编码字符串,因此请确保将字符串转换为此编码。 CLR能够很好地管理BSTR字符串,因此您不必注意释放字符串内存:这将是CLR的工作。

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