Working with INI files in C# & P/Invoke

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.



Now I know that INI files are considered outdated by some, but there are situations where working with them is easier than some of the alternatives, so let’s go.

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

readValue
/// <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)
{

&nbsp
;      
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 members

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:ProgramDataMicrosoftWindowsStart MenuProgramsdesktop.ini");

        textBox1
.Text = parse.readVaue("LocalizedFileNames", "Windows Fax and Scan.lnk");

        textBox2
.Text = parse.readVaue("LocalizedFileNames", "Windows Media Player.lnk");


        ini
.Dispose();
}

Here's the complete class in one fail swoop:


using System;
using System.Runtime.InteropServices;
using System.Text;
using System.ComponentModel;
using System.Reflection;
namespace PC.Core
{

       
/// <summary>

       
/// Create a New INI file to store or load data

       
/// </summary>

       
public class INIParse : IDisposable

       
{

               
//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;


               
/// <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 ha
ndle to our class when finished


               
/// </summary>

               
[DllImport("kernel32.dll", SetLastError = true)]

               
private static extern int CloseHandle(IntPtr hObject);


               
/// <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; }

               
}


               
/// <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();

               
}


               
/// <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;

                       
}


               
}

               

               
/// <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;

                       
}

                       


               
}


               
/// <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;

               
}


               
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;

                               
}

                       
}                      

               
}


               
~INIParse()

               
{

                       
Dispose(false);

               
}

       
}
}

So now you know how to parse an INI file with P/Invoke, and it wasn't nearly as scary and difficult as you thought it would be now was it. I hope you found this useful and thanks for reading :) 

No comments:

Post a Comment