堆已损坏:调用非托管函数时

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 breakpointUnhandled 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”类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们。 错误解决一例。(代码片段

从非托管 c++ 调用 C# 函数(通过托管包装器)

从非托管 c++ 调用托管 c# 函数

从非托管 c++ 调用托管 c# 函数