Subscribe to Blog.Parse(typeof(PsychoCoder), Richard McCutchen);        RSS Feed
-----

Parse an INI file with C# & P/Invoke

Icon 1 Comments
Today we're going to look at parsing an INI file with Win32 API's. Now I'm sure there are thousands of ways to accomplish this task, I just prefer to use P/Invoke, it's more straightforward for me and is less complicated (in my opinion). One thing to remember is when using a Win32 API you need to release the object as soon as you're done with it, prevents bad things from happening within your application.

NOTE: This assumes you have a semi-working knowledge of P/Invoke and how to use it.


Not only will this example show you how to parse your INI file, but also on how to release the objects from memory. In order to accomplish this we're going to need a few namespaces:



So let's get started. First add your references to the top of your class

using System;
using System.Runtime.InteropServices;
using System.Text;
using System.ComponentModel;
using System.Reflection;



Then we create our class (Mine is named, oddly enough, INIParse. Our class will implement the IDisposable Interface, allowing us to free up allocated resources.

namespace PC.Core
{
	/// <summary>
	/// Create a New INI file to store or load data
	/// </summary>
    public class INIParse : IDisposable
    {

    }
}



Now inside our class let's add some variables that will be needed. Three for handling the disposing of our object and two for read-only properties

//variables for handling the freeing up of allocated resources
private bool disposed = false;
private Component component = new Component();
private IntPtr handle;

//holds the path of our INI file
private string _path;

//variable that will hold any return message when somethign fails
private string _errMessage;



Now, as stated, we're going to be using P/Invoke, and the Win32 API's we'll be using are



So now let's set up our three functions. For P/Invoke we will be adding the DLLImportAttribute letting the compiler/system know we are accessing an unmanaged DLL file

/// <summary>
/// method for writing data to an INI file
/// </summary>
[DllImport("kernel32")]
private static extern long WritePrivateProfileString(string lPAppName, string lpKeyName, string lpString, string lpFileName);

/// <summary>
/// method for reading from a specified section of an INI file
/// </summary>
[DllImport("kernel32")]
private static extern int GetPrivateProfileString(string lpAppName, string lpKeyName, string lpDefault, StringBuilder lpReturnedString, int nSize, string lpFilePath);

/// <summary>
/// method for closing the handle to our class when finished
/// </summary>
[DllImport("kernel32.dll", SetLastError = true)]
private static extern int CloseHandle(IntPtr hObject);



Now for our class properties. One will be used to hold the error message returned if a method failed, and the second will hold the path to our INI file that the user provides when the class is inntialized. Both properties are read only

/// <summary>
/// property to hold the error message returned if a method call fails
/// </summary>
public string ErrorMessage
{
    get { return _errMessage; }
}

/// <summary>
/// property that will hold the path to our INI file
/// </summary>
public string FilePath
{
    get { return _path; }
}



In our class we're going to have a single constructor, that will set the value of the path to our INI file. In this constructor you're going to see a reference to a method named GetHandle, you'll see that method later (It's used to get the handle to our class since, unlike a form, we don't have a readily available handle so we'll use Reflection to get it) so don't worry about it right now

/// <summary>
/// INIParse Constructor
/// Sets the path to our INI file and get's the handle to our class (for disposal later)
/// </summary>
/// <param name="path"></param>
public INIParse(string path)
{
    //set the path to the INI file we're working with
	_path = path;

    //get the handle to our class
    this.handle = GetHandle();
}



Well we now have our references, Win32 API's variables & properties set. We have our constructor so the object can be used, now let's move on to writing to and reading from an INI file.

writeValue
/// <summary>
/// method to write data to an INI file
/// </summary>
/// <param name="sectionName">section in the INI file we're writing to</param>
/// <param name="keyName">the new key we're adding</param>
/// <param name="newValue">the value of the key</param>
public bool writeValue(string sectionName, string keyName, string newValue)
{
    try
    {
        //make sure the user provided a value to be written, otherwise
        //throw a ArgumentNullException
        if (string.IsNullOrEmpty(newValue))
            throw new ArgumentNullException("keyValue");
        else
        {
            //use P/Invoke to write the value and get it's returned value
            //0 means something went wrong so we'll throw a Win32Exception
            long status = WritePrivateProfileString(sectionName, keyName, newValue, this._path);

            if (status == 0)
                throw new Win32Exception(Marshal.GetLastWin32Error());
            else
                //nothing went wrong so let the user know all is well
                return true;
        }
    }
    catch (Exception ex)
    {
        //if we're here something went wrong so let the user know
        this._errMessage = ex.Message;
        return false;
    }

}



