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 that sometimes arises is calling a function that 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.Text.RegularExpressions;
class Test
{
    static void Main()
    {
        string str = "1 + 2 = three";
        string pattern = @"(\d+) (\S) (\d+) (\S) (\S+)";
        Match match = Regex.Match(str, pattern);
        if (match.Success)
        {
            int firstNumber = int.Parse(match.Groups[1].Value);
            char operation1 = char.Parse(match.Groups[2].Value);
            int secondNumber = int.Parse(match.Groups[3].Value);
            char operation2 = char.Parse(match.Groups[4].Value);
            string resultString = match.Groups[5].Value;
            Console.WriteLine($"First Number: {firstNumber}");
            Console.WriteLine($"Operation 1: {operation1}");
            Console.WriteLine($"Second Number: {secondNumber}");
            Console.WriteLine($"Operation 2: {operation2}");
            Console.WriteLine($"Result String: {resultString}");
        }
        else
        {
            Console.WriteLine("No match found.");
        }
        Console.ReadKey();
    }
}

This builds fine, but then, when you try to run it, 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 expected 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(10); // 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#?

The 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.

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, __arglist);
    static void Main()
    {
        // First sscanf call
        string str1 = "1 + 2 = three";
        string format1 = "%d %c %d %c %s"; // int, char, int, char, string
        int arg1, arg3;
        char arg2, arg4;
        StringBuilder arg5_1 = new StringBuilder(5); // buffer for string
        int result1 = sscanf(str1, format1, __arglist(out arg1, out arg2, out arg3, out arg4, arg5_1));

        // Second sscanf call
        string str2 = "one + 2 = 3";
        string format2 = "%s %c %d %c %d"; // string, char, int, char, int
        StringBuilder arg5_2 = new StringBuilder(10); // buffer for string
        int result2 = sscanf(str2, format2, __arglist(arg5_2, out arg2, out arg1, out arg4, out arg3));

        // Output results
        Console.WriteLine("First sscanf result: {0} {1} {2} {3} {4}", arg1, arg2, arg3, arg4, arg5_1);
        Console.WriteLine("Second sscanf result: {0} {1} {2} {3} {4}", arg5_2, 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 the problem is solved.


Similar Articles