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
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.
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
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
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
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
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
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
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
Now our IDisposable Interface members
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
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
Here's the complete class in one fail swoop:
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 :)
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:
- System.Runtime.InteropServices: This Namespace will allow us to work with Win32.
- System.ComponentModel:
- System.Reflection: We'll use this to get insformation from our assembly for freeing up memory and such
- System.Text: This will give us access to the StringBuilder Class
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)
{
 
; 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