我正在寻找一种可以快速将非托管字符串转换为托管字符串的函数。我在看Marshal.PtrToStringAnsi
,但它真的很慢。
我在.NET框架源代码中看到了以下定义:
public static String PtrToStringAnsi(IntPtr ptr)
{
if (Win32Native.NULL == ptr) {
return null;
}
else if (IsWin32Atom(ptr)) {
return null;
}
else {
int nb = Win32Native.lstrlenA(ptr);
if( nb == 0) {
return string.Empty;
}
else {
StringBuilder sb = new StringBuilder(nb);
Win32Native.CopyMemoryAnsi(sb, ptr, new IntPtr(1+nb));
return sb.ToString();
}
}
}
为了提高应用程序的性能,我创建了以下方法,该方法使用更快的Marshal.PtrToStringAnsi(IntPtr,int)方法。
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, EntryPoint = "lstrlenA")]
[ResourceExposure(ResourceScope.None)]
internal static extern int lstrlenA(IntPtr ptr);
static public string PtrToString( IntPtr p )
{
if (p == IntPtr.Zero)
return null;
int len = lstrlenA(p);
if (len == 0)
return string.Empty;
return Marshal.PtrToStringAnsi(p, len);
}
这种替代方案看起来要快得多。有没有理由说微软首先没有编写PtrToStringAnsi函数的代码?我可能错过了一些重要的事情......
答案 0 :(得分:1)
不同之处在于对IsWin32Atom
的调用。您的版本省略了。当你把它放回去时,你会发现你的版本与Marshal
中的版本相当。即使您取消对IsWin32Atom
的通话,性能提升也可以忽略不计。
我的测试程序版本如下所示:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Test
{
internal class Program
{
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, EntryPoint = "lstrlenA")]
[ResourceExposure(ResourceScope.None)]
internal static extern int lstrlenA(IntPtr ptr);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern System.IntPtr GetCommandLine();
private static readonly IntPtr HIWORDMASK = unchecked(new IntPtr((long)0xffffffffffff0000L));
private static bool IsWin32Atom(IntPtr ptr)
{
long num = (long)ptr;
return 0 == (num & (long)HIWORDMASK);
}
public static string PtrToString(IntPtr p)
{
if (p == IntPtr.Zero)
return null;
if (IsWin32Atom(p))
return null;
int len = lstrlenA(p);
if (len == 0)
return String.Empty;
return Marshal.PtrToStringAnsi(p, len);
}
private static void Main(string[] args)
{
var p = Marshal.StringToHGlobalAnsi("Console.WriteLine(\"Marshal class: result={0} time={1}ms\", s, sw.ElapsedMilliseconds);");
string s = "";
Stopwatch sw = new Stopwatch();
sw.Start();
for (double i = 0; i < 5000000; i++)
{
s = Marshal.PtrToStringAnsi(p);
}
sw.Stop();
Console.WriteLine("Marshal class: result={0} time={1}ms", s, sw.ElapsedMilliseconds);
sw.Restart();
for (double i = 0; i < 5000000; i++)
{
s = Program.PtrToString(p);
}
sw.Stop();
Console.WriteLine("My implementation: result={0} time={1}ms", s, sw.ElapsedMilliseconds);
Console.ReadLine();
}
}
}
运行时的运行时间变化很大。但这是一个典型的输出:
Marshal class: result=Console.WriteLine("Marshal class: result={0} time={1}ms", s, sw.ElapsedMilliseconds); time=1914ms My implementation: result=Console.WriteLine("Marshal class: result={0} time={1}ms", s, sw.ElapsedMilliseconds); time=2065ms
但有时它反过来说出来了。简而言之,两者之间没有什么可供选择的。
当您删除对IsWin32Atom
的呼叫时,您的版本会更频繁地获胜。但不是很多。通常速度差异约为5%。我不知道为什么你认为Marshal.PtrToStringAnsi
“非常慢”。
我非常希望Marshal.PtrToStringAnsi
的两个参数版本基本上是代码的else
子句。
注意:我的测试环境是Win7 x64,VS2012,AnyCPU,Release。