.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。
我目前正在将我们公司的 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。