将字符串从 .NET 传递到本机(64 位)
Passing string from .NET to native (64-bit)
我在将简单字符串从我的 .NET 应用程序(编译为 64 位)传递到我的本机 DLL(也编译为 64 位)时遇到问题。
C++ 签名:
DllExport void SetFoo(LPWSTR foo);
C# 签名:
[DllImport(Library, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
internal static extern void SetFoo(
[In][MarshalAs(UnmanagedType.LPWStr)] string foo);
然后我 运行 在 C# 中编写以下代码:
X.SetFoo("some string");
当它到达DLL中的调试器时,值在中文骂我:Ⴐ虘翺
当我将 .NET 和本机代码都更改为 32 位时,我在调试器中获得的值是正确的字符串。我哪里傻了?
最小复制为 Visual Studio 2015 解决方案:download here
重现:
- 使用 WinForms 项目创建新的 Visual Studio 解决方案。
- 将 DLL 类型的 Win32 项目添加到解决方案
- 添加以下文件:
Foo.h:
extern "C" {
__declspec( dllexport ) void SetFoo(LPWSTR);
}
Foo.cpp:
#include "stdafx.h"
#include "Foo.h"
DllExport void SetFoo(LPWSTR foo)
{
}
- 在
SetFoo
的左大括号上设置断点
- 向 winforms 窗体添加一个按钮
- 双击它,调用
SetFoo("abc123")
。
- 实施
SetFoo
:
Form1.cs:
[DllImport("Win32Project1.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern void SetFoo(string text);
- 通过打开配置管理器将应用更改为以 64 位模式构建。
- 将 Win32Project1 设置为构建为 x64
- 将 WindowsFormApplication1 设置为构建为 x64,通过选择新平台,选择 x64,确定。
- 更改 WindowsFormsApplication1 的输出目录以匹配其他应用程序的输出目录。
- 不调试就开始。
- 通过将
Attach to
设置为 Managed (v4.5, v4.0) code, Native code
并找到进程来附加调试器 (Ctrl+Alt+P)。
- 观察断点处的值。
当您将 ANSI 编码的拉丁文本解释为 UTF-16 时,您会看到汉字。这显然是正在发生的事情。因此,您的 C# 代码正在以某种方式发送 ANSI 编码的文本。
p/invoke最好这样写:
[DllImport(Library, CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Unicode)]
internal static extern void SetFoo(string foo);
不需要[in]
,默认即可。 MarshalAs
毫无意义,因为您指定了 CharSet.Unicode
。但是,这两项更改都不会影响语义。
对您在问题中描述的内容的唯一合理解释是:
- 实际代码与您描述的不一样,或者
- 调试器有缺陷。
我建议你把非托管函数改成
DllExport void SetFoo(LPWSTR foo)
{
MessageBoxW(0, L"", foo, MB_OK);
}
如果消息框显示正确的文本,那么结论似乎是调试器有缺陷。
我在将简单字符串从我的 .NET 应用程序(编译为 64 位)传递到我的本机 DLL(也编译为 64 位)时遇到问题。
C++ 签名:
DllExport void SetFoo(LPWSTR foo);
C# 签名:
[DllImport(Library, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
internal static extern void SetFoo(
[In][MarshalAs(UnmanagedType.LPWStr)] string foo);
然后我 运行 在 C# 中编写以下代码:
X.SetFoo("some string");
当它到达DLL中的调试器时,值在中文骂我:Ⴐ虘翺
当我将 .NET 和本机代码都更改为 32 位时,我在调试器中获得的值是正确的字符串。我哪里傻了?
最小复制为 Visual Studio 2015 解决方案:download here
重现:
- 使用 WinForms 项目创建新的 Visual Studio 解决方案。
- 将 DLL 类型的 Win32 项目添加到解决方案
- 添加以下文件:
Foo.h:
extern "C" {
__declspec( dllexport ) void SetFoo(LPWSTR);
}
Foo.cpp:
#include "stdafx.h"
#include "Foo.h"
DllExport void SetFoo(LPWSTR foo)
{
}
- 在
SetFoo
的左大括号上设置断点
- 向 winforms 窗体添加一个按钮
- 双击它,调用
SetFoo("abc123")
。 - 实施
SetFoo
:
Form1.cs:
[DllImport("Win32Project1.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern void SetFoo(string text);
- 通过打开配置管理器将应用更改为以 64 位模式构建。
- 将 Win32Project1 设置为构建为 x64
- 将 WindowsFormApplication1 设置为构建为 x64,通过选择新平台,选择 x64,确定。
- 更改 WindowsFormsApplication1 的输出目录以匹配其他应用程序的输出目录。
- 不调试就开始。
- 通过将
Attach to
设置为Managed (v4.5, v4.0) code, Native code
并找到进程来附加调试器 (Ctrl+Alt+P)。 - 观察断点处的值。
当您将 ANSI 编码的拉丁文本解释为 UTF-16 时,您会看到汉字。这显然是正在发生的事情。因此,您的 C# 代码正在以某种方式发送 ANSI 编码的文本。
p/invoke最好这样写:
[DllImport(Library, CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Unicode)]
internal static extern void SetFoo(string foo);
不需要[in]
,默认即可。 MarshalAs
毫无意义,因为您指定了 CharSet.Unicode
。但是,这两项更改都不会影响语义。
对您在问题中描述的内容的唯一合理解释是:
- 实际代码与您描述的不一样,或者
- 调试器有缺陷。
我建议你把非托管函数改成
DllExport void SetFoo(LPWSTR foo)
{
MessageBoxW(0, L"", foo, MB_OK);
}
如果消息框显示正确的文本,那么结论似乎是调试器有缺陷。