Artistic Style Developer Information

Calling Artistic Style from a C# Program
Using Explicit Linking

 

Artistic Style can be compiled as a shared library (DLL) and called from a C# program.

A C# program compiled with "Any CPU" can be run on any platform. Artistic Style, however, is a C++ program and must be compiled for the platform on which it will run. Once this is done Artistic Style can be called from a C# program.

With explicit linking the shared library name can be changed at runtime, allowing the C# program to load either an x86 or x64 bit shared library. This provides the capability to make the program run on "Any CPU". The shared object can also be loaded from any directory, not just the directories searched by the system.

Compile Options

To compile Artistic Style for use with a C# program the compile option ASTYLE_LIB must be defined. Then it will accept the files and options as parameters from a function call instead of the command line. It is the responsibility of the calling program to read the source files and accept the options from the user via a graphical interface or other method. These are then passed via the function call described below. After the source files are formatted they will be returned to the calling program, which must then save the source file and take other appropriate action.

Explicit Linking

Explicit linking can be used in C# with C language DLLs. The procedure is similar to using explicit linking in a C or C++ program with the additional task of marshalling objects between the languages.

With explicit linking, applications must make function calls to explicitly load the DLL at run time. To explicitly link to a DLL, an application must use the following Windows functions, or their Linux equivalent:

The AStyleInterface class using explicit linking has internal classes to define the DLL functions for Windows and Linux in addition to the AStyleInterface class. The class constructor handles the LoadLibrary and GetProcAddress function calls while the destructor handles the FreeLibrary function. The LoadLibrary and FreeLibrary functions in the DLL have a reference count and will load and free the library only when needed. Static methods are NOT required, but may be used if you want.

Explicit linking in the following AStyleInterface example was based on C#: How to dynamically load native libraries; cross-platform edition.

AStyleMainUtf16 Method

This method is called to format the source code. Wide strings ARE used here.

Syntax

    /// AStyleMainUtf16 function call.
    /// NOTE: Wide strings ARE used here (UTF-16 in BOTH Windows and Linux).
    delegate IntPtr AStyleMainUtf16([MarshalAs(UnmanagedType.LPWStr)] string textIn,
                                    [MarshalAs(UnmanagedType.LPWStr)] string options,
                                    AStyleErrorDelgate AStyleError,
                                    AStyleMemAllocDelgate AStyleMemAlloc);

Parameters

textIn
A wide-string pointer to the source file to be formatted.

options
A wide-string pointer to the formatting options. They should be in the same format as in the default options file. The options may be set apart by new-lines, commas, tabs or spaces. The long options do not need the "--" prefix. Comments may be used, but they must be terminated by a new-line "\n" character.

If the file is not a C/C++ file, the file mode option "mode=java" or "mode=cs" must be included. Otherwise the default mode of C/C++ is used.

AStyleError
A function pointer to the error handling function. If there are errors in the parameters textIn or options, this function is called. See the following section on Callback Methods for more information..

AStyleMemAlloc
A function pointer to the memory allocation function. The calling program must allocate memory for the output source file. See the following section on Callback Methods for more information.

The calling program is responsible for freeing the allocated memory when it is no longer needed.

Return Value

If the function succeeds, the return value is an IntPtr to the formatted source code. This function returns a 16-bit Unicode string. The IntPtr should be converted to a C# string using Marshal.PtrToStringUni.

If the function fails, the return value is a null pointer. Before the pointer is returned, an error message will be sent to the error handling function.

This function typically fails for one of the following reasons:

The function will NOT fail for an invalid option in the formatting options. In this case an error message is sent to the error handling function and the formatted source code is returned without using the invalid option.

Remarks

The calling program is responsible for freeing the memory allocated by memAllocFunc when it is no longer needed.

AStyleMainUtf16 should be called using a function pointer. The function pointer is obtained by a call to GetProcAddress and marshalled to a function pointer using Marshal.GetDelegateForFunctionPointer. See the AStyleInterface constructor in the example for the procedure.

Callback Methods

Callback methods must be delegated for the call to AStyleMainUtf16. Wide strings are NOT used here.

Syntax

    /// AStyleError callback in AStyle.
    /// NOTE: Wide strings are NOT used here.
    delegate void AStyleErrorDelgate(int errorNumber,
                                     [MarshalAs(UnmanagedType.LPStr)] string error);

    /// AStyleMemAlloc callback in AStyle.
    delegate IntPtr AStyleMemAllocDelgate(int size);

