使用 DllImport 将 return char* 从 C++ 传递到 C#
Passing a return char* from C++ to C# with DllImport
我是 C# WPF 和 C++ 的新手。
最近,我得到了一个外部 .dll,return 是一个 char*
,我想通过使用 DllImport
在 C# 中接收 return 值。然后,使用 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];
}
new char(128)
returns 指向一个字符的指针,初始值为 128.
我可以告诉您如何分配 128 个字符,但主要问题是您无法清理它,所以这不是一个有用的答案。检查有关将字符串返回到 C# 的现有问题。
你的代码有几个问题。
在 C++ 方面,您的 DLL 函数实现完全错误:
getRbtData = new char(128);
您正在分配值为 128 的 单个 char
,而不是 128 char
的 array秒。为此,您需要使用 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
没有分配足够的内存来接收所有复制的 char
,所以 memcpy()
到 getRbtData
也是 未定义的行为,如果没有彻底崩溃,将会浪费内存。
return getRbtData;
将指针传递给 C# 就可以了。
然而,由于内存是用new
分配的(更好,new[]
),它需要用delete
(delete[]
)释放,你没有做。所以你正在泄漏内存。
Marshal.PtrToStringAnsi()
在 C# 方面不会(也不能)为您释放 new
的内存。因此,您的 C# 代码需要将指针传递回 DLL,以便它可以 delete
正确地使用内存。
否则需要使用Win32APILocalAlloc()
或CoTaskMemAlloc()
函数分配内存,才能使用Marshal
class在 C# 端直接释放内存,根本不将其传回 DLL。
在 C# 方面,您在 DllImport
语句中使用了错误的调用约定。为与大多数 Win32 API 函数兼容,默认值为 StdCall
。但是您的 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# WPF 和 C++ 的新手。
最近,我得到了一个外部 .dll,return 是一个 char*
,我想通过使用 DllImport
在 C# 中接收 return 值。然后,使用 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]; }
new char(128)
returns 指向一个字符的指针,初始值为 128.
我可以告诉您如何分配 128 个字符,但主要问题是您无法清理它,所以这不是一个有用的答案。检查有关将字符串返回到 C# 的现有问题。
你的代码有几个问题。
在 C++ 方面,您的 DLL 函数实现完全错误:
getRbtData = new char(128);
您正在分配值为 128 的 单个
char
,而不是 128char
的 array秒。为此,您需要使用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
没有分配足够的内存来接收所有复制的char
,所以memcpy()
到getRbtData
也是 未定义的行为,如果没有彻底崩溃,将会浪费内存。return getRbtData;
将指针传递给 C# 就可以了。
然而,由于内存是用
new
分配的(更好,new[]
),它需要用delete
(delete[]
)释放,你没有做。所以你正在泄漏内存。Marshal.PtrToStringAnsi()
在 C# 方面不会(也不能)为您释放new
的内存。因此,您的 C# 代码需要将指针传递回 DLL,以便它可以delete
正确地使用内存。否则需要使用Win32API
LocalAlloc()
或CoTaskMemAlloc()
函数分配内存,才能使用Marshal
class在 C# 端直接释放内存,根本不将其传回 DLL。
在 C# 方面,您在 DllImport
语句中使用了错误的调用约定。为与大多数 Win32 API 函数兼容,默认值为 StdCall
。但是您的 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];
}