ใช้ pinvoid ใน c# เพื่อโทรหา sprintf และเพื่อน ๆ บน 64 บิต

ฉันมีปัญหาที่น่าสนใจกับการใช้ pinvoid ใน C# เพื่อโทร _snwprintf ใช้งานได้กับประเภทจำนวนเต็ม แต่ไม่ใช่กับตัวเลขทศนิยม

นี่คือบน Windows 64 บิต มันทำงานได้ดีบน 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 และจนถึงตอนนี้ฉันก็นิ่งงัน


person WildCrustacean    schedule 19.03.2010    source แหล่งที่มา
comment
ปีศาจชนิดใดที่สามารถกระตุ้นให้คุณกระตุ้นตระกูล C runtime printf ได้ แบบแผนการเรียก C, อาร์กิวเมนต์ที่แปรผัน, dll ที่ไม่ใช่ระบบปฏิบัติการ, มันเหมือนกับการสร้างพายุที่สมบูรณ์แบบ!   -  person Remus Rusanu    schedule 19.03.2010
comment
string.Format ไม่ดีพอสำหรับคุณใช่ไหม   -  person zneak    schedule 19.03.2010
comment
@Remus - ฉันรู้ว่านี่ไม่ใช่เรื่องดีที่จะทำและกำลังถามหาปัญหา แต่บางครั้งเราทุกคนก็ต้องอยู่กับโค้ดที่มีอยู่ นอกจากนี้ ถึงแม้มันจะแย่แค่ไหน แต่สำหรับฉันแล้วก็ยังดูเหมือนว่ามันควรจะได้ผล   -  person WildCrustacean    schedule 19.03.2010
comment
@zneak - ฉันชอบที่จะใช้ string.Format แทน แต่ในกรณีนี้ ฉันต้องใช้กับตัวระบุรูปแบบ printf-style หากวิธีนี้ใช้ไม่ได้ผล วิธีแก้ไขปัญหาอื่นที่ฉันกำลังพิจารณาคือการเขียนฟังก์ชันเพื่อแปลงจากสตริงรูปแบบ printf ไปเป็น c# จากนั้นใช้ string.Format   -  person WildCrustacean    schedule 19.03.2010
comment
@bde: อ่าฉันเห็นแล้ว แม้ว่าการแยกวิเคราะห์สตริงรูปแบบ printf ไปเป็นสตริงรูปแบบ C# อาจทำให้พกพาสะดวกกว่า PInrigg   -  person zneak    schedule 19.03.2010
comment
@ ทุกคน ฉันเห็นด้วยกับ bde ที่นี่เราควรพยายามช่วยเขา โดยมีข้อตกลงว่าเขารู้ว่าเขากำลังทำอะไรอยู่ บางครั้งคุณไม่สามารถใช้เงินหลายพันดอลลาร์เพื่อเขียนแอปพลิเคชันทั้งหมดใหม่ได้ แต่เราต้องฉลาดพอที่จะประหยัดเวลา เขียนแพตช์ในซอฟต์แวร์ของเรา   -  person Akash Kava    schedule 19.03.2010
comment
นี่เป็นปัญหาที่น่าสนใจ แม้ว่าจะไม่ใช่สิ่งที่หลายคนต้องการก็ตาม +1.   -  person Robert Fraser    schedule 19.03.2010


คำตอบ (5)


ฉันไม่แน่ใจว่าเหตุใดการโทรของคุณจึงไม่ทำงาน แต่ เวอร์ชันที่ปลอดภัยของสิ่งเหล่านี้ วิธีการทำงานได้อย่างถูกต้องทั้งใน 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();
    }
}
person Reed Copsey    schedule 19.03.2010
comment
นั่นไม่ได้ผล ปัญหาเดียวกันนี้เกิดขึ้นถ้าฉันใช้ Single แทนที่จะเป็น Double ฉันจะเปลี่ยนตัวอย่างเพื่อใช้ lf ดูความคิดเห็นหลักว่าทำไมฉันถึงพยายามทำเช่นนี้ - person WildCrustacean; 19.03.2010
comment
@bde: ฉันเพิ่งแก้ไขคำตอบของฉันใหม่ รหัสที่โพสต์ไว้ใช้งานได้ใน x86 และ x64 โดยไม่มีปัญหา - person Reed Copsey; 19.03.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();
    }
}