Remarks

The delegates AStyleErrorDelegate and AStyleMemAllocDelegate delegate the error handling  and memory allocation methods. These methods  process any errors and allocate the required memory returned from AStyleMainUtf16. The delegates are similar to C++ function pointers, but are type safe.

AStyleErrorDelgate
AStyleError is called if there are errors in the string parameters in the call to AStyleMainUtf16. It should display an error message and then either abort or continue the program depending on the error. The first parameter is a number identifying the error. The second parameter is a pointer to a standard error message. The error message is Not Unicode. The message is deleted when the error handler returns.

Error messages numbered 100-199 are errors that prevent the file from being formatted. A NULL pointer is returned to the calling program. Error messages numbered 200-299 are errors that do NOT prevent the file from being formatted. A valid string containing a formatted file is returned. This will occur if an invalid option is sent to AStyleMainUtf16. The calling program has the option of accepting or rejecting the formatted file.

AStyleMemAllocDelgate
AStyleMemAlloc is the memory allocation method. The program must allocate memory for the output source file. This function will be called when the memory is needed. The parameter is the amount of memory that should be allocated in a number of char's.

Memory should be allocated using Marshal.AllocHGlobal. With this method, if there is an allocation exception, a NULL pointer will be sent to AStyleMainUtf16. Artistic Style will generate an error message and return an empty string from the AStyleMainUtf16 function. The calling program can handle the exception at this point rather than in the memory allocation function. See the following example program for the procedure.

The calling program is responsible for freeing the memory allocated by AStyleMemAlloc when it is no longer needed.

AStyleGetVersion Method

This method is called to get the Artistic Style version number.

Syntax

    /// AStyleGetVersion function call.
    /// NOTE: Wide strings are NOT returned here.
    delegate IntPtr AStyleGetVersion();

Return Value

The return value is an IntPtr to the Artistic Style version number. This function does NOT return Unicode. The IntPtr should be converted to a C# string using Marshal.PtrToStringAnsi.

Remarks

AStyleGetVersion should be called using a function pointer. The function pointer is obtained by a call to GetProcAddress and marshalled to a function pointer using Marshal.GetDelegateForFunctionPointer. See the AStyleInterface constructor in the example for the procedure.

Example

The following example formats source files by calling the Artistic Style formatter. It is a console application, but the procedure for a GUI is the same. The two classes can be copied and pasted into source files. Or they can be downloaded with test data from the "Developer Information" page.

The Artistic Style source code must be compiled as a shared library (DLL) using the option ASTYLE_LIB. The shared library must be copied to the directory that contains the C# executable (it is loaded from the current directory). The directory of the files to be formatted is an absolute path in the function GetProjectDirectory() in Example.cs. This will need to be changed to reflect your directory structure. When the program is run be sure the current working directory is the directory with the executable and shared library.

The Example class is identical in both C# examples. The differences are in the AStyleInterface class.

 

    
// Example.cs

using System;
using System.IO;
using System.Text;

/// Example opens the source files, calls the AStyleInterface methods
/// to format the files, and saves the reformatted source. The files
/// are in a test-data directory. The option mode=cs must be included
/// for C# files.
public class Example
{   /// Main function for this example.
    public static void Main(string[] args)
    {   // files to pass to AStyle
        string[] fileName =  { "ASBeautifier.cpp",
                               "ASFormatter.cpp",
                               "astyle.h"
                             };

        // options to pass to AStyle
        // mode=cs is required for C# files
        string options = "-A2tOP";

        // create an object
        AStyleInterface AStyle = new AStyleInterface();

        // get Artistic Style version
        // does not need to terminate on an error
        string version = AStyle.GetVersion();
        if (version != String.Empty)
            Console.WriteLine("Example C# - AStyle " + version);

        // process the files
        for (int i = 0; i < fileName.Length; i++)
        {   // get the text to format
            string filePath = GetTestDirectoryPath() + fileName[i];
            string textIn = GetText(filePath);

            // call the Artistic Style formatting function
            // does not need to terminate on an error
            string textOut = AStyle.FormatSource(textIn, options);
            // does not need to terminate on an error
            // an error message has been displayed by the error handler
            if (textOut == String.Empty)
            {   Console.WriteLine("Cannot format "  + filePath);
                continue;
            }

            // return the formatted text
            Console.WriteLine("Formatted " + fileName[i]);
            SetText(textOut, filePath);
        }

        return;
    }

