0

我正在寻找一种可以快速将非托管字符串转换为托管字符串的函数。我正在看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 函数进行编码是有原因的吗?我可能错过了一些重要的东西......

4

1 回答 1

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 类:result=Console.WriteLine("Marshal 类:result={0} time={1}ms",
  s, sw.ElapsedMilliseconds); 时间=1914ms
我的实现:result=Console.WriteLine("Marshal class: result={0} time={1}ms",
  s, sw.ElapsedMilliseconds); 时间=2065ms

但有时它会反过来出现。简而言之,两者之间没有什么可以选择的。

当您删除对 的调用时IsWin32Atom,您的版本通常会获胜。但不是很多。速度通常有大约 5% 的差异。我不知道你为什么认为这Marshal.PtrToStringAnsi是“非常慢”。

我非常希望两个参数版本Marshal.PtrToStringAnsi本质上else是您的代码的子句。

注意:我的测试环境是 Win7 x64、VS2012、AnyCPU、Release。

于 2013-03-14T14:14:52.263 回答