来自 C# 的 mingw DLL:为啥我必须覆盖新/删除?

Posted

技术标签:

【中文标题】来自 C# 的 mingw DLL:为啥我必须覆盖新/删除?【英文标题】:mingw DLL from C#: why do I have to override new/delete?来自 C# 的 mingw DLL:为什么我必须覆盖新/删除? 【发布时间】:2018-10-03 15:30:20 【问题描述】:

我正在尝试在 Windows 10 上从 C# 调用最小的 C 函数。我使用 mingw/g++ 将 C 代码编译成 .dll

事实证明,我必须定义 opterator new[] 或使用 Visual Studio 编译 .dll。否则我的 C# 程序会因以下错误而崩溃:

The program '[14740] Test.exe' has exited with code -1073741819 (0xc0000005) 'Access violation'.

我真的很想了解这里到底发生了什么,以及如何在不覆盖所有新/删除运算符但仍使用 mingw 的情况下解决此问题。

这是重现错误的最小示例,包括解决方法(如果定义了 AddNewOperator,则将定义 operator new[] 并且生成的 .dll 将正常工作):

Test.cs(使用 Visual Studio 2017 编译/运行):

using System;
using System.Runtime.InteropServices;
class Program

    [DllImport("libTest", CallingConvention = CallingConvention.StdCall, ExactSpelling = true)]
    public static extern int TestFunction();
    static void Main(string[] args)
    
        Console.WriteLine("!!" + TestFunction());
    

使用mingw编译的Test.cpp(见下文):

#include <new>
#include <cstdlib>

#ifdef AddNewOperator // This will fix the issue
void* operator new[](std::size_t sz)
    return std::malloc(sz);

#end

extern "C" 
int __stdcall __declspec(dllexport) TestFunction() 
        int* test = new int[3]; // removing this line will make everything work when building
        return test[2];

这是构建脚本:

# Remove the following # and the compiled dll will work just fine
g++ -g -s -Wall -c -fmessage-length=0 Test.cpp  #-DAddNewOperator
g++ -g -shared -o libTest.dll *.o -Wl,--subsystem,windows

编辑: 为 x86 而不是 64 位编译所有内容也解决了这个问题(这对我来说再次没有选择)

【问题讨论】:

我的意思是如果我想使用new[]。因此,如果我想使用new[],我要么必须覆盖operator new[],要么必须使用Visual Studio 编译.dll。我更改了这句话以进行澄清。 【参考方案1】:

TL;DR

不得在编译器之间混合分配/释放!

您面临的问题非常棘手,实际上您的程序每次都应该崩溃,不管有没有void* operator new[](size_t)... 定义。

如果您调试您的程序,它实际上应该在删除您的 test 变量时崩溃。这个变量是使用 mingw 的 new 运算符创建的,但是使用 MSVC 删除运算符删除的,它们不是interoperable。所以你必须使用mingw的delete函数。

你可以做一个简单的测试:

c++ 代码:

int* test = nullptr;
int __stdcall __declspec(dllexport) TestFunction() 
    test = new int[3]; // note test is global
    return test[2];

void __stdcall _declspec(dllexport) CleanUp() 
    delete[] test;

c#代码:

public static extern int TestFunction();
public static extern int CleanUp();
static void Main(string[] args)

    Console.WriteLine("!!" + TestFunction());
    CleanUp();


为什么重新定义 new 操作符后程序不会崩溃?!

我实际上并不确定,但我认为,mingw 的 malloc 实现使用 legacy C runtime,它使用 HeapAlloc 进行分配,使用 HeapFree 删除您的 test 变量。简而言之,当您自定义定义 operator new 并在内部使用 malloc 时,您只是幸运/不幸,它没有崩溃......

但是,如果您使用 Visual Studio 编译它,则(dll 和 exe)都使用相同的运行时,因此分配/解除分配是在同一个内存空间管理器中完成的。 仍然是 UB,你会遇到问题!例如:如果您使用 msvc10 创建您的库并希望将此库与 msvc14 一起使用,那么这里也会发生同样的情况!我记得一些来自内存管理错误的错误的代码问题;我们使用了一个用 msvc11 创建的库,但我们的代码是用 msvc12 编译的...

【讨论】:

我很清楚混合来自不同编译器的 new/delete 是危险的,但是当我只使用 new[] 运算符而根本不使用 delete 时,为什么我的示例代码会崩溃?此外,当我根本没有定义任何 new/delete 运算符时,我希望使用来自同一个编译器的实现,因此不会崩溃...... @kunzej UB 是 UB。如果您的代码尝试执行标准中未涵盖的操作,那么您就无法说明会发生什么。

以上是关于来自 C# 的 mingw DLL:为啥我必须覆盖新/删除?的主要内容,如果未能解决你的问题,请参考以下文章

无论代码如何,MinGW .exe 都需要一些 gcc dll?

VC DLL 中 C++ 符号的 MinGw 未定义符号

为啥我的 C# 和 C++ dll 表现出不同的行为?

在 C# 中调试 c++ dll

C# 使用 liblas.dll(无法加载 DLL)

为啥 C# 需要注册 COM DLL 才能引用它?