using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; using System.IO; namespace IniExample { /// /// Contains native Win32 API methods found in Kernel32. /// public static class Kernel32 { /// /// Retrieves a string from the specified section in an initialization file. /// /// The name of the section containing the key name. If this parameter is NULL, the GetPrivateProfileString function copies all section names in the file to the supplied buffer. /// The name of the key whose associated string is to be retrieved. If this parameter is NULL, all key names in the section specified by the lpAppName parameter are copied to the buffer specified by the lpReturnedString parameter. /// A default string. If the lpKeyName key cannot be found in the initialization file, GetPrivateProfileString copies the default string to the lpReturnedString buffer. If this parameter is NULL, the default is an empty string, "". /// Avoid specifying a default string with trailing blank characters. The function inserts a null character in the lpReturnedString buffer to strip any trailing blanks. /// A pointer to the buffer that receives the retrieved string. /// The size of the buffer pointed to by the lpReturnedString parameter, in characters. /// The name of the initialization file. If this parameter does not contain a full path to the file, the system searches for the file in the Windows directory. /// The return value is the number of characters copied to the buffer, not including the terminating null character. [DllImport("Kernel32.dll", EntryPoint = "GetPrivateProfileStringW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] public static extern int GetPrivateProfileString(string lpAppName, string lpKeyName, string lpDefault, [In, Out] char[] lpReturnString, int nSize, string lpFilename); } /// /// Represents the common INI file format consisting of parameter-value pairs grouped by sections. /// public class INIFile { #region Nested Types public enum CaseSensitiviy { CaseInsensitive, CaseSensitive } #endregion #region Properties /// /// Provides the string value of parameter belonging to the specified section. The section and parameter must exist. /// /// The name of the section the parameter belongs to. /// The name of the parameter containing the value. /// Thrown if either the section or parameter name do not exist. /// The value of the parameter. public string this[string sectionName, string parameterName] { get { if (_sections.ContainsKey(sectionName)) { if (_sections[sectionName].ContainsKey(parameterName)) return _sections[sectionName][parameterName]; else throw new ArgumentException("ErrorBadIniFile"); } else throw new ArgumentException("ErrorBadIniFile"); } } /// /// Specifies what case sensitivity mode is being used when looking up section and parameter names. /// public CaseSensitiviy CaseSensitivity { get { return _caseSensitivity; } } #endregion #region Events #endregion #region Constructors /// /// Loads the specified INI file. By default, all searches for section and parameter names will be case sensitive. /// /// The file name and path to the INI file. If this parameter does not contain a full path to the file, the method will search the Windows directory. /// Thrown if filename could not be found and doesn't exist in the Window directory. public INIFile(string filename) : this(filename, CaseSensitiviy.CaseSensitive) { } /// /// Loads the specified INI file. /// /// The file name and path to the INI file. If this parameter does not contain a full path to the file, the method will search the Windows directory. /// Specifies if letter case should be accounted for when looking up section and parameter names. /// Thrown if filename could not be found and doesn't exist in the Window directory. public INIFile(string filename, CaseSensitiviy sensitivity) { // make sure the file exists, directly, or in the Windows directory if (!File.Exists(filename)) if (!File.Exists(Path.Combine(Environment.ExpandEnvironmentVariables("%WinDir%"), filename))) throw new FileNotFoundException(); // save local state _caseSensitivity = sensitivity; _readBufferSize = _readBufferDefaultSize; _readBuffer = new char[_readBufferSize]; // read all section names string[] raw = GetPrivateProfileString(null, null, null, filename); // the data is stored in a dictionary, we must tell it if case sensitivity is important if (_caseSensitivity == CaseSensitiviy.CaseInsensitive) _sections = new Dictionary>(StringComparer.CurrentCultureIgnoreCase); else _sections = new Dictionary>(); // now, read in all parameters and values for each section name we located foreach (string s in raw) ReadSection(filename, s, _sections); } #endregion #region Public Methods /// /// Checks to see if a given section exists in the Ini file. /// /// The section name to check for. /// True if the section exists, false if not. public bool DoesSectionExist(string sectionName) { return _sections.ContainsKey(sectionName); } /// /// Checks to see if a given parameter exists for a section. /// /// The name of the section containing the parameter. /// The name of the parameter to check for. /// True if the parameter exists, false if not. public bool DoesParameterExist(string sectionName, string parameterName) { if (DoesSectionExist(sectionName)) return _sections[sectionName].ContainsKey(parameterName); else return false; } #endregion #region Private Methods /// /// Reads all parameters and values for a given section within the Ini file. /// /// The ini file to read from. /// The name of the section to read. /// A dictionary containing an entry for the section that will be populated with the resulting parameters/values. private void ReadSection(string inifile, string sectionName, IDictionary> storage) { // create the parameter/value dictionary for the section entry // it is assumed that the callers logic created/validated storage[sectionName] -- we will not check again here if (_caseSensitivity == CaseSensitiviy.CaseInsensitive) storage[sectionName] = new Dictionary(StringComparer.CurrentCultureIgnoreCase); else storage[sectionName] = new Dictionary(); string[] raw = GetPrivateProfileString(sectionName, null, null, inifile); // now read in the values for the parameters that we found foreach (string s in raw) ReadParameter(inifile, sectionName, s, storage[sectionName]); } /// /// Reads the value of a parameter in a given section within the Ini file. /// /// The ini file to read from. /// The name of the section containing the parameter. /// The name of the parameter to read. /// The dictionary that the parameter value will be written to. /// Thrown if more than 1 value is associated with a single parameter. private void ReadParameter(string inifile, string sectionName, string parameterName, IDictionary storage) { // get the param value string[] raw = GetPrivateProfileString(sectionName, parameterName, null, inifile); // store the resulting value, if more than 1 value comes back for the parameter throw an exception, not sure how to parse that scenario // if no values come back just use an empty string by default if (raw.Length > 1) throw new ArgumentException("ErrorBadIniFile"); else if (raw.Length == 0) storage[parameterName] = ""; else storage[parameterName] = raw[0]; } /// /// Wrapper around the Win32 function GetPrivateProfileString to make it nicer and more robust. /// /// The name of the section containing the key name. If this parameter is null, all section names will be returned. /// The name of the key whose associated string is to be retrieved. If this parameter is null, all key names in the section will be returned. /// A default string. If the keyName key cannot be found in the initialization file, GetPrivateProfileString copies the default string to the /// returned value. If this parameter is null, the default is an empty string, "". /// The path and file name of the INI file. If this parameter does not contain a full path to the file, the system searches for the file in the Windows directory. /// An array of string containing either section names, parameter names, or values corresponding to appName and keyName. /// private string[] GetPrivateProfileString(string appName, string keyName, string defaultName, string inifile) { // the file inifile should exist as it's validated in the class constructor, however GetPrivateProfileString does not lock the file hence it could be locked or deleted by // another process between calls to this method // used to tell if we successfully read all requested data from the file // from MSDN: // If neither lpAppName nor lpKeyName is NULL and the supplied destination buffer is too small to hold the requested string, the string is truncated and followed by a null character, and the return value is equal to nSize minus one. // If either lpAppName or lpKeyName is NULL and the supplied destination buffer is too small to hold all the strings, the last string is truncated and followed by two null characters. In this case, the return value is equal to nSize minus two. int sizeReadOffset = (appName != null && keyName != null) ? 1 : 2; // try and read the entire amount of data from the ini file int sizeRead = Kernel32.GetPrivateProfileString(appName, keyName, defaultName, _readBuffer, _readBufferSize, inifile); // if unable to read all of the data because the buffer is to small, increase the size and try again, but only allow memory to increase up to _readBufferMaxSize while ((sizeRead >= _readBufferSize - sizeReadOffset) && (_readBufferSize < _readBufferMaxSize)) { _readBufferSize += 512; _readBuffer = new char[_readBufferSize]; sizeRead = Kernel32.GetPrivateProfileString(appName, keyName, defaultName, _readBuffer, _readBufferSize, inifile); } // if memory consumption exceeded _readBufferMaxSize then we have a problem if (_readBufferSize >= _readBufferMaxSize) throw new FileLoadException("ErrorBadIniLoad"); // the buffer returned from GetPrivateProfileString will be null terminated C-strings followed by a double null at the end - so split the strings on the nulls char[] sep = new char[1]; sep[0] = '\0'; string s = new string(_readBuffer, 0, sizeRead); string[] result = s.Split(sep, StringSplitOptions.RemoveEmptyEntries); return result; } #endregion #region Fields /// /// Stores the sections from the Ini file. The section name is the key. The value is another dictionary containing the parameter values, keyed on the parameter name. /// private IDictionary> _sections; /// /// Determines if case is ignored or not when referencing section and parameter names. /// private CaseSensitiviy _caseSensitivity; /// /// Holds the information from the last read of the ini file. The buffer is overwritten with sections, parameters, or values on each call to GetPrivateProfileString. /// private char[] _readBuffer; /// /// The size, in bytes, of _readBuffer. /// private int _readBufferSize; /// /// The starting size of _readBuffer, in bytes. /// private const int _readBufferDefaultSize = 1024; /// /// The maximum size _readBuffer is allowed to grow to in order to hold all information obtained from GetPrivateProfileString. /// private const int _readBufferMaxSize = 4096; #endregion } }