Dexterity: Creating custom ini files

Patrick RothOne question that I've been asked a few times in the past has been - "Can I read/write to other sections of the Dex.ini other than the [General] section with Dexterity?".

Here I presume the questioner would like to have their own section of the dex.ini file to write their own application settings instead of mixing them into the settings that Dynamics GP uses.

The answer to this is question is - No, not natively.  Dexterity only knows about the [General] section of the Dex.ini using the Defaults_Read() and Defaults_Write() functions.  However by leveraging a few Win32 dll function calls we can make this possible to do.  And I'll go you one better - as long as we are writing to our own ini file section, why not just have our own ini file for our application?

At this time, you might consider reviewing the Dexterity help topics under "DLLs" for an overview of how Dexterity can access native 32 bit windows dlls (known typically as Win32 dlls).  Note that this is NOT using COM method calls such as we would use with Microsoft Outlook or ADO (ActiveX Data Objects).

To start, we need to build the prototypes in Dexterity that model for Dexterity what the dll expects for parameters so that we know how to cast them for the method call.  The parameters were taken from the Windows SDK.  Because these are the C definitions, the datatypes given in the SDK must be translated to Dexterity.  The prototypes shown below show the Dexterity and original C definitions commented out.

 

procedure GetPrivateProfileStringA@kernel32.dll

 out long return_value;

in string lpAppName;
in string lpKeyName;
in string lpDefault;
inout string lpReturnedString;
in integer nSize;
in string lpFileName;

extern 'GetPrivateProfileStringA@kernel32.dll', 
    return_value,
   lpAppName,
  lpKeyName,
  lpDefault,
  lpReturnedString,
   nSize,
  lpFileName;
 

{
C Header from Windows SDK
DWORD GetPrivateProfileString(

    LPCTSTR  lpAppName,     // points to section name 
    LPCTSTR  lpKeyName,       // points to key name 
    LPCTSTR  lpDefault,       // points to default string 
    LPTSTR  lpReturnedString,   // points to destination buffer 
    DWORD  nSize,           // size of destination buffer 
    LPCTSTR  lpFileName       // points to initialization filename 
   );
}

procedure WritePrivateProfileStringA@kernel32.dll

 out long return_value;

in string lpAppName;
in string lpKeyName;
in string lpszString;
in string lpFileName;

extern 'WritePrivateProfileStringA@kernel32.dll', 
  return_value,
   lpAppName,
  lpKeyName,
  lpszString,
 lpFileName;
     
{
C Header from Windows SDK
BOOL WritePrivateProfileString(

    LPCTSTR  lpszSection,  // address of section name 
    LPCTSTR  lpszKey,    // address of key name 
    LPCTSTR  lpszString, // address of string to add 
    LPCTSTR  lpszFile   // address of initialization filename 
   );
}

Once the prototypes procedures are created and compiled, we could call these procedures as-is.  However it is common and useful to write nicer wrapper functions around them that allows the user to call them easier and perhaps add a few features.

In this case, we could add a bit of code to our function that will get the path to the \Data folder, get the name of the executing dictionary, and then use that to determine the name of the 'ini' file.  We can also add an optional parameter to the functions to allow us to read/write to different sections of the ini file as we wish.

Function INI_Write

 function returns boolean bExists;

in  string lpKeyName;
in  string lpszString;

optional in string lpAppName ="General";

local long return_val;
local string lpReturnedString;
local integer nSize = 255;
local string lpFileName;
local string dictname;

{Get the dictionary name and use as ini file.  This will give us
a custom ini file for our application.  Or we could set the
dictname = "Dex.ini" to use the GP Dex.ini}
dictname = Dict_GetName();
dictname = replace(dictname,"ini",pos(dictname,".",1)+1,3);

lpFileName =  Dict_GetPathname(PATH_DATAFOLDER);
lpFileName = Path_MakeNative(lpFileName);
lpFileName = lpFileName + dictname;

call 'WritePrivateProfileStringA@kernel32.dll',
 return_val,
 lpAppName,
  lpKeyName,
  lpszString, 
    lpFileName;

bExists = true;

Function INI_Read

 function returns string return_string;

in string lpKeyName;
optional in  string lpAppName ="General";

local long return_val;
local string lpDefault = "";

local string lpReturnedString;
local integer nSize = 255;
local string lpFileName;
local boolean bResult;

local string dictname;

{Get the dictionary name and use as ini file.  This will give us
a custom ini file for our application.  Or we could set the
dictname = "Dex.ini" to use the GP Dex.ini}
dictname = Dict_GetName();
dictname = replace(dictname,"ini",pos(dictname,".",1)+1,3);

lpFileName =  Dict_GetPathname(PATH_DATAFOLDER);
lpFileName = Path_MakeNative(lpFileName);
lpFileName = lpFileName + dictname;

call 'GetPrivateProfileStringA@kernel32.dll',
   return_val,
 lpAppName,
  lpKeyName,
  lpDefault,
  return_string,
  nSize,
  lpFileName;

{If we don't get a value back, write an empty key value to the ini file.
Remove this if you don't want this behavior.}
if empty(return_string) then
    bResult = INI_Write(lpKeyName,return_string);
end if;

After the above functions are entered, we can use them with Dexterity function calls.  In this case, we have coded the INI_Read & INI_Write functions to write to an ini file to match the dictionary name in the \Data folder where the Dex.ini also typically resides.

 

Dexterity Test Procedure

 local boolean b;
local currency amount;

local string key1,key2;

{Write 2 string values to the General section and one currency amount to Custom section}
b = INI_Write("key1","my first key");
b = INI_Write("key2","my second key");
b = INI_Write("Amount","100.00","Custom");

{Read the 3 values back into local variables to verify the write was successful}
key1 = INI_Read("key1");
key2 = INI_Read("key2");
amount = value(INI_Read("Amount","Custom"));

Looking in my \Data folder, there is now a TestIni.ini file that didn't exist previously.  The name of my dictionary is TestIni.dic so the name is correct.

Looking at the contents of the file (below), the data is as we would expect.

[General]
key1=my first key
key2=my second key
[Custom]
Amount=100.00

So in your next customization, if you have just a few settings you probably would just continue using the Dex.ini to store them.  However if you have a large number then you might consider using this method to store them in your own application settings ini file.

Note: Storing data in an ini file will only make it available on the single instance of the Microsoft Dynamics GP application on the current workstation.  For data that needs to be available to all workstations, it should be stored in a SQL table.

Patrick

// Copyright © Microsoft Corporation. All Rights Reserved.
// This code released under the terms of the
// Microsoft Public License (MS-PL, https://opensource.org/licenses/ms-pl.html.)