Menggunakan pinvoke di c# untuk memanggil sprintf dan teman-teman di 64-bit

Saya mengalami masalah menarik saat menggunakan pinvoke di C# untuk memanggil _snwprintf. Ini berfungsi untuk tipe integer, tetapi tidak untuk bilangan floating point.

Ini pada Windows 64-bit, berfungsi dengan baik pada 32-bit.

Kode saya di bawah, harap diingat bahwa ini adalah contoh yang dibuat untuk menunjukkan perilaku yang saya lihat.

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();
    }
}

Output dari program ini adalah

   0.0
     1

Seharusnya mencetak 1,0 pada baris pertama dan bukan 0,0, dan sejauh ini saya bingung.


person WildCrustacean    schedule 19.03.2010    source sumber
comment
Setan macam apa yang dapat mengarahkan Anda ke pinvoke keluarga printf runtime C? Konvensi pemanggilan C, argumen variabel, dll yang diterapkan non-OS, seperti membuat badai yang sempurna!   -  person Remus Rusanu    schedule 19.03.2010
comment
Bukankah string.Format cukup baik untuk Anda?   -  person zneak    schedule 19.03.2010
comment
@Remus - Saya menyadari bahwa ini bukanlah hal yang baik untuk dilakukan dan menimbulkan masalah, namun terkadang kita semua harus hidup dengan kode yang ada. Selain itu, meskipun betapa mengerikannya hal itu, bagi saya tampaknya hal itu tetap berhasil.   -  person WildCrustacean    schedule 19.03.2010
comment
@zneak - Saya ingin menggunakan string.Format daripada ini, tetapi dalam hal ini saya harus menggunakan penentu format gaya printf. Jika ini tidak berhasil, solusi lain yang saya pertimbangkan adalah menulis fungsi untuk mengonversi dari string format printf ke string c#, dan kemudian menggunakan string.Format.   -  person WildCrustacean    schedule 19.03.2010
comment
@bde: ah, begitu. Meskipun mengurai string format gaya printf ke string format C# mungkin membuatnya lebih portabel daripada PINvoke.   -  person zneak    schedule 19.03.2010
comment
@ semuanya, saya setuju dengan bde, di sini kita harus mencoba membantunya, dengan persetujuan bahwa tentu saja dia tahu apa yang dia lakukan, terkadang Anda tidak bisa menghabiskan ribuan dolar untuk menulis ulang seluruh aplikasi, tetapi kita harus cukup pintar untuk menghemat waktu untuk tulis tambalan di perangkat lunak kami.   -  person Akash Kava    schedule 19.03.2010
comment
Ini adalah masalah yang menarik, meskipun ini bukan sesuatu yang dibutuhkan banyak orang. +1.   -  person Robert Fraser    schedule 19.03.2010


Jawaban (5)


Saya tidak begitu yakin mengapa panggilan Anda tidak berfungsi, tetapi versi aman dari panggilan ini metode berfungsi dengan baik di x86 dan x64.

Kode berikut berfungsi, seperti yang diharapkan:

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();
    }
}
person Reed Copsey    schedule 19.03.2010
comment
Itu tidak berhasil. Masalah yang sama terjadi jika saya menggunakan Single, bukan Double. Saya akan mengubah contoh untuk menggunakan lf. Lihat komentar utama mengapa saya mencoba melakukan ini. - person WildCrustacean; 19.03.2010
comment
@bde: Saya baru saja mengulang jawaban saya. Kode yang diposting di sana berfungsi di x86 dan x64, tanpa masalah. - person Reed Copsey; 19.03.2010

Hal ini dimungkinkan dengan kata kunci __arglist yang tidak berdokumen:

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();
    }
}

Tolong jangan gunakan itu.

person Hans Passant    schedule 19.03.2010
comment
Maaf untuk duplikat postingan, tidak melihat __arglist Anda saat memposting jawaban saya :) - person Alex F; 19.03.2010
comment
Saya sebenarnya sudah mencobanya sebelumnya hari ini, dan saya tidak berhasil. Semua kode contoh ini bagi saya adalah mencetak string format. Mungkin saya melewatkan sesuatu di sini. Saya setuju bahwa saya lebih suka tidak menggunakan metode ini. - person WildCrustacean; 19.03.2010
comment
@bde: Proyek + Properti, tab Aplikasi, Target Platform = x86. Itulah bahayanya. - person Hans Passant; 19.03.2010
comment
itu memperbaikinya, tapi itu aneh. Jadi rahasia __arglist tidak didukung untuk x64? - person WildCrustacean; 19.03.2010
comment
@bde: interpretasi saya adalah __arglist tidak dapat membuat konvensi panggilan x64 dengan benar. Ini rumit karena 4 argumen pertama diteruskan dalam register. - person Hans Passant; 19.03.2010

uint adalah 32 bit. Parameter panjang snprintf adalah size_t, yaitu 64 bit dalam proses 64 bit. Ubah parameter kedua menjadi IntPtr, yang merupakan setara .NET terdekat dengan size_t.

Selain itu, Anda perlu mengalokasikan terlebih dahulu StringBuilder Anda. Saat ini Anda mengalami buffer overrun.

person Ben Voigt    schedule 19.03.2010
comment
Mencobanya, masalah yang sama. Saya akan memperbarui contoh saya, tetap harus menggunakan IntPtr. - person WildCrustacean; 19.03.2010

Coba MarshalAs R8 (Itu untuk real/floating 8 (yang ganda)) pada parameter terakhir di fungsi kedua.

person Akash Kava    schedule 19.03.2010
comment
Saya baru saja membaca bantuan msdn, parameter ke-2 yang diharapkan dalam bentuk array parameter, saya tidak mengetahui cara mengatur ini ke array parameter c, bahkan jika Anda mengirimkan parameter Anda sebagai ganda, mungkin runtime tidak mengharapkannya di bentuk yang sama, saya bertanya-tanya bagaimana int bekerja dan double juga harus berfungsi. coba kirimkan sebagai item pertama dalam array ganda di parameter terakhir dan coba masukkan params. - person Akash Kava; 19.03.2010
comment
Vararg C dan C++ sangat berbeda di bawahnya dari array parameter .NET. sprintf pasti menggunakan nilai pass-by. - person Ben Voigt; 19.03.2010

Lihatlah dua artikel ini:

http://www.codeproject.com/Messages/2840231/Alternative-using-MSVCRT-sprintf.aspx (ini sebenarnya adalah catatan untuk artikel CodeProject)

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

person Alex F    schedule 19.03.2010