Pretty simple, we use WritePrivateProfileString to write our value to the specified key area. If no key is provided then Windows will create the section. We want to ensure the user provides a value tow rite so we make sure it's not null or empty before attempting to write it. That's simple enough, now let's look at reading a value from your INI file.

for this we will use GetPrivateProfileString and provide it the INI file, the section we're reading from and the key we want the value from. This method looks like this

/// <summary>
/// method for reading data from an INI file
/// </summary>
/// <param name="sectionName">section we're looking in</param>
/// <param name="keyName">the key we want the value from</param>
/// <returns></returns>
public string readVaue(string sectionName, string keyName)
{
    try
    {
        //this will hold the value returned from the Win32 API call
        StringBuilder sb = new StringBuilder(500);

        //the P/Invoke returns an integer value
        int status = GetPrivateProfileString(sectionName, keyName, "", sb, sb.Capacity, this._path);

        //return the value
        return sb.ToString();
    }
    catch (Exception ex)
    {
        return ex.Message;
    }       

}



Ok, our class is almost complete, but if you were to try and compile it now you'd get an error message because we haven't implemented the IDisposable Interface yet, so let's do that. First is the GetHandle method that is used in our constructor

/// <summary>
/// method for getting the handle to a class. Unlike a Form
/// a class doesnt have a "handle" redily available, but it's 
/// available using Reflection
/// </summary>
/// <returns></returns>
private IntPtr GetHandle()
{
    //get the type of our class
    Type type = this.GetType();

    //get the ToString attribute
    MethodInfo info = type.GetMethod("ToString");

    //get the RuntimeHandle and convert it to a type
    RuntimeTypeHandle hwnd = type.TypeHandle;

    RuntimeMethodHandle _handle = info.MethodHandle;
    return _handle.Value;
}



Now our IDisposable Interface

public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}

private void Dispose(bool disposing)
{
    if (!this.disposed)
    {
        component.Dispose();
        if (disposing)
        {
            CloseHandle(handle);
            this.handle = IntPtr.Zero;
            this.disposed = true;
        }
    }            
}




If you notice one is private and one is public. The public one is called whever the class is used, which in turns calls the private one, closing us P/Invoke and the handle to our class. Finally we use a Destructor

~INIParse()
{
    Dispose(false);
}



So now our class for parsing an INI file is created and ready to go. To test it I used my desktop.ini file to read from a form like so

private void Form1_Load(object sender, System.EventArgs e)
{
    INIParse parse = new INIParse("C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\desktop.ini");
    textBox1.Text = parse.readVaue("LocalizedFileNames", "Windows Fax and Scan.lnk");
    textBox2.Text = parse.readVaue("LocalizedFileNames", "Windows Media Player.lnk");

    ini.Dispose();
}




And read two value from the LocalizedFilenames into my 2 TextBox controls, and the results look like this (it displays the 2 key's values I was looking for)

Attached Image

So now you know how to parse an INI file with P/Invoke. I hope you found this useful and thanks for reading :)

1 Comments On This Entry

Page 1 of 1

maffelu Icon

18 April 2010 - 09:02 PM
That was a very good blog. I've used the WritePrivateProfileString and GetPrivateProfileString functions to some extent in C but I've been reluctant to have the same approach in C#. This is, however, a lot simpler than I thought it would be.

Thanks PC!
0
Page 1 of 1

Trackbacks for this entry [ Trackback URL ]

There are no Trackbacks for this entry

July 2014

S M T W T F S
  12345
6789101112
13141516171819
20212223242526
27 28 293031  

Recent Entries

Search My Blog

1 user(s) viewing

1 Guests
0 member(s)
0 anonymous member(s)