Использование pinvoke в c # для вызова sprintf и друзей на 64-битной - PullRequest
2 голосов
/ 19 марта 2010

У меня интересная проблема с использованием pinvoke в C # для вызова _snwprintf. Это работает для целочисленных типов, но не для чисел с плавающей запятой.

Это на 64-битной Windows, отлично работает на 32-битной.

Мой код приведен ниже, имейте в виду, что это надуманный пример, демонстрирующий поведение, которое я наблюдаю.

class Program
{
    [DllImport("msvcrt.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
    private static extern int _snwprintf([MarshalAs(UnmanagedType.LPWStr)] StringBuilder str, IntPtr length, String format, int p);

    [DllImport("msvcrt.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
    private static extern int _snwprintf([MarshalAs(UnmanagedType.LPWStr)] StringBuilder str, IntPtr length, String format, double p);

    static void Main(string[] args)
    {
        Double d = 1.0f;
        Int32 i = 1;
        Object o = (object)d;
        StringBuilder str = new StringBuilder(32);

        _snwprintf(str, (IntPtr)str.Capacity, "%10.1lf", (Double)o);
        Console.WriteLine(str.ToString());

        o = (object)i;
        _snwprintf(str, (IntPtr)str.Capacity, "%10d", (Int32)o);
        Console.WriteLine(str.ToString());

        Console.ReadKey();
    }
}

Вывод этой программы

   0.0
     1

Он должен напечатать 1.0 в первой строке, а не 0.0, и пока я в замешательстве.

Ответы [ 5 ]

6 голосов
/ 19 марта 2010

Я не совсем уверен, почему ваши звонки не работают, но защищенные версии этих методов работают правильно как в x86, так и в x64.

Следующий код работает, как и ожидалось:

class Program
{
    [DllImport("msvcrt.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
    private static extern int _snwprintf_s([MarshalAs(UnmanagedType.LPWStr)] StringBuilder str, IntPtr bufferSize, IntPtr length, String format, int p);

    [DllImport("msvcrt.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
    private static extern int _snwprintf_s([MarshalAs(UnmanagedType.LPWStr)] StringBuilder str, IntPtr bufferSize, IntPtr length, String format, double p);

    static void Main(string[] args)
    {
        // Preallocate this to a given length
        StringBuilder str = new StringBuilder(100);
        double d = 1.4;
        int i = 7;
        float s = 1.1f;

        // No need for box/unbox
        _snwprintf_s(str, (IntPtr)100, (IntPtr)32, "%10.1lf", d);
        Console.WriteLine(str.ToString());

        _snwprintf_s(str, (IntPtr)100, (IntPtr)32, "%10.1f", s);
        Console.WriteLine(str.ToString());

        _snwprintf_s(str, (IntPtr)100, (IntPtr)32, "%10d", i);
        Console.WriteLine(str.ToString());

        Console.ReadKey();
    }
}
3 голосов
/ 19 марта 2010

Это возможно с недокументированным ключевым словом __arglist:

using System;
using System.Text;
using System.Runtime.InteropServices;

class Program {
    [DllImport("msvcrt.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
    private static extern int _snwprintf(StringBuilder str, int length, String format, __arglist);

    static void Main(string[] args) {
        Double d = 1.0f;
        Int32 i = 1;
        String s = "nobugz";
        StringBuilder str = new StringBuilder(666);

        _snwprintf(str, str.Capacity, "%10.1lf %d %s", __arglist(d, i, s));
        Console.WriteLine(str.ToString());
        Console.ReadKey();
    }
}

Пожалуйста, не используйте это.

0 голосов
/ 19 марта 2010

Взгляните на эти две статьи:

http://www.codeproject.com/Messages/2840231/Alternative-using-MSVCRT-sprintf.aspx (на самом деле это примечание к статье CodeProject)

http://bartdesmet.net/blogs/bart/archive/2006/09/28/4473.aspx

0 голосов
/ 19 марта 2010

Попробуйте MarshalAs R8 (то есть для действительных / плавающих 8 (что двойное число)) с последним параметром во второй функции.

0 голосов
/ 19 марта 2010

uint составляет 32 бита. Параметр длины snprintf равен size_t, что составляет 64 бита в 64-битных процессах. Измените второй параметр на IntPtr, который является ближайшим эквивалентом .NET для size_t.

Кроме того, вам необходимо предварительно выделить StringBuilder. В настоящее время у вас переполнение буфера.

...