Calling Unmanaged Functions Which Take a Variable Number of Arguments From C#

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!