c# - 使用 DllImport 将返回的 char* 从 C++ 传递到 C#

标签 c# c++ return dllimport

我是 C# WPF 和 C++ 的新手。

最近,我得到一个外部.dll,它返回一个char*,我想通过DllImport在C#中接收返回值。然后,使用 str.Split(';') 来分隔字符。为此,我创建了一个按钮,当我单击该按钮时,它会显示我在标签上拆分的字符串中的第一个字符。

因此,我使用 IntPtr 接收来自 C++ .dll 的 char*,并调用 Marshal.PtrToStringAnsi()把它变成一个字符串。但是,当我执行代码时,它有时会工作但有时会崩溃。然后错误代码总是显示

Unhandled exception at 0x00007FFC06839269 (ntdll.dll) in UITest.exe: 0xC0000374: heap corruption  (parameters: 0x00007FFC068A27F0).

我认为我的代码是合理的,我找不到根本原因。谁能帮我?谢谢!

下面是.dll中的内容以及我在C#中使用的代码。

  • Dlltest.h 中的 C++ 代码:

    #define DLL_EXPORT extern "C" __declspec(dllexport)
    char* getRbtData = nullptr;
    DLL_EXPORT char* func_getRbtData();
    
  • Dlltest.cpp 中的 C++ 代码:

    char* func_getRbtData()
    {
       getRbtData = new char(128);
       memset(getRbtData, 0, strlen(getRbtData));
       char* _getRbtData = "1.0;23.0;55.0;91.0;594.0;";
       memcpy(getRbtData, _getRbtData, strlen(_getRbtData));
       return getRbtData;
    };
    
  • UITest.xaml.cs 中的 C# 代码:

    [DllImport("DllTest.dll",EntryPoint = "func_getRbtData", CharSet = CharSet.Ansi)]
    
    public static extern IntPtr func_getRbtData();
    
    string[] words;
    private void btn_test_Click(object sender, RoutedEventArgs e)
    {
        IntPtr intptr = func_getRbtData();
        string str = Marshal.PtrToStringAnsi(intptr);
        words = str.Split(';');
        lb_content.Content = words[1];
    }
    

最佳答案

您的代码有几个问题。

在 C++ 方面,您的 DLL 函数实现完全错误:

  • getRbtData = new char(128);

    您分配的是 单个 char,其值为 128,而不是 128 个 char数组。为此,您需要使用 new char[128]

  • memset(getRbtData, 0, strlen(getRbtData));

    getRbtData 不是指向空终止字符串的指针,因此 strlen(getRbtData)未定义的行为。它在计算长度的同时读入周围的内存,直到在内存中找到一个随机的 0x00 字节。

    然后随后的 memset() 进入 getRbtData 将覆盖周围的内存。如果它不直接崩溃。

  • char* _getRbtData = "1.0;23.0;55.0;91.0;594.0;";

    在 C++11 之前,这个赋值是可以的,但不鼓励。在 C++11 及更高版本中,此赋值实际上是非法的,无法编译。

    字符串文字是只读数据,因此您需要在指针类型中使用 const char 而不是 char。即使在较旧的编译器中,您也应该这样做。

  • memcpy(getRbtData, _getRbtData, strlen(_getRbtData));

    strlen(_getRbtData) 可以,因为 _getRbtData 是一个指向空终止字符串的指针。

    但是,由于 getRbtData 没有分配足够的内存来接收所有复制的 charmemcpy() 进入 getRbtData 也是未定义的行为,如果不是彻底崩溃的话,它会破坏内存。

  • 返回getRbtData;

    这可以将指针传递给 C#。

    但是,由于内存是用new分配的(更好的是new[]),它需要用delete释放( delete[]),您没有这样做。所以你正在泄漏内存。

    C# 端的

    Marshal.PtrToStringAnsi() 不会(也不能)为您释放的内存。因此,您的 C# 代码需要将指针传递回 DLL,以便它可以正确地删除内存。

    否则,您将需要使用 Win32 API LocalAlloc()CoTaskMemAlloc() 函数分配内存,以便 Marshal 类可以在 C# 端使用以直接释放内存,而无需将其传回 DLL。

在 C# 方面,您在 DllImport 语句中使用了错误的调用约定。默认为 StdCall 以与大多数 Win32 API 函数兼容。但是您的 DLL 函数根本没有指定任何调用约定。大多数 C/C++ 编译器将默认为 __cdecl,除非配置不同。

话虽如此,试试这个:

Dlltest.h

#define DLL_EXPORT extern "C" __declspec(dllexport)
DLL_EXPORT char* func_getRbtData();
DLL_EXPORT void func_freeRbtData(char*);

Dlltest.cpp

char* func_getRbtData()
{
    const char* _getRbtData = "1.0;23.0;55.0;91.0;594.0;";
    int len = strlen(_getRbtData);
    char *getRbtData = new char[len+1];
    // alternatively:
    /*
    char *getRbtData = (char*) LocalAlloc(LMEM_FIXED, len+1);
    if (!getRbtData) return NULL;
    */
    memcpy(getRbtData, _getRbtData, len+1);
    return getRbtData;
}

void func_freeRbtData(char *p)
{
    delete[] p;
    // alternatively:
    // LocalFree((HLOCAL)p);
}

UITest.xaml.cs

[DllImport("DllTest.dll", EntryPoint = "func_getRbtData", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr func_getRbtData();

[DllImport("DllTest.dll", EntryPoint = "func_freeRbtData", CallingConvention = CallingConvention.Cdecl)]
public static extern void func_freeRbtData(IntPtr p);

string[] words;
private void btn_test_Click(object sender, RoutedEventArgs e)
{
    IntPtr intptr = func_getRbtData();
    string str = Marshal.PtrToStringAnsi(intptr);
    func_freeRbtData(intptr);
    // alternatively:
    // Marshal.FreeHGlobal(intptr);
    words = str.Split(';');
    lb_content.Content = words[1];
}

关于c# - 使用 DllImport 将返回的 char* 从 C++ 传递到 C#,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59358169/

相关文章:

c# - 访问冲突异常之谜

java - 需要在Java中引用和使用C# dll

c# - 包含 foreach 语句的方法只有一个返回值

c++ - boost::geometry:for_each_segment 怎么做?

c++ - 在没有系统调用(管道)的线程之间使用 std::istream 和 std::ostream

c++ - 如何从单个C++ return语句返回多个值中的一个?

c++ - 无法将字符串返回给主函数?

c# - 有没有办法告诉 Visual Studio 在发布的结果中包含子文件夹的配置文件?

c# - 如何加快向 ListView 添加项目的速度?

c++ - 在Visual C++中调试代码时获取argc为零和argv为null