Using pinvoke in c# to call sprintf and friends on 64-bit
I am having an interesting problem with using pinvoke in C# to call _snwprintf. It works for integer types, but not for floating point numbers.
This is on 64-bit Windows, it works fine on 32-bit.
My code is below, please keep in mind that this is a contrived example to show the behavior I am seeing.
class Program
{
[DllImport("msvcrt.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
private static exter开发者_JAVA百科n 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();
}
}
The output of this program is
0.0
1
It should print 1.0 on the first line and not 0.0, and so far I am stumped.
I'm not exactly sure why your calls do not work, but the secured versions of these methods do work properly in both x86 and x64.
The following code does work, as expected:
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();
}
}
It is possible with the undocumented __arglist keyword:
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();
}
}
Please don't use that.
uint is 32 bits. The length parameter of snprintf is size_t, which is 64 bits in 64 bit processes. Change the second parameter to IntPtr, which is the closest .NET equivalent of size_t.
In addition, you need to preallocate your StringBuilder. Currently you have a buffer overrun.
Try MarshalAs R8 (Thats for real/floating 8 (which is double)) on last parameter in second function.
Take a look at these two articles:
http://www.codeproject.com/Messages/2840231/Alternative-using-MSVCRT-sprintf.aspx (this is actually note to CodeProject article)
http://bartdesmet.net/blogs/bart/archive/2006/09/28/4473.aspx
精彩评论