Я хочу написать функцию для C #, которая будет похожа на функции sprintf (3) и vsnprintf (3) в C (или аналогична команде "format" в Tcl). В настоящее время у меня есть некоторый успех с этим, но все еще есть несколько вопросов.
Ниже приведен исходный код. Класс «Формат» содержит несколько открытых функций, которые принимают список переменных аргументов (но не более 7 аргументов, см. Ниже) и создают форматированную строку. Это аналог функции sprintf (3). Также функция «format» может принимать кортеж C #, который должен содержать строку формата в первом элементе, и аргументы для строки формата в других элементах - это аналог функции vsprintf (3).
К сожалению, здесь у меня есть два основных ограничения:
1) Я не могу передать более семи аргументов в функцию format (), потому что аргументы, переданные в кортеже C #, и кортеж не могут содержать более восьми элементов (первый элемент - это сама строка формата, это необходимо, поскольку пусто кортежи недоступны в C #). Я надеюсь получить несколько советов, как мне улучшить функцию format (), чтобы избежать этого ограничения.
2) Еще одно важное ограничение: не все типы могут быть переданы в шаблон C #. В частности, я не могу передать указатели на функцию форматирования (в этом случае я получу следующую ошибку: «ошибка CS0306: тип« int * »не может использоваться в качестве аргумента типа»). Это ограничение C #? Можно переписать функцию format (), чтобы избежать этого ограничения?
Другое серьезное неудобство заключается в том, что я должен открыть какое-то определенное имя библиотеки и использовать имя функции C, которое отличается для разных операционных систем:
для Windows я должен использовать "msvcrt.dll" и "_snprintf";
для Linux я должен использовать "libc.so.6" и "snprintf";
для linux на встроенной платформе Имя C-библиотеки может иметь другое имя ...
Можно определить, какую библиотеку следует открывать во время выполнения, или сортировка внешних функций может быть определена только во время компиляции? Итак, я вижу два варианта здесь:
1) Мне нужно создать разные DLL для разных целевых платформ;
2) Или я могу решить во время выполнения, какое имя функции и библиотеку libc следует использовать?
Я не понял, как мне переписать код для первого или второго варианта. Также очень неудобно иметь знания об имени libc. Почему бы просто не вызвать dlsym ("snprintf")?
Еще один вопрос, я пытаюсь ограничить использование динамической памяти, но выглядит невозможно избежать выделения нескольких классов String и StringBuilder в куче. Это выглядит страшно, потому что при программировании на C / C ++ можно выполнять почти всю работу без динамического выделения памяти. Может быть, кто-то подскажет мне, как улучшить функции format (), чтобы избежать динамического выделения памяти.
using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
namespace FK0
{
using Args = ITuple;
public class Format
{
// const string LIBC = "msvcrt.dll"; //"libc.so";
// const string FUNC = "_snprintf";
const string LIBC = "libc.so.6";
const string FUNC = "snprintf";
[DllImport(LIBC, EntryPoint=FUNC)] static private extern int snprintf(StringBuilder result, [MarshalAs(UnmanagedType.SysInt)] IntPtr size, StringBuilder format, [MarshalAs(UnmanagedType.I4)] int a1);
[DllImport(LIBC, EntryPoint=FUNC)] static private extern int snprintf(StringBuilder result, [MarshalAs(UnmanagedType.SysInt)] IntPtr size, StringBuilder format, [MarshalAs(UnmanagedType.I8)] long a1);
[DllImport(LIBC, EntryPoint=FUNC)] static private extern int snprintf(StringBuilder result, [MarshalAs(UnmanagedType.SysInt)] IntPtr size, StringBuilder format, double a1);
[DllImport(LIBC, EntryPoint=FUNC)] static private extern int snprintf(StringBuilder result, [MarshalAs(UnmanagedType.SysInt)] IntPtr size, StringBuilder format, [MarshalAs(UnmanagedType.LPStr)]string a1);
// function returns length of next format segment (string, copied as is, or single format spec.)
static private int parse_format(string fmt, int pos)
{
int p = fmt.IndexOf('%', pos);
if (p == -1) return fmt.Length - pos; // copy to end of string
else if (p != pos) return p - pos; // copy till %
char[] fmt_term = {'d','i','o','u','x','X','e','E','f','F','g','G','a','A','c','s','p','n','%' };
int e = fmt.IndexOfAny(fmt_term, p + 1);
if (e == -1) throw new System.ArgumentException("invalid format string");
return e - p + 1; // format specifier length
}
// call real `snprintf(3)' from C-library, marshal arguments appropriately
static private int call_snprintf(ref StringBuilder res, int len, StringBuilder fmt, Object arg)
{
if (arg is long || arg is ulong)
return snprintf(res, (IntPtr)len, fmt, Convert.ToInt64(arg));
else if (arg is float || arg is double || arg is decimal)
return snprintf(res, (IntPtr)len, fmt, Convert.ToDouble(arg));
else if (arg is string || arg is StringBuilder)
return snprintf(res, (IntPtr)len, fmt, Convert.ToString(arg));
else if (arg.GetType().IsPointer || arg is IntPtr) // XXX can't pass pointer to template!!!
return snprintf(res, (IntPtr)len, fmt, ((IntPtr)arg).ToInt64());
//else if (arg.GetType()
else
return snprintf(res, (IntPtr)len, fmt, Convert.ToInt32(arg));
}
// vsnprintf-like function (accepts all arguments in tuple)
static public string format(Args args)
{
if (! (args[0] is string)) // check, that first argument is string
throw new System.ArgumentException("wrong string format type");
// first pass
// compute number of arguments, size of output string and max size of formatted output
string fmt = args[0].ToString();
StringBuilder ns = null, fs = new StringBuilder();
int total_len = 0, maxlen = 0, narg = 1;
int pos = 0;
while (pos < fmt.Length) {
int len = parse_format(fmt, pos);
if (fmt[pos] == '%') { // pass format specifier to snprintf(3)
fs.Clear(); fs.Append(fmt, pos, len);
int flen = call_snprintf(ref ns, 0, fs, args[narg]);
if (flen == -1) throw new System.ArgumentException("wrong format string");
total_len += flen;
if (flen > maxlen) maxlen = flen;
narg++;
}
else { // compute size of literal part
total_len += len;
}
pos += len;
}
if (narg != args.Length)
throw new System.ArgumentException("incorrect # of arguments for format string");
// second pass
// print each argument separately
var result = new StringBuilder(total_len);
var part = new StringBuilder(maxlen + 1); // include terminating zero
pos = 0; narg = 1;
while (pos < fmt.Length) {
int len = parse_format(fmt, pos);
if (fmt[pos] == '%') { // pass format specifier to snprintf(3)
fs.Clear(); fs.Append(fmt, pos, len);
call_snprintf(ref part, part.Capacity, fs, args[narg++]);
result.Append(part);
Console.WriteLine(part);
}
else { // copy literal part as is
result.Append(fmt, pos, len);
}
pos += len;
}
return result.ToString();
}
// C# have no vararg templates, as C++03, also max size of tuple limited to 8 elements,
// also impossible to create empty tuple, so maximum number arguments limited to 7 (plus format string as 0-th element).
static public string format<T1, T2, T3, T4, T5, T6, T7>(string fmt, T1 a1, T2 a2, T3 a3, T4 a4, T5 a5, T6 a6, T7 a7)
{
return format(Tuple.Create(fmt, a1, a2, a3, a4, a5, a6, a7));
}
static public string format<T1, T2, T3, T4, T5, T6>(string fmt, T1 a1, T2 a2, T3 a3, T4 a4, T5 a5, T6 a6)
{
return format(Tuple.Create(fmt, a1, a2, a3, a4, a5, a6));
}
static public string format<T1, T2, T3, T4, T5>(string fmt, T1 a1, T2 a2, T3 a3, T4 a4, T5 a5)
{
return format(Tuple.Create(fmt, a1, a2, a3, a4, a5));
}
static public string format<T1, T2, T3, T4>(string fmt, T1 a1, T2 a2, T3 a3, T4 a4)
{
return format(Tuple.Create(fmt, a1, a2, a3, a4));
}
static public string format<T1, T2, T3>(string fmt, T1 a1, T2 a2, T3 a3)
{
return format(Tuple.Create(fmt, a1, a2, a3));
}
static public string format<T1, T2>(string fmt, T1 a1, T2 a2)
{
return format(Tuple.Create(fmt, a1, a2));
}
static public string format<T1>(string fmt, T1 a1)
{
return format(Tuple.Create(fmt, a1));
}
static public string format(string fmt)
{
return format(Tuple.Create(fmt));
}
};
}
public class Program
{
unsafe public static void Main()
{
// System.Threading.Thread.Sleep(100000);
int z = 123;
int* y = &z;
IntPtr v = (IntPtr)y;
string s = FK0.Format.format("%p %d %d", v, *y, z);
Console.WriteLine(s);
}
}