Click here to Skip to main content
15,889,842 members
Articles / Programming Languages / C#
Article

INI Class for .NET

Rate me:
Please Sign up or sign in to vote.
3.49/5 (26 votes)
9 Aug 2005CC (ASA 2.5)2 min read 172.8K   1.8K   77   33
Class for interfacing with INI files.

Sample Image - aejw_ini_class.jpg

Introduction

This is a class for reading and adjusting INI files. The class utilizes the WritePrivateProfileString and GetPrivateProfileString API calls. Written mainly for legacy support, as a lot applications still use INI files and although there is XML support native to .NET (".config" files), I tend to find that for some circumstances INI files are straightforward and more practical to use.

Using the code

  • Add the class file "cINI.cs" to your project / solution.
  • Declare and use the class.
    C#
    //Read value from INI
    aejw.cIni oIni = new aejw.cIni("C:\\INIFile.ini");
    string sRet = oIni.ReadValue("TheSection", 
                       "TheKey", "DefaultReturn");

Functions / Methods

The following examples require the object / class to be declared.

C#
aejw.cIni oIni = new aejw.cIni("C:\\INIFile.ini");

Read Value

C#
//Read value from INI
string sRet = oIni.ReadValue("TheSection", "TheKey", "");

Write Value

C#
//Write value to INI
oIni.WriteValue("TheSection", "TheKey", "TheValue");

Remove Value

C#
//Remove value from INI
oIni.RemoveValue("TheSection", "TheKey");

Read Sections

C#
//Read sections from INI
Array oSections = new Array[0];
oIni.ReadSections(ref oSections);

Read Values (Value names)

C#
//Read values from INI
Array oValues = new Array[0];
oIni.ReadValues(ref oValues);

About the code

Below is a general breakdown of how the code works...

  • Referring to the API function...

    All the functions at some stage use a API call. By reading the MSDN Platform SDK material, we get the C++ API call and behavior of the API function...

    DWORD GetPrivateProfileString(
        LPCTSTR lpAppName,
        LPCTSTR lpKeyName,
        LPCTSTR lpDefault,
        LPTSTR  lpReturnedString,
        DWORD   nSize,
        LPCTSTR lpFileName
    );

    Which loosely translates to... (I am using the term loosely on the bases that there are multiple ways of handling the parameters.)

    C#
    [DllImport("kernel32", SetLastError=true)]
    private static extern int GetPrivateProfileString(
        string pSection,
        string pKey,
        string pDefault,
        byte[] pValue,
        int    pBufferLen,
        string pFile
    );

    Make note of the LPTSTR to byte[] conversion. The LPTSTR refers to a pointer / reference in memory that the API function will write to, we are going to use a framework function later to collect the text.

  • Calling the API function...

    Once the API declaration has been added, we need to create a function to wrap the API function into a C# friendly function.

    C#
    private string z_GetString(string psSection, 
                               string psKey, string psDefault){
    
        //Prepare the default return and create 
        //a byte array to pass as a pointer
        string sRet=psDefault;
        byte[] bRet=new byte[255];
    
        //Call the API function, passing a Section name, value name, default value,
        //buffer length (length of the passed byte array) and the ini filename.
        int i=GetPrivateProfileString(psSection, psKey, 
                        psDefault, bRet, 255, ls_IniFilename);
    
        //As referred above we use a framework function 
        //to collect the string from the byte
        //array used as a pointer. Note the 'i' variable 
        //is the return from the API call, this
        //is passed to the text encoding as the total length of the return.
        sRet=System.Text.Encoding.GetEncoding(1252).GetString(bRet, 
                                             0,i).TrimEnd((char)0);
    
        return(sRet);
    
    }
  • Using the function...

    Now that we have a API declaration and friendly function, we can call the function at will...

    C#
    string sSetting=z_GetString("SectionName","ValueName","");
    string sSetting2=z_GetString("SectionName2", 
                                 "ValueName2","DefaultReturn");