กรุณาอย่าใช้สิ่งนั้น

person Hans Passant    schedule 19.03.2010
comment
ขออภัยสำหรับการโพสต์ซ้ำ ไม่เห็น __arglist ของคุณเมื่อโพสต์คำตอบของฉัน :) - person Alex F; 19.03.2010
comment
จริงๆ แล้ววันนี้ฉันได้ลองทำแบบนั้นแล้ว แต่กลับทำไม่ได้ โค้ดตัวอย่างทั้งหมดนี้ทำเพื่อฉันคือพิมพ์สตริงรูปแบบ บางทีฉันอาจพลาดบางสิ่งบางอย่างที่นี่ ฉันยอมรับว่าฉันไม่ควรใช้วิธีนี้ - person WildCrustacean; 19.03.2010
comment
@bde: โครงการ + คุณสมบัติ, แท็บแอปพลิเคชัน, เป้าหมายแพลตฟอร์ม = x86 นั่นคืออันตราย - person Hans Passant; 19.03.2010
comment
นั่นแก้ไขมันได้ แต่มันแปลกมาก ดังนั้นความลับ __arglist จึงไม่รองรับ x64 ใช่ไหม - person WildCrustacean; 19.03.2010
comment
@bde: การตีความของฉันคือ __arglist ไม่สามารถรับหลักการเรียก x64 ได้ เป็นเรื่องที่ยุ่งยากเนื่องจากอาร์กิวเมนต์ 4 ตัวแรกถูกส่งผ่านในรีจิสเตอร์ - person Hans Passant; 19.03.2010

uint คือ 32 บิต พารามิเตอร์ความยาวของ snprintf คือ size_t ซึ่งเป็น 64 บิตในกระบวนการ 64 บิต เปลี่ยนพารามิเตอร์ตัวที่สองเป็น IntPtr ซึ่งเทียบเท่ากับ .NET ที่ใกล้เคียงที่สุดกับ size_t

นอกจากนี้ คุณต้องจัดสรร StringBuilder ของคุณล่วงหน้า ขณะนี้คุณมีบัฟเฟอร์มากเกินไป

person Ben Voigt    schedule 19.03.2010
comment
พยายามแล้วปัญหาเดียวกัน ฉันจะอัปเดตตัวอย่างของฉัน แต่ควรใช้ IntPtr อยู่แล้ว - person WildCrustacean; 19.03.2010

ลองใช้ MarshalAs R8 (นั่นคือของจริง/ลอย 8 (ซึ่งเป็นสองเท่า)) กับพารามิเตอร์สุดท้ายในฟังก์ชันที่สอง

person Akash Kava    schedule 19.03.2010
comment
ฉันเพิ่งอ่านวิธีใช้ msdn พารามิเตอร์ตัวที่ 2 ที่คาดหวังจะอยู่ในรูปแบบของอาร์เรย์พารามิเตอร์ ฉันไม่ทราบวิธีการจัดเรียงสิ่งนี้กับอาร์เรย์พารามิเตอร์ของ c แม้ว่าคุณจะส่งพารามิเตอร์ของคุณเป็นสองเท่า แต่อาจทำให้รันไทม์ไม่ได้คาดหวังใน รูปแบบเดียวกัน ฉันสงสัยว่า int ทำงานอย่างไร และ double ก็ควรจะทำงานเช่นกัน ลองส่งเป็นรายการแรกในอาร์เรย์ double ในพารามิเตอร์สุดท้ายแล้วลองใส่พารามิเตอร์ - person Akash Kava; 19.03.2010
comment
varargs C และ C++ นั้นแตกต่างกันมากภายใต้อาร์เรย์พารามิเตอร์ .NET sprintf ใช้ pass-by-value อย่างแน่นอน - person Ben Voigt; 19.03.2010

ดูบทความทั้งสองนี้:

http://www.codeproject.com/Messages/2840231/Alternative-using-MSVCRT-sprintf.aspx (นี่คือหมายเหตุสำหรับบทความ CodeProject จริงๆ)

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

person Alex F    schedule 19.03.2010