    ///  Error message function for this example.
    private static void Error(string message)
    {   Console.WriteLine(message);
        Console.WriteLine("The program has terminated!");
        Environment.Exit(1);
    }

    /// Find the test directory path from the application program path.
    /// This may need to be changed for your directory structure.
    private static string GetTestDirectoryPath()
    {   string topDirectory = "astyledev";
        string appDirectory = System.AppDomain.CurrentDomain.BaseDirectory;
        if (String.IsNullOrEmpty(appDirectory))
            Error("Cannot find application directory!");
        int indexTop = appDirectory.ToLower().IndexOf(topDirectory);
        if (indexTop == -1)
            Error("Cannot find top level folder!");
        string testPath = appDirectory.Substring(
                              0, indexTop + topDirectory.Length) + "/test-data/";
        return testPath;
    }

    ///  Get the text to be formatted.
    ///  Usually the text would be obtained from an edit control.
    private static string GetText(string filePath)
    {   // create input buffers
        const int readSize = 131072;     // 128 KB
        StringBuilder bufferIn = new StringBuilder(readSize);
        char[] fileIn = new char[readSize];

        // read file data
        try
        {   FileStream file = new FileStream(filePath, FileMode.Open);
            StreamReader streamIn = new StreamReader(file);
            // use ReadBlock to preserve the current line endings
            int charsIn = streamIn.ReadBlock(fileIn, 0, readSize);
            while (charsIn != 0)
            {   bufferIn.Append(fileIn, 0, charsIn);
                charsIn = streamIn.ReadBlock(fileIn, 0, readSize);
            }
            streamIn.Close();
        }
        catch (DirectoryNotFoundException e)
        {   Console.WriteLine(e.ToString());
            Error("Cannot find directory " + filePath);
        }
        catch (FileNotFoundException e)
        {   Console.WriteLine(e.ToString());
            Error("Cannot find file " + filePath);
        }
        catch (Exception e)
        {   Console.WriteLine(e.ToString());
            Error("Error reading file " + filePath);
        }

        return bufferIn.ToString();
    }

    ///  Return the formatted text.
    ///  Usually the text would be returned to an edit control.
    private static void SetText(string textOut, string filePath)
    {   // create a backup file
        string origfilePath = filePath + ".orig";
        File.Delete(origfilePath);                  // remove a pre-existing file
        FileInfo outFile = new FileInfo(filePath);
        outFile.MoveTo(origfilePath);

        // write the output file - same name as input
        try
        {   char[] bufferOut = textOut.ToCharArray();
            FileStream file = new FileStream(filePath, FileMode.Create);
            StreamWriter streamOut = new StreamWriter(file);
            streamOut.Write(bufferOut, 0, bufferOut.Length);
            streamOut.Close();
        }
        catch (Exception e)
        {   Console.WriteLine(e.ToString());
            Error("Error writing file " + filePath);
        }

        return;
    }

}   // class Example

 

Beginning with version 2.04 of Artistic Style, UTF-16 Unicode can be used by calling the function AStyleMainUtf16. With C#, UTF-16 Unicode is used in both Windows and Linux.

 

 
// AStyleInterface.cs

using System;
using System.IO;
using System.Runtime.InteropServices;

interface NativeLibrary
{   void FreeLibrary(IntPtr handle);
    IntPtr GetProcAddress(IntPtr handle, string procName);
    IntPtr LoadLibrary(string fileName);
}

/// Windows native methods.
internal class NativeLibraryWindows : NativeLibrary
{   void NativeLibrary.FreeLibrary(IntPtr handle)
    {   FreeLibrary(handle);
    }

    IntPtr NativeLibrary.GetProcAddress(IntPtr handle, string procName)
    {   return GetProcAddress(handle, procName);
    }

    IntPtr NativeLibrary.LoadLibrary(string fileName)
    {   return LoadLibrary(fileName);
    }

    [DllImport("kernel32.dll")]
    private static extern IntPtr LoadLibrary(string fileName);

    [DllImport("kernel32.dll")]
    private static extern int FreeLibrary(IntPtr handle);

