PtrToStrAnsi更快的替代方案?

时间:2013-03-14 13:27:46

标签: c# marshalling

我正在寻找一种可以快速将非托管字符串转换为托管字符串的函数。我在看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函数的代码?我可能错过了一些重要的事情......

1 个答案:

答案 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。