History

  • 3rd August 2005 - build 0019
    • Wrote some more general documentation.
    • Changed class filename to 'cINI.cs'.
    • Corrected 'ReadSections' and 'ReadValues'. Rewrote 'z_GetString' function to correctly handle the text encoding and accept chr(0). (Thanks to 'yurij' for reporting the bug).
    • General small bug fixes and improvements.
  • 14th April 2005 - build 0018
    • Corrected issue when returning high ASCII characters, e.g.: 'åäö'
    • Tidied class and documents
  • 6th Feb 2004 - build 0016
    • Corrected bug in 'ReadValue', would return 'char(0)' to the length of the buffer after the value returned.
  • 5th Feb 2004 - build 0015
    • First build posted on CodeProject, based on previous code and dubbed 0015.

License

This article, along with any associated source code and files, is licensed under The Creative Commons Attribution-ShareAlike 2.5 License


Written By
Web Developer
New Zealand New Zealand
C#, VB.net (Web and forms), SQL Server, MySQL, ASP, Win32 API, ...
Site: aejw.com

Comments and Discussions

 
Questionfirst item of array filled by ReadValues always seems to be an empty string Pin
c_keen12-Mar-12 8:48
c_keen12-Mar-12 8:48 
GeneralThanks for the Class Pin
Decad18-Jul-08 0:34
Decad18-Jul-08 0:34 
GeneralRe: Thanks for the Class Pin
aejw20-Jul-08 14:53
aejw20-Jul-08 14:53 
GeneralIf the value is Unicode,like simple chinese charset, there will be a bug Pin
lwlojj17-Jul-08 4:17
lwlojj17-Jul-08 4:17 
GeneralRe: If the value is Unicode,like simple chinese charset, there will be a bug Pin
aejw20-Jul-08 14:52
aejw20-Jul-08 14:52 
Questionhelp Pin
Member 381377111-Feb-08 3:13
Member 381377111-Feb-08 3:13 
AnswerRe: help Pin
aejw20-Jul-08 14:56
aejw20-Jul-08 14:56 
QuestionHigh ASCII chars in value name Pin
z00z020-Mar-06 7:27
z00z020-Mar-06 7:27 
AnswerRe: High ASCII chars in value name Pin
aejw2-May-06 16:57
aejw2-May-06 16:57 
GeneralRe: High ASCII chars in value name Pin
z00z04-May-06 9:43
z00z04-May-06 9:43 
GeneralNaming convebtions Pin
Guido_d10-Aug-05 0:15
Guido_d10-Aug-05 0:15 
GeneralRe: Naming convebtions Pin
aejw10-Aug-05 7:51
aejw10-Aug-05 7:51 
GeneralUnicode Pin
Elder Benassi3-Aug-05 21:16
Elder Benassi3-Aug-05 21:16 
GeneralRe: Unicode Pin
Anonymous-aejw6-Aug-05 0:49
sussAnonymous-aejw6-Aug-05 0:49 
Generalerror in ReadSections Pin
Member 17274072-Aug-05 10:08
Member 17274072-Aug-05 10:08 
GeneralRe: error in ReadSections Pin
aejw3-Aug-05 11:56
aejw3-Aug-05 11:56 
QuestionNini?? Pin
kaschimer20-Apr-05 13:18
kaschimer20-Apr-05 13:18 
AnswerRe: Nini?? Pin
Yinon Ehrlich14-Jun-05 20:18
Yinon Ehrlich14-Jun-05 20:18 
GeneralNative version Pin
Guido_d20-Apr-05 2:14
Guido_d20-Apr-05 2:14 
Hi,

here's a native version of an ini wrapper, without native PrivateProfileIni calls.

The first class wraps the ini file, the second class wraps each section.

You might want to do things differently, esp handling duplicate keys or sections, but I think you'll get the idea.

