【问题标题】:Critical error detected c0000374 - C++ dll returns pointer off allocated memory to C#检测到严重错误 c0000374 - C++ dll 将指针从分配的内存返回给 C#
【发布时间】:2014-06-21 16:15:29
【问题描述】:

我有一个 c++ dll,它为我的主要 c# 应用程序提供一些功能。 在这里,我尝试读取一个文件,将其加载到内存中,然后将一些信息(例如指向加载数据的指针和内存块的计数)返回给 c#。 Dll 成功将文件读取到内存,但在返回主应用程序时,由于堆损坏导致程序崩溃(检测到严重错误 c0000374)。

代码非常简单明了,我之前做过一些类似的事情没有问题,但是我不知道是什么造成了这里的问题,我尝试使用“new、malloc 和 GlobalAlloc”分配内存,但也没有帮助。代码如下:

C++ MyDll:

typedef unsigned long         U32;

extern "C" __declspec(dllexport) int ReadFile(LPSTR Path, U32** DataPtr, U32* Count)
{
   FILE *fp;
   U32 *Data;
   CString tempStr(Path);
   long fSize;

   if(!(fp = fopen(tempStr, "rb"))) {
    return 0;
   }

   // Obtain File Size;
   fseek(fp, 0, SEEK_END);
   fSize =  ftell(fp);
   rewind(fp);

   Data = (U32 *)GlobalAlloc(0, fSize);
   if(Data == NULL) {
            fclose(fp);
            return -1;
    }

    // Copy file into the buffer.
        if(!(*Count = fread(Data, sizeof(U32), fSize / sizeof(U32), fp))) {
           fclose(fp);
           free(Data);
           return -2;
        }

   *DataPtr = (U32 *)Data;
       return 1;
}

C# 应用程序:

        [DllImport(@"MyDll.dll", CallingConvention= CallingConvention.Cdecl)]
    private static extern int ReadFile([MarshalAs(UnmanagedType.LPStr)]string Path, out IntPtr dataPtr, out uint Count);

private void readDump(string Path)
{
    uint count = 0;
    IntPtr Data = new IntPtr();

   try{
       if(ReadFile(Path, out Data, out count) == 1) //The Program crashes just right after this statement
       {
           //Do Something ...
       }
    }
    catch() {}

}

程序在调试和发布模式下都会崩溃。除非我在加载文件后在调试模式下暂停程序并在“Visual Studio 的即时窗口”中调用一些内存块。 要加载的文件大小约为 64MB,PC 上有超过 2GB 的未使用内存。

更新:我注意到,他们之前运行的一些第三方程序会因“异常代码:c0000005”而崩溃,并且在 Windows 7(主机)中发生了一些其他奇怪的事情。所以我在另一个安装的 Windows 中测试了代码,一切似乎都正常工作。所以可能它与Windows 7有关。现在我该如何解决这个问题? "sfc /scannow" 找不到任何问题。

【问题讨论】:

  • fSize / 4 是错误的,如果你使用 GCC,它就不会是 4。我假设这会走下坡路,因为您忘记了 [DllImport] 属性中的 CallingConvention 属性,它是 Cdecl。写这样的代码根本没有意义,FileStream 也会这样做。
  • 感谢您的评论,我将 "fSize / 4" 更改为 "fSize/sizeof(U32)" 和 "[DllImport(PCIiDllAddress)]" 更改为 [DllImport(PCIiDllAddress, CallingConvention= CallingConvention.Cdecl) ],但问题依然存在。我有充分的理由用 C++ 做一些工作,(这不是我的完整代码)。
  • C++ 在破坏堆时从来不会遇到太多麻烦。我猜问题出在我们看不到的代码中。在尝试与代码互操作之前,先对代码进行单元测试。
  • 您在此处看到的所有代码都崩溃了。
  • 我试图释放const char*s。在将近一年的时间里,我发现free 忽略了常量指针,所以我在内存上使用free 时并不小心,因为这两者都可能是常量,也可能不是常量。出于某种原因,free 不再忽略常量指针,而是对它们做了一些奇怪的事情。也许它试图释放可执行映像,或者它故意抛出堆损坏错误(也许它认为如果有人试图删除这种指针,一定是出了问题)。

标签: c# c++ dll dynamic-memory-allocation heap-corruption


【解决方案1】:

