我是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);
您正在分配单个值为128的char
,而不是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
分配足够的内存来接收所有复制的char
,因此memcpy()
进入getRbtData
也是未定义的行为,并且如果不彻底崩溃将浪费内存。return getRbtData;
可以将指针传递给C#。
但是,由于内存是使用new
(更好的是new[]
)分配的,因此需要使用delete
(delete[]
)释放它,而您并没有这样做。因此,您正在泄漏内存。
C#端的Marshal.PtrToStringAnsi()
不会(也不能)为您释放new
的内存。因此,您的C#代码将需要将指针传递回DLL,以便它可以正确地delete
内存。
否则,您将需要使用Win32 API LocalAlloc()
或CoTaskMemAlloc()
函数分配内存,以便可以在C#端使用Marshal
类直接释放内存,而无需将其传递回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];
}