    [DllImport("kernel32.dll")]
    private static extern IntPtr GetProcAddress(IntPtr handle, string procName);
}


/// Linux native methods.
internal class NativeLibraryLinux : NativeLibrary
{   public void FreeLibrary(IntPtr handle)
    {   dlclose(handle);
    }

    public IntPtr GetProcAddress(IntPtr handle, string procName)
    {   dlerror();      // clear previous errors if any
        var res = dlsym(handle, procName);
        var errPtr = dlerror();
        if (errPtr != IntPtr.Zero)
            Console.WriteLine("dlsym: " + Marshal.PtrToStringAnsi(errPtr));
        return res;
    }

    public IntPtr LoadLibrary(string fileName)
    {   dlerror();      // clear previous errors if any
        var res = dlopen(fileName, RTLD_NOW);
        var errPtr = dlerror();
        if (errPtr != IntPtr.Zero)
            Console.WriteLine("dlopen: " + Marshal.PtrToStringAnsi(errPtr));
        return res;
    }

    const int RTLD_NOW = 2;

    [DllImport("libdl")]
    private static extern IntPtr dlopen(string fileName, int flags);

    [DllImport("libdl")]
    private static extern IntPtr dlsym(IntPtr handle, string symbol);

    [DllImport("libdl")]
    private static extern int dlclose(IntPtr handle);

    [DllImport("libdl")]
    private static extern IntPtr dlerror();
}

/// AStyleInterface contains methods to load the C shared libraries
/// and call the Artistic Style formatter.
public class AStyleInterface
{   /// AStyleGetVersion function call.
    /// NOTE: Wide strings are NOT returned here.
    delegate IntPtr AStyleGetVersion();

    /// AStyleError callback in AStyle.
    /// NOTE: Wide strings are NOT used here.
    delegate void AStyleErrorDelgate(int errorNumber,
                                     [MarshalAs(UnmanagedType.LPStr)] string error);

    /// AStyleMemAlloc callback in AStyle.
    delegate IntPtr AStyleMemAllocDelgate(int size);

    /// AStyleMainUtf16 function call.
    /// NOTE: Wide strings ARE used here (UTF-16 in BOTH Windows and Linux).
    delegate IntPtr AStyleMainUtf16([MarshalAs(UnmanagedType.LPWStr)] string textIn,
                                    [MarshalAs(UnmanagedType.LPWStr)] string options,
                                    AStyleErrorDelgate AStyleError,
                                    AStyleMemAllocDelgate AStyleMemAlloc);

    private NativeLibrary astyle = null;

    private IntPtr astyleHandle = IntPtr.Zero;

    private AStyleMainUtf16 fpAStyleMainUtf16 = null;

    private AStyleGetVersion fpAStyleGetVersion = null;

    /// The constructor will load the native Artistic Style library and
    /// get the procedure addresses to call functions in the library.
    /// LoadLibrary has a reference count and will load the library only when needed.
    public AStyleInterface()
    {   astyle = IsWindows()
                 ? (NativeLibrary)new NativeLibraryWindows()
                 : new NativeLibraryLinux();

        // must indicate the current directory for the Linux shared object
        string libraryName = "./" + GetLibraryName();
        // load the library
        // loadLibrary has a reference count and will load the library only when needed
        if (!File.Exists(libraryName))
        {   Error("Cannot find native library " + libraryName);
        }
        astyleHandle = astyle.LoadLibrary(libraryName);
        if (astyleHandle == IntPtr.Zero)
        {   Console.WriteLine("Cannot load the native library " + libraryName);
            Error("You may be mixing 32 and 64 bit code!");
        }

        // get the procedure address for AStyleGetVersion
        var astyleVersionHandle = astyle.GetProcAddress(astyleHandle, "AStyleGetVersion");
        if (astyleVersionHandle == IntPtr.Zero)
        {   Console.WriteLine("Cannot find the symbol AStyleGetVersion in " + libraryName);
            Error("The function must be undecorated in the library.");
        }
        fpAStyleGetVersion = (AStyleGetVersion)Marshal.GetDelegateForFunctionPointer(
                                 astyleVersionHandle, typeof(AStyleGetVersion));

        // get the procedure address for AStyleMainUtf16
        var astyleMainUtf16Handle = astyle.GetProcAddress(astyleHandle, "AStyleMainUtf16");
        if (astyleMainUtf16Handle == IntPtr.Zero)
        {   Console.WriteLine("Cannot find the symbol AStyleMainUtf16 in " + libraryName);
            Error("The function must be undecorated in the library.");
        }
        fpAStyleMainUtf16 = (AStyleMainUtf16)Marshal.GetDelegateForFunctionPointer(
                                astyleMainUtf16Handle, typeof(AStyleMainUtf16));
    }

