Introduction
Many C# programmers are familiar with the Platform Invoke mechanism (using the
DllImport attribute) for calling unmanaged functions from C#.
A problem which sometimes arises is calling a function which takes a variable
number of arguments. Consider for example the sscanf() function in the C runtime
library which is defined as follows:
int
sscanf (const char
*str, const char
*format, ...);
This function reads data from 'str', splits it up using 'format' and then stores
the results in the locations pointed to by the remaining arguments (the '...')
which are variable in both number and type.
So, how can we call this function from C#?
First attempt
You may be thinking, no problem, C# can cope with a variable number of arguments
using its 'params' keyword which leads you to try the following code:
using
System;
using
System.Runtime.InteropServices;
class Test
{
[DllImport("msvcrt.dll",
CharSet = CharSet.Ansi, CallingConvention =
CallingConvention.Cdecl)]
static extern int sscanf(string
str, string format,
params object[] varargs);
static void
Main()
{
string str =
"1 + 2 = three";
string format =
"%d %c %d %c %s";
// int, char, int, char, string
object[] varargs =
new object[5];
int result = sscanf(str, format, varargs);
for (int
i = 0; i < result; i++) Console.WriteLine(varargs[i]);
Console.ReadKey();
}
}
This builds fine but then, when you try to run it, blows up with a dreaded 'System.AccessviolationException'.
So, what's wrong here?
Second attempt
The problem is that, having consulted the 'format' string, the C function is
expecting to be presented with 5 pointers in sequence. Instead it gets a single
pointer to an object array - a different animal!
This suggests that the following might work:
using
System;
using
System.Runtime.InteropServices;
using
System.Text;
class Test
{
[DllImport("msvcrt.dll",
CharSet = CharSet.Ansi, CallingConvention =
CallingConvention.Cdecl)]
static extern int sscanf(string
str, string format, out int arg1, out char arg2, out int arg3, out char arg4,
StringBuilder arg5);
static void
Main()
{
string str =
"1 + 2 = three";
string format =
"%d %c %d %c %s";
// int, char, int, char, string
int arg1, arg3;
char arg2, arg4;
StringBuilder arg5 =
new StringBuilder(5);
// need a buffer big enough to return the string
int result = sscanf(str, format,
out arg1, out
arg2, out arg3, out
arg4, arg5);
Console.WriteLine("{0}
{1} {2} {3} {4}", arg1, arg2, arg3, arg4, arg5);
Console.ReadKey();
}
}
And indeed it does, reconstituting the original string:
1 + 2 = three
Suppose though that we have a dozen such strings to process, each with different
formats and different types and numbers of variables. Does this mean that we
have to write out a dozen overloads of the sscanf() function to use it from C#?
Third (and final) attempt
Fortunately, we can do this with just a single function declaration by making
use of the obscure (and undocumented) C# keyword __arglist. Here's how:
class Test
{
[DllImport("msvcrt.dll",
CharSet = CharSet.Ansi, CallingConvention =
CallingConvention.Cdecl)]
static extern int sscanf(string
str, string format, __arglist);
static void
Main()
{
string str =
"1 + 2 = three";
string format =
"%d %c %d %c %s";
// int, char, int, char, string
int arg1, arg3;
char arg2, arg4;
StringBuilder arg5 =
new StringBuilder(5);
// need a buffer big enough to return the string
int result = sscanf(str, format,
__arglist(out
arg1, out arg2, out
arg3, out arg4, arg5));
Console.WriteLine("{0}
{1} {2} {3} {4}", arg1, arg2, arg3, arg4, arg5);
str = "one + 2 = 3";
format = "%s %c %d %c %d";
// string, char, int, char, int
result = sscanf(str, format, __arglist(arg5,
out arg2, out
arg1, out arg4, out
arg3));
Console.WriteLine("{0}
{1} {2} {3} {4}", arg5, arg2, arg1, arg4, arg3);
Console.ReadKey();
}
}
This gives us the same result as before plus an extra line:
one + 2 = 3
proving that only a single function declaration is needed. So problem solved!