<br />
using System;<br />
using System.Text;<br />
using System.IO;<br />
using System.Collections;<br />
using System.Runtime;<br />
using System.Runtime.Serialization;<br />
using System.Xml	;<br />
<br />
using System.Diagnostics;<br />
<br />
namespace IniWrapper<br />
{<br />
	/// <summary><br />
	/// IniFile: ini file wrapper. IniSections are used for [Section] wrappers<br />
	/// </summary><br />
	[Serializable]<br />
	public class IniFile : ICollection, IEnumerable<br />
	{<br />
		public static bool operator == (IniFile lhs, IniFile rhs)<br />
		{<br />
			foreach(IniSection section in lhs.m_sections)<br />
			{<br />
				if (!rhs.ContainsSection(section.Name)) <br />
					return false;<br />
				IniSection othersection = rhs[section.Name] as IniSection;<br />
				if (section != othersection) return false;<br />
			}<br />
<br />
			return true;<br />
		}<br />
		public static bool operator != (IniFile lhs, IniFile rhs)<br />
		{<br />
			if (lhs == rhs) return false;<br />
			return true;<br />
		}<br />
<br />
<br />
		public override bool Equals(object obj)<br />
		{<br />
			if (!(obj is IniFile))<br />
				return false;<br />
			return (this==(IniFile)obj);<br />
		}<br />
<br />
<br />
<br />
		public IniFile()<br />
		{<br />
		}<br />
		<br />
		public IniFile(string path)<br />
		{<br />
			m_path = path;<br />
			Read();<br />
			Parse();<br />
		}<br />
		private string m_path = "";<br />
		private ArrayList m_lines;<br />
<br />
		private ArrayList m_sections = new ArrayList();<br />
		private Hashtable m_sectionlookup = new Hashtable();<br />
		<br />
		public void AddSection(IniSection section)<br />
		{<br />
			string name =section.Name;<br />
			this[name]=section;<br />
		}<br />
<br />
		public IniSection this[string section_name]<br />
		{<br />
			get<br />
			{<br />
				if (m_sectionlookup.ContainsKey(section_name.ToLower()))<br />
					return m_sections[(int)m_sectionlookup[section_name.ToLower()]] as IniSection;<br />
				<br />
				// auto-add:<br />
				<br />
				int id = m_sections.Add(new IniSection(section_name));<br />
				m_sectionlookup.Add(section_name.ToLower(),id);<br />
				return m_sections[id] as IniSection;<br />
<br />
				//return null;<br />
			}<br />
<br />
			set<br />
			{<br />
				if (m_sectionlookup.ContainsKey(section_name.ToLower()))<br />
					m_sections[(int)m_sectionlookup[section_name.ToLower()]] = value;<br />
				else<br />
				{<br />
					int id = m_sections.Add(value);<br />
					m_sectionlookup.Add(section_name.ToLower(),id);<br />
					<br />
				}<br />
			}<br />
		}<br />
<br />
		public IniSection this[int section_id]<br />
		{<br />
			get<br />
			{<br />
				if (m_sectionlookup.ContainsKey(section_id))<br />
					return m_sections[(int)m_sectionlookup[section_id]] as IniSection;<br />
				return null;<br />
			}<br />
		}<br />
<br />
		public bool ContainsSection(string section_name)<br />
		{<br />
			foreach(IniSection section in m_sections)<br />
			{<br />
				if (section.Name.ToLower()==section_name.ToLower()) return true;<br />
			}<br />
			return false;<br />
		}<br />
<br />
		public void Read(string path)	<br />
		{<br />
			m_path = path;<br />
			Read();<br />
		}<br />
		public void Read()	<br />
		{<br />
			m_lines = new ArrayList();<br />
			m_sections = new ArrayList();<br />
			m_sectionlookup = new Hashtable();<br />
<br />
			if (m_path.Length==0) return;<br />
			if (!File.Exists(m_path)) throw new FileNotFoundException("Ini file not found",m_path);<br />
<br />
			// process Ini file<br />
<br />
			StreamReader sr = new StreamReader(m_path ,System.Text.Encoding.GetEncoding(1252));<br />
			try <br />
			{<br />
				while (true)<br />
				{<br />
					string line = sr.ReadLine();<br />
					if (line==null) break;<br />
					if (line.Length>0)<br />
						m_lines.Add(line);<br />
				} <br />
			}	  <br />
			catch {}<br />
			sr.Close();<br />
<br />
<br />
<br />
		}<br />
<br />
		public void Parse()<br />
		{<br />
			if (m_path.Length==0 || m_lines==null || m_lines.Count==0) return;<br />
			IniSection section = null;<br />
			m_sections.Clear();<br />
			m_sectionlookup.Clear();<br />
			for(int c=0;c<m_lines.Count;c++)<br />
			{<br />
				string line = m_lines[c].ToString().Trim();<br />
<br />
				if (line != null && line.Length>0)<br />
				{<br />
					if (line.StartsWith("[") && (line.EndsWith("]")))<br />
					{<br />
						if (section != null)<br />
						{<br />
							m_sectionlookup.Add(section.Name.ToLower(),m_sections.Count);<br />
							m_sections.Add(section);<br />
<br />
						}<br />
						section = new IniSection(line.Substring(1,line.Length-2).Trim());<br />
					}<br />
					else<br />
					{<br />
						int eq = line.IndexOf("=");<br />
						if (section==null || eq<0) // no Ini section yet defined or no '=' in line<br />
						{}<br />
						else<br />
						{<br />
							string setting = line.Substring(0,eq).Trim();<br />
							string val = line.Substring(eq+1).Trim();<br />
							if (setting.Length>0) <br />
							{<br />
								if (!section.ContainsKey(setting))<br />
								{<br />
									section.Add(setting, val);<br />
								} <br />
								else //catch (Exception)<br />
								{<br />
									string err = string.Format("Error reading Ini file {0} line {1}; possible duplicate value detected.",m_path,c);<br />
									Trace.WriteLine(err); // Log::Fatal(&err);<br />
<br />
									if (section.ContainsKey(setting))<br />
									{<br />
										// if value is defined multiple times and numeric, compute total of values<br />
										try {<br />
											string oldval = section[setting];<br />
											int aantal = Convert.ToInt32(oldval);<br />
											int extra = Convert.ToInt32(val);<br />
											int total = aantal + extra;<br />
											section[setting] = total.ToString();<br />
											Trace.WriteLine("WARNING: Numbers have been added. New total is " + total.ToString());<br />
										} <br />
										catch {<br />
											string err2 = "FATAL: Duplicate Ini value in non-numeric section... Ini file invalid.";<br />
											Trace.WriteLine(err2);<br />
											throw new Exception(err2);<br />
										}<br />
									}<br />
<br />
								}<br />
							}<br />
						}<br />
<br />
					}<br />
				}<br />
			}<br />
			if (section != null)<br />
			{<br />
				m_sectionlookup.Add(section.Name.ToLower(),m_sections.Count);<br />
				m_sections.Add(section);<br />
			}<br />
		}<br />
<br />
		public override string ToString()<br />
		{<br />
			StringBuilder sb = new StringBuilder();<br />
			for (int c=0;c<m_sections.Count;c++)<br />
			{<br />
				sb.Append((m_sections[c] as IniSection).ToString());<br />
			}<br />
			return sb.ToString();<br />
<br />
		}<br />
		public void Save()<br />
		{<br />
			if (m_path.Length>0)<br />
				this.Save(m_path,true);<br />
		}<br />
		public void Save(string path)<br />
		{<br />
			Save(path, false);<br />
		}<br />
		public void Save(string path, bool overwrite)<br />
		{<br />
			if (File.Exists(path) && !overwrite)<br />
				throw new ArgumentException("File exists");<br />
			if (File.Exists(path)) File.Delete(path);<br />
			StreamWriter sw = new StreamWriter(path,false,System.Text.Encoding.UTF8);<br />
			sw.Write(this.ToString());<br />
			sw.Close();<br />
		}<br />
<br />
		public string Path<br />
		{<br />
			get<br />
			{<br />
				return m_path;<br />
			}<br />
			set<br />
			{<br />
				m_path = value;<br />
				Read();<br />
			}<br />
		}<br />
<br />
		<br />
		<br />
		<br />
		public override int GetHashCode()<br />
		{<br />
			return m_sections.GetHashCode ();<br />
		}<br />
<br />
		#region ICollection Members<br />
<br />
		public bool IsSynchronized<br />
		{<br />
			get<br />
			{<br />
				return m_sections.IsSynchronized;<br />
			}<br />
		}<br />
<br />
		public int Count<br />
		{<br />
			get<br />
			{<br />
				return m_sections.Count;<br />
			}<br />
		}<br />
<br />
		public void CopyTo(Array array, int index)<br />
		{<br />
			m_sections.CopyTo(array, index);<br />
		}<br />
<br />
		public object SyncRoot<br />
		{<br />
			get<br />
			{<br />
				return m_sections.SyncRoot;<br />
			}<br />
		}<br />
<br />
		#endregion<br />
<br />
		#region IEnumerable Members<br />
<br />
		public IEnumerator GetEnumerator()<br />
		{<br />
			return m_sections.GetEnumerator();<br />
		}<br />
<br />
		#endregion<br />
	}<br />
<br />
<br />
/// <summary><br />
	/// Represents a section in an ini file<br />
	/// </summary><br />
	[Serializable]<br />
	public class IniSection : System.Collections.Hashtable <br />
	{<br />
		// index table for sorting<br />
		private Hashtable m_sortorder = new Hashtable();<br />
		private Hashtable m_upperlower = new Hashtable();<br />
<br />
		public IniSection(string title) : base()<br />
		{<br />
			this.m_name=title;<br />
		}<br />
<br />
		public string this[string setting_name] <br />
		{<br />
			get<br />
			{<br />
				try <br />
				{<br />
					if (this.Contains(setting_name))<br />
					{<br />
						return base[setting_name].ToString();<br />
					} <br />
					else if (m_upperlower.ContainsKey(setting_name.ToLower())) <br />
					{<br />
						string name = m_upperlower[setting_name.ToLower()].ToString();<br />
						if (this.ContainsKey(name))<br />
						{<br />
							string val = base[name].ToString();<br />
							if (val==null || val.Length==0) return "";<br />
							return val;<br />
						} <br />
						else<br />
							return "";<br />
				<br />
					}<br />
					else if (this.Contains(setting_name.ToLower()))<br />
					{<br />
						return base[setting_name.ToLower()].ToString();<br />
					}<br />
					else<br />
						return "";<br />
				} <br />
				catch(Exception e)<br />
				{<br />
					Trace.WriteLine(string.Format("{0}\n{1}\n{2}\n{3}\n{4}",new string[] {setting_name, e.Message,e.Source,e.StackTrace,e.InnerException.Message}));<br />
				}<br />
				return "";<br />
			}<br />
			set<br />
			{<br />
				this.Add(setting_name, value);<br />
			}<br />
		}<br />
<br />
		public override void Clear()<br />
		{<br />
			m_sortorder.Clear();<br />
			m_upperlower.Clear();<br />
			base.Clear ();<br />
		}<br />
<br />
		public string FindKey(string setting_value)<br />
		{<br />
			foreach(DictionaryEntry entry in this)<br />
			{<br />
				if (entry.Value.ToString()==setting_value)<br />
					return entry.Key.ToString();<br />
				else if (entry.Value.ToString().ToLower()==setting_value.ToLower())<br />
					return entry.Key.ToString();<br />
<br />
			}<br />
				<br />
			return null;<br />
		}<br />
<br />
		public string this[string setting_name, string default_value] <br />
		{<br />
			get<br />
			{<br />
				try <br />
				{<br />
					if (this.Contains(setting_name))<br />
					{<br />
						string val = base[setting_name].ToString();<br />
						if (val==null || val.Length==0) return default_value;<br />
						return val;<br />
					}<br />
					else if (m_upperlower.ContainsKey(setting_name.ToLower())) <br />
					{<br />
						string val = base[m_upperlower[setting_name.ToLower()].ToString()].ToString();<br />
						if (val==null || val.Length==0) return default_value;<br />
						return val;<br />
				<br />
					}<br />
					else if (this.Contains(setting_name.ToLower()))<br />
					{<br />
						string val = base[setting_name.ToLower()].ToString();<br />
						if (val==null || val.Length==0) return default_value;<br />
						return val;<br />
					}<br />
				 <br />
					else<br />
						return default_value;<br />
				} <br />
				catch<br />
				{<br />
					return default_value;<br />
				}<br />
			}<br />
			set<br />
			{<br />
				this.Add(setting_name, value);<br />
			}<br />
		}<br />
<br />
<br />
		public string this[int id]<br />
		{<br />
			get<br />
			{<br />
				if (m_sortorder.Contains(id))<br />
					return this[m_sortorder[id].ToString()];<br />
				else<br />
					return null;<br />
			}<br />
		}<br />
<br />
<br />
		public static bool operator == (IniSection lhs, IniSection rhs)<br />
		{<br />
			if ((object)lhs==null && (object)rhs==null) return true;<br />
			if ((object)lhs==null || (object)rhs==null) return false;<br />
			if (lhs.Name != rhs.Name) return false;<br />
			foreach(DictionaryEntry entry in lhs)<br />
			{<br />
				if (!rhs.Contains(entry.Key)) return false;<br />
				string val1=entry.Value.ToString();<br />
				string val2=rhs[entry.Key].ToString();<br />
				if (val1 != val2) return false;<br />
			}<br />
			return true;<br />
		}<br />
<br />
		public static bool operator != (IniSection lhs, IniSection rhs)<br />
		{<br />
			if (lhs==rhs) return false;<br />
			return true;<br />
		}<br />
<br />
<br />
public override bool Equals(object obj)<br />
		{<br />
			if (!(obj is IniSection))<br />
				return false;<br />
			return (this==(IniSection)obj);<br />
		}<br />
<br />
		<br />
		/// <summary><br />
		/// Renders the Ini section<br />
		/// </summary><br />
		/// <returns>a string containing the Ini section</returns><br />
		public override string ToString()<br />
		{<br />
			System.Text.StringBuilder sb = new System.Text.StringBuilder();<br />
			sb.AppendFormat("[{0}]{1}",m_name, Environment.NewLine);<br />
<br />
			for(int c=0;c<m_sortorder.Count;c++)<br />
			{<br />
				string keyname =  m_sortorder[c].ToString();<br />
				string keyval = this[keyname].ToString();<br />
				sb.AppendFormat("{0}={1}{2}",keyname,keyval, Environment.NewLine);<br />
			}<br />
			sb.AppendFormat("{0}{0}", Environment.NewLine);<br />
			return sb.ToString();<br />
		}<br />
<br />
		<br />
		<br />
		/// <summary><br />
		/// Add a Value to this section<br />
		/// </summary><br />
		/// <param name="setting">name of the setting</param><br />
		/// <param name="setting_value">value of the setting</param><br />
		public override void Add(object setting, object setting_value)<br />
		{<br />
			int item_id = this.Count;<br />
			if (!this.ContainsKey(setting.ToString()))<br />
			{<br />
				m_sortorder.Add(item_id, setting.ToString());<br />
				m_upperlower.Add(setting.ToString().ToLower(),setting.ToString());<br />
				// ensure string<br />
				base.Add(setting.ToString(), setting_value.ToString());<br />
			}<br />
			else<br />
			{<br />
				this[setting]=setting_value;<br />
			}<br />
		}<br />
<br />
	<br />
		/// <summary><br />
		/// Display name of the section: [title]<br />
		/// </summary><br />
		public string Name<br />
		{<br />
			get { return m_name; }<br />
		}<br />
		private string m_name;<br />
<br />
	<br />
		public override int GetHashCode()<br />
		{<br />
			return base.GetHashCode ();<br />
		}<br />
<br />
		<br />
<br />
	}<br />
<br />
}<br />
<br />

GeneralRe: Native version Pin
aejw20-Apr-05 8:28
aejw20-Apr-05 8:28 
GeneralSymbols Pin
Wronex12-Apr-05 6:59
Wronex12-Apr-05 6:59 
GeneralRe: Symbols Pin
aejw14-Apr-05 16:32
aejw14-Apr-05 16:32 
GeneralRe: Symbols Pin
Wronex15-Apr-05 23:42
Wronex15-Apr-05 23:42 
GeneralcINI0015.cs Pin
zoranl16-Mar-04 17:48
zoranl16-Mar-04 17:48 
GeneralRe: cINI0015.cs Pin
aejw17-Mar-04 23:34
aejw17-Mar-04 23:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.