    /// The destructor will free the native Artistic Style library.
    /// FreeLibrary has a reference count and will free the library only when needed.
    ~AStyleInterface()
    {   astyle.FreeLibrary(astyleHandle);
    }

    /// Error handler to abort the program.
    private void Error(string message)
    {   Console.WriteLine(message);
        Console.WriteLine("The program has terminated!");
        Environment.Exit(1);
    }

    /// Call the AStyleMainUtf16 function in Artistic Style.
    /// An empty string is returned on error.
    public string FormatSource(string textIn, string options)
    {   // Return the allocated string
        // Memory space is allocated by AStyleMemAlloc, a callback function
        string textOut = String.Empty;
        try
        {   // NOTE: a Unicode string IS returned here.
            IntPtr fpText = fpAStyleMainUtf16(textIn, options,
                                              AStyleError, AStyleMemAlloc);
            if (fpText != IntPtr.Zero)
            {   textOut = Marshal.PtrToStringUni(fpText);
                Marshal.FreeHGlobal(fpText);
            }
        }
        catch (Exception e)
        {   Console.WriteLine(e.ToString());
        }
        return textOut;
    }

    /// Called by constructor to get the shared library name.
    /// This will get any version of the library in the executable's directory.
    /// Usually a specific version would be obtained, in which case a constant
    /// could be used for the library name.
    private string GetLibraryName()
    {   // get a library name in the executable's directory (any platform)
        // the following example will allow different library names for x86 and x64
        //if (IsWindows())
        //    libraryName = IntPtr.Size == 8 ? "AStyle64-2.06.dll" : "AStyle32-2.06.dll";
        //else
        //    libraryName = IntPtr.Size == 8 ? "libastyle64-2.06.so" : "libastyle32-2.06.so";
        string libraryName = null;
        string appDirectory = System.AppDomain.CurrentDomain.BaseDirectory;
        string[] files = Directory.GetFiles(appDirectory, "*.*");
        foreach (string filePath in files)
        {   string fileName = Path.GetFileName(filePath).ToLower();
            if ((fileName.EndsWith(".dll")
                    || fileName.Contains(".so")
                    || fileName.EndsWith(".dylib"))
                    && (fileName.StartsWith("astyle")
                        || fileName.StartsWith("libastyle")))
            {   libraryName = Path.GetFileName(filePath);
                break;
            }
        }
        if (libraryName == null)
        {   Error("Cannot find astyle native library in "
                  + appDirectory
                  + Path.DirectorySeparatorChar);
        }
        return libraryName;
    }

    /// Get the Artistic Style version number.
    public string GetVersion()
    {   string version = String.Empty;
        try
        {   // NOTE: a Unicode string is NOT returned here.
            IntPtr fpVersion = fpAStyleGetVersion();
            if (fpVersion != IntPtr.Zero)
            {   version = Marshal.PtrToStringAnsi(fpVersion);
            }
        }
        catch (Exception e)
        {   Error(e.ToString());
        }
        return version;
    }

    /// Determine if this is a Linux platform.
    private bool IsLinux()
    {   var p = (int)Environment.OSVersion.Platform;
        return (p == 4) || (p == 6) || (p == 128);
    }

    /// Determine if this is a Windows platform.
    private bool IsWindows()
    {   var p = (int)Environment.OSVersion.Platform;
        return (p != 4) && (p != 6) && (p != 128);
    }

    /// AStyleMainUtf16 callback to allocate memory for the return string.
    private IntPtr AStyleMemAlloc(int size)
    {   return Marshal.AllocHGlobal(size);
    }

    /// AStyleMainUtf16 callback to display errors from Artistic Style.
    private void AStyleError(int errorNumber, string error)
    {   Console.WriteLine("AStyle error " + errorNumber + "\n" + error);
    }

}   // class AStyleInterface