堆已损坏:调用非托管函数时
Posted
技术标签:
【中文标题】堆已损坏:调用非托管函数时【英文标题】:A heap has been corrupted: When calling unmanaged function 【发布时间】:2019-05-21 15:50:51 【问题描述】:我正在从 C# 托管函数调用一个非托管且非常简单的 C++ 函数(位于 JNIDiskInfoDll.dll
),如下所示:
C++:
#include "stdafx.h"
#include "AtaSmart.h"
#include <iostream>
#include <string.h>
extern "C" __declspec(dllexport) char* __cdecl getSerial(LPTSTR inCStrIn)
return "abcdefg";
C#:
using System;
using System.Runtime.InteropServices;
namespace HardInfoRetriever
class DiskInfoRetreiver
[DllImport("C:\\Users\\User1\\Documents\\Visual Studio 2017\\Projects\\HardInfoRetriever\\Debug\\JNIDiskInfoDll.dll",
EntryPoint = "getSerial", CallingConvention = CallingConvention.Cdecl,
BestFitMapping = false, ThrowOnUnmappableChar = true, CharSet = CharSet.Ansi)]
public static extern String getSerial([MarshalAs(UnmanagedType.LPTStr)]String _driveletter_);
public static String getSerialNumber(String driveletter)
try
return getSerial(driveletter);
catch (Exception e)
throw e;
我的问题是,在运行应用程序后,我收到两个连续的错误,分别是 projectName.exe has triggered a breakpoint
和 Unhandled exception at 0x77110E23 (ntdll.dll) in projectName.exe: 0xC0000374: A heap has been corrupted (parameters: 0x7712E930).
。知道虽然我收到了这些错误,但该函数仍在返回所需的输出。
请注意getSerial
C 函数有LPTSTR inCStrIn
参数,因为我在删除整个代码(仅保留return "abcdefg";
)之前使用它,错误仍然存在。
我不知道这可能是什么问题。我试图将DllImport
中的Charset
更改为Unidcode
,但仍然出现相同的错误。有什么帮助吗?
【问题讨论】:
不返回指针。让它工作的可靠方法是调用者提供缓冲区,函数将字符串复制到缓冲区。这就是所有 Windows API 函数处理字符串的方式。 @paulMckenzie 感谢您的回复,您是否有任何外部链接可以提供此示例,或者您能否在可能的情况下发布答案? 以GetWindowText API函数为例。 您的getSerial
函数返回指向堆栈上某个地址的指针,该地址用“abcdefg”初始化(在 C++ 中,您必须通过 new
运算符明确指定您在堆中创建对象的意图) .显然堆栈值不会超过设置它的函数,所以在调用代码时,你会得到指向一些不相关的内存区域的指针。
这个函数很难在 C++ 程序中可靠地使用,当你调用它时不会变得更好。问题在于谁负责释放字符串的存储空间。除非阅读手册或查看源代码,否则 C++ 程序员不会知道。编组器既不能做任何事情,也假设该函数表现良好。它调用 Marshal.FreeCoTaskMem() 的等价物。 Kaboom,它没有在那个堆中分配。按原样,您需要将返回类型更改为 IntPtr 并使用 Marshal.PtrToStringAnsi()。希望它在 C++ 代码中保留为文字。
【参考方案1】:
感谢@PaulMcKnezie 的评论:
不返回指针。让它发挥作用的可靠方法是 调用者提供缓冲区,函数将字符串复制到 缓冲区。
所以我使用了与here提到的相同方法相同的概念,即关于
返回带有 BSTR * 参数的字符串。
最后,下面是我的代码的最终工作版本:
C++:
extern "C" __declspec(dllexport) HRESULT __cdecl getSerial(LPTSTR inCStrIn, BSTR* inCStrOut)
*inCStrOut= SysAllocString(L"abcdefg");
return S_OK;
C#:
using System;
using System.Runtime.InteropServices;
namespace HardInfoRetriever
class DiskInfoRetreiver
[DllImport("C:\\Users\\User1\\Documents\\Visual Studio 2017\\Projects\\HardInfoRetriever\\Debug\\JNIDiskInfoDll.dll",
EntryPoint = "getSerial", CallingConvention = CallingConvention.Cdecl,
BestFitMapping = false, ThrowOnUnmappableChar = true, CharSet = CharSet.Ansi)]
//[return: MarshalAs(UnmanagedType.LPTStr)]
public static extern int getSerial([MarshalAs(UnmanagedType.LPTStr)] string _driveletter_, [MarshalAs(UnmanagedType.BStr)] out string serial);
public static String getSerialNumber(string letter)
try
string serial;
int result = getSerial(letter, out serial);
if (result == 0)
return serial;
return null;
catch (Exception e)
throw e;
【讨论】:
以上是关于堆已损坏:调用非托管函数时的主要内容,如果未能解决你的问题,请参考以下文章
对“XXX::Invoke”类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们
类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们的问题的解决方法
对“xxx”类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们。 错误解决一例。(代码片段