如果您的所有代码确实如上所示,那么我看不到问题所在。但是,当我遇到这个问题时,有时是因为 malloc/new/whatever 检测到堆损坏,通常这种损坏之前已经在程序中发生过,但是崩溃已经延迟到下一次调用 new/malloc。

如果您在执行上述操作并崩溃之前读取其他文件,或者分配或释放其他缓冲区,我会在那里寻找问题。也许在你写入缓冲区的任何地方抛出一堆断言并检查边界和你正在写的内容是否溢出。 抱歉,这不是一个具体的答案,我没有足够的代表留下这个建议作为评论。

【讨论】:

  • 实际上我不确定这个问题,但正如我在更新中所说的那样,经过这段时间和程序完美运行,我认为这个问题只是在我旧的 Windows 安装中发生,所以我重新安装了 windows,问题就消失了。
  • 这是我的问题,我之前在代码中超出了 std::vector,但对为什么 free 崩溃感到困惑
  • 我喜欢使用 gflags 和 Application Verifier 等工具来更早地检测到此类堆损坏。
【解决方案2】:

您正在分配输出数据 2 次。 一次在 C# 中作为新的 IntPtr,然后在 C++ 中作为 GlobalAlloc,然后返回由 GlobalAlloc 返回的指针。所以newintPtr返回的Pointer已经丢失了。

【讨论】:

  • 我不认为这个答案是正确的。 C# IntPtr 是一个托管对象... C++ 中的 GlobalAlloc() 在堆上分配一个数组,然后将 ptr 分配给该内存给新的 C# IntPtr 指向的指针。事实上,我写了一个测试来证明这一点。我不能真正将代码粘贴到评论中,但是将输出 IntPtr 传递给本机函数,并为其分配 U32*,就像 OP 所做的那样,完美无瑕 - 我能够读取正确的值(我在 c++ 端写的)输出并在托管 c# 端打印它们。
【解决方案3】:

我迟到了,但我走了。

我从自己的程序中收到了这个错误代码,这让我看到了这篇文章。我在 Windows 7 中设置了超出范围的数组位置,导致下一次分配使程序崩溃。我通过使用 MinGW 的 gcc 使用 -g 标志编译然后使用 gdb 运行程序发现了错误。在这里的某个地方,您读取或写入了一个无效的位置,并且下一次分配在堆损坏时开始。我通过边界检查我的迭代器解决了我的问题,但这似乎不是这里的问题。

C 程序的主要问题:

  • 您用来查找文件大小的方法没问题,但是当您为 Data 数组分配内存时,您将转换为 32 位整数数组,这有问题。转换到这个要求文件的大小以字节为单位是 4 的倍数。如果你有一个带有 FILE_SIZE % 4 == 1 的文件,那么有 3 个字节不是你分配的数据的一部分,但是当你查看时可以访问u32 数组的最后一个元素。
  • fread 是如何实现的,您应该避免写入这个超出范围的位置,但是当文件大小不是 4 的倍数时,您将丢失最后 1 到 3 个字符,因为整数除法会截断余数。
  • 您应该在退出范围之前关闭文件,包括在读取文件中的所有内容之后。

解决方案:

  • 对此的解决方案可能是将数组大小四舍五入到最接近的 4 的倍数。然后保证不会访问从 [0] 到 [fSize - 1] 范围之外的位置。
  • 如果试图复制文件的最后一个字节会导致致命错误,除了使用字符数组四舍五入最接近的 4 倍数,然后逐字节读取之外,我没有合适的解决方案。阅读完所有内容后,您可以转换为 u32,因为 C 允许转换。

该版本的软件在强制转换时可能做了一些额外的工作,因此它写入了一个超出范围的位置,或者 C# 做了一些额外的分配调用来做同样的事情(我不熟悉 C# 的编译方式以及它可能强加的指令更改)。

查找下一个4的倍数的一些代码:

size_t diff, rfSize = fSize; /* size_t is preferable for array sizes and indexes, 
                              * but matching to fSize's data type will work and
                              * ensures no truncation occurs. */

/* Only if this is not already a multiple of 4 */
if (diff = fSize % 4)
  /* Mod gives the remainder by division by 4, which is also the difference between 
   * fSize and the next multiple of 4. */
  rfSize= fSize + diff;

【讨论】:

    猜你喜欢
    • 2012-02-02
    • 1970-01-01
    • 1970-01-01
    • 2011-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-30
    相关资源
    最近更新 更多