.NET 4.0 char* 到字符串互操作在 x64 0xc0000374 上崩溃

.NET 4.0 char* to string Interop Crashes On x64 0xc0000374

我目前正在将我们公司的 x86 软件迁移到 x64,但遇到了障碍。当解决方案平台切换到 x64 时,我们来自非托管 dll 的方法 returns a char* 崩溃并显示 0xc0000374,我想知道原因。

C++ 本机代码:

#include "stdafx.h"
#include <windows.h>
#include "windef.h"
#define VERSION "3.3.12"

extern "C"
{
  __declspec(dllexport) char* WINAPI GetMyVersion()
  {
    return (char*)VERSION;
  }
}

C# Dll导入:

using System;
using System.Runtime.InteropServices;
using System.Security;

namespace InteropTest.Adapter
{
    [SuppressUnmanagedCodeSecurity]
    public unsafe class MyAdapter
    {
        private const string DLLName = "VersionNumber";

        private const string EntryPointName = "GetMyVersion";

        [DllImport(DLLName, EntryPoint = EntryPointName, CharSet = CharSet.Ansi)]
        internal static extern IntPtr GetMyVersionPtr();

        [DllImport(DLLName, EntryPoint = EntryPointName, CharSet = CharSet.Ansi)]
        internal static extern string GetMyVersionString();
    }
}

GetMyVersionString() 仅适用于 x86 平台并在 x64 平台上崩溃。但是,我能够获得 IntPtr 并使用 Marshal class 将其转换为 x86 和 x64 上的托管 string,如下所示:

using System.Runtime.InteropServices;
using System.Windows;
using InteropTest.Adapter;

namespace InteropTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            var ptr = MyAdapter.GetMyVersionPtr();
            var convertedString = Marshal.PtrToStringAnsi(ptr);

            // This crashes in x64 build
            var directString = MyAdapter.GetMyVersionString();

            VersionTextBlock.Text = convertedString;
        }
    }
}

我也曾尝试为 return 值明确指定 MarshalAs 属性,但没有成功。如果您没有明确使用 Marshal class,那么 64 位 char* 似乎被用作 32 位指针。这只是 .NET 中的错误还是我做错了什么?从我的角度来看,使用 Marshal class 转换应该等同于使用 MarshalAs 属性。

编辑:

这是我为复制错误而制作的示例项目: https://github.com/chenhuang444/dotNet40InteropCrash

编辑 2:

程序并以代码 0xc0000374 退出,没有本机调试,如果我打开本机调试,则仅显示 "InteropTest.exe has triggered a breakpoint",堆栈跟踪为:

    ntdll.dll!00007ffc0fdc4cfa()    Unknown
    ntdll.dll!00007ffc0fdcc806()    Unknown
    ntdll.dll!00007ffc0fdccad1()    Unknown
    ntdll.dll!00007ffc0fd69a55()    Unknown
    ntdll.dll!00007ffc0fd77347()    Unknown
    mscorlib.ni.dll!00007ffbd566759e()  Unknown
    [Managed to Native Transition]  
>   InteropTest.exe!InteropTest.MainWindow.OnLoaded(object sender, System.Windows.RoutedEventArgs e) Line 23    C#
    PresentationCore.dll!System.Windows.EventRoute.InvokeHandlersImpl(object source, System.Windows.RoutedEventArgs args, bool reRaised)    Unknown

编辑 3:

非托管 C++ 代码是我们解决方案中的一个项目,x64/x86 配置已设置,类似于上面链接的示例项目。

嗯,您的版本不是 "string",它确实是指向原始内存的指针。如果您告诉它它是一个字符串,.NET 框架将尝试释放该指针。

所以,你有两个选择:

  • 像你一样使用 IntPtr,这很好。
  • 使用 COM 分配器分配 .NET 可以理解的真实字符串,因此在您的情况下,您的 C++ 代码可以替换为:

.

__declspec(dllexport) char* WINAPI GetMyVersion() {
      char* p = (char*)CoTaskMemAlloc(7);
      CopyMemory(p, VERSION, 7);
      return p;
}

注意:您也可以使用 BSTR。