< C++ .Net System Prog. 3 | Main | C++ .Net System Prog. 5 >


 

Early Stages of the C++ .Net 18

(Managed Extensions for C++)

 

 

The following are the topics available in this page.

  1. Diagnostic Switches

  2. Remoting

  3. Startup

  4. Configuration Section Handlers

 

 

 

 

Diagnostic Switches

 

The <system.diagnostics> section has several values that you can use to determine how debugging actions and tracing occurs in your application. However, it is interesting to take a look at one of the sections, <switches>. If you have a need for an integer value in a configuration file, you can put it in the <switches> section and read the value directly using a class derived from Switch. This class is abstract, and the .NET Framework provides an implementation named BooleanSwitch that interprets the value as either true or false. The documentation in MSDN says that you should enable tracing or debugging in your application to use switches. However, you can safely ignore this advice because the runtime makes no check. Using BooleanSwitch is simple: in the configuration file, you add a <switches> section, as shown here:

<!-- Bool.exe.config -->

<configuration>

   <system.diagnostics>

      <switches>

         <add name="Value" value="1"/>

      </switches>

   </system.diagnostics>

</configuration>

Notice that this setting is a name-value pair. You provide the name of the switch to the constructor of BooleanSwitch, and curiously, you also provide a description of the switch, as shown in the following code:

// bool.cpp

BooleanSwitch* value = new BooleanSwitch(S"Value", S"description");

if (value->Enabled) Console::WriteLine(S"Enabled");

else Console::WriteLine(S"Not enabled");

The description parameter is ignored by the .NET Framework, and we guess this behavior is left over from an earlier version of this class developed during the beta cycle of the Framework classes. The beta versions of this class also allowed you to set a switch as an environment variable or in the registry, but the release version only supports configuration files. If the switch does not exist in the configuration file or if it has a value of zero, the switch is considered not to be enabled, so the Enabled property returns false. Otherwise, the property will be true. The property is read/write, which would suggest that you could use this property to write to configuration files. Alas, this is not the case; the set method of the property is used for another purpose.

The BooleanSwitch derives from Switch, which has a protected virtual method named OnSwitchSettingChanged. The set method of Enabled calls OnSwitchSettingChanged, which does nothing in both BooleanSwitch and Switch. However, you could derive your own class from BooleanSwitch and implement OnSwitchSettingChanged, which performs some action when the switch is changed programmatically. Indeed, you can derive a class from Switch to read a value from the configuration file. Switch has a property named SwitchSetting that will read the switch with the name passed to its constructor. This property is a 32-bit integer.

 

Remoting

 

The remoting section holds many values that are used to configure .NET remoting. We won’t go into the details here because to do so would require a complete description of .NET remoting and contexts. However, it is interesting to note that remoting sections are not automatically read by the system. Instead, you have to explicitly call RemotingConfiguration::Configure and pass the name of the file that contains these configuration sections.

 

Startup

 

An application can indicate that it runs under a specific version of the runtime through the <requiredRuntime> element in a configuration file, as shown here:

<configuration>

   <startup>

      <requiredRuntime version="v1.0.3705.0"/>

   </startup>

</configuration>

The runtime checks the major and minor version given in the configuration file. If the specified version of the runtime is not installed on the machine, the application will not be loaded.

 

Configuration Section Handlers

 

The machine.config file can be found in the CONFIG folder in the .NET Framework system folder (in the %systemroot%\Microsoft.NET\Framework\<version> folder, where <version> is the version of the .NET Framework that you have installed). The following Figures show the three versions of Framework installed in Windows XP SP 2 machine.

 

.Net Framework files location

 

Figure 13

 

The following is the files in the version 1.0.xxxx folder.

 

.Net framework files for version 1.0.xxx

 

Figure 14

 

This file contains a description of all the sections that you can include in a configuration file in a section named <configSections>. We will explain this section in more detail in the next section (“Custom Configuration Sections”); however, we want to point out here that these settings indicate the name of a handler that will be used to read the section. For example, here is the value for the <appSettings> section:

<!-- machine.config -->

<configuration>

   <configSections>

      <section name="appSettings"

         type="System.Configuration.NameValueFileSectionHandler,

            System, Version=1.0.3300.0, Culture=neutral,

            PublicKeyToken=b77a5c561934e089"/>

   </configSections>

</configuration>

This value indicates that the handler for this section is NameValueFileSectionHandler in the System::Configuration namespace. As mentioned earlier, this handler is misnamed because it reads key-value pairs. This class is poorly documented in the MSDN library, which merely says: “This type supports the .NET Framework infrastructure and is not intended to be used directly from your code.” However, this type, and the other handlers in the System::Configuration namespace, should have been documented better if only to give a clue as to the type of collection that is used to hold the configuration sections that they handle. The terse documentation in the MSDN library is right in one respect: you do not use these classes yourself; instead, you use a class named ConfigurationSettings. This class has a static method named GetConfig to which you pass the name of the section that you want to read. This method will return an instance of the collections identified in Table 3.

 

Handler

Collection

DictionarySectionHandler

System::Collection::Hashtable

DiagnosticsConfigurationHandler

System::Collection::Hashtable

IgnoreSectionHandler

-

NameValueFileSectionHandler

System::Collection::Specialized::NameValue

NameValueSectionHandler

System::Collection::Specialized::NameValue

SingleTagSectionHandler

System::Collection::Hashtable

 

Table 3.   Collections for .NET Framework Configuration Section Handlers

 

We have not given a collection for the IgnoreSectionHandler class because as the name suggests, when GetConfig is asked to read a section with this type, a NULL pointer will be returned. The implication is that the section is not intended to be read using GetConfig. As an example of using GetConfig, you can get access to the <system.diagnostics> section with the following code:

// sections.cpp

Hashtable* h;

h = static_cast<Hashtable*>(ConfigurationSettings::GetConfig(S"system.diagnostics"));

IDictionary* d = static_cast<IDictionary*>(h->Item[S"switches"]);

IEnumerator* e = d->Keys->GetEnumerator();

 

while (e->MoveNext())

{

   Console::WriteLine(S"switches[{0}] = {1}", e->Current, d->Item[e->Current]);

}

When this code calls GetConfig, the method will create an instance of the DiagnosticsConfigurationHandler class, which will read the specified section and return a Hashtable containing the items. The <system.diagnostics> section can have nested collections. The Hashtable class implements IDictionary, that is, it is an associative container, so we can access the Item property to get a specific item. In this case, we access the <switches> collection, which returns another IDictionary interface that we can use to iterate over all the items in the section.

If you look through the <configSections> section in machine.config, you will see that there are some sections that are declared with <sectionGroup> rather than <section>. Such sections have nested sections, and the <sectionGroup> element allows you to identify the section handler. As you can see with <appSettings>, a section can have a single handler even though it has nested sections, but in this case, the handler provides a collection with all the data. The <sectionGroup> element allows you to provide collections better suited to the nested section. The <sectionGroup> element does not have a type associated with it, so there is no handler and you cannot pass the name of the group to GetConfig. Instead, you have to pass the group name and section within that group concatenated with a /. For example, to get the <webControls> section within the <system.web> section group, you call:

Hashtable* h = static_cast<Hashtable*>(ConfigurationSettings::GetConfig(S"system.web/webControls"));

Custom Configuration Sections

You can create your own configuration section handlers. To do so, you should create a class that implements IConfigurationSectionHandler and add entries to the configuration file to identify the new section handler. For example, the following configuration file defines a new section named <appData>, which we intend to use for my own application data. <appData> is a group, but we have defined just one child section named <window> that is handled by a class within the assembly named WindowConfig This class is in the global namespace. If the class was in a named namespace, we would have to give the complete name, including the namespace using a period as the scope resolution operator.

<!-- dynamicForms.exe.config -->

<configuration>

   <configSections>

      <sectionGroup name="appData">

         <section name="window" type="WindowConfig,dynamicForms" />

      </sectionGroup>

   </configSections>

 

   <appData>

      <window Name = "mainForm" Text = "Test Window"

              Width = "400" Height = "150"

              FormBorderStyle = "FixedDialog">

         <controls>

            <control class = "TextBox" Name = "txt"

               Multiline = "true" Dock = "Top" Height = "50" />

            <control class = "Button" Name = "btn"

               Dock = "Bottom" Height = "50" Text = "Press Me" />

         </controls>

      </window>

   </appData>

</configuration>

In this configuration file, we declare an <appData> section with a child <window> section. <window> is used to describe a form that contains controls. The <window> tag has attributes for the form, and these attributes conveniently have the same name as properties of the Form class. To declare the controls that go on the form, we use a section named <controls> that is a collection of <control> elements. The <controls> and <control> tags are not mentioned in the <configSections> because they do not have a specific handler. It is the responsibility of WindowConfig to parse this data. The <control> element describes a control in the System::Windows::Forms namespace, the class attribute is the name of the control class, and the other attributes are names of properties of the Control class. The WindowConfig class implements IconfigurationSectionHandler, which has a single method named Create:

Object* Create(Object* parent, Object* ctx, XmlNode* sec);

This method is passed the XML of the section that it is to parse in the final para­meter. In my case, this will be the <window> element. The other parameters are not relevant in this discussion. The Create method should parse the XML and then return the configuration object that is returned from the GetConfig method. Note that there is no indication of the type of the object that will be returned from this method. It would have been nice if the designers of the .NET Framework had added an extra attribute to <section> for a developer to provide the type of the configuration object. Instead, you have to call GetConfig and use Object::GetType to determine the type. Our implementation of Create will read the items in the <window> section and use it to construct a Form object. Here is the code:

// dynamicForms.cpp

// This is the handler class for the custom section.

// It creates a window based on the items in the config file.

public __gc class WindowConfig : public IConfigurationSectionHandler

{

public:

   // Create a new Form object based on the

   // data in the config file.

   Object* Create(Object* parent, Object* ctx, XmlNode* sec)

   {  // Make sure that we are passed an XML node from the file.

      // If sec is zero, we cannot create a form.

      if (sec != 0)

      {  // Make sure that we are passed the <window> section.

         if (!sec->Name->ToLower()->Equals(S"window")) return 0;

 

         Form* frm = new Form;

         // Initialize the form's properties with the items

         // passed as attributes of the main node.

         InitProperties(sec, frm);

         // Get the <controls> collection so that we can create

         // the controls on the form.

         XmlNode* controls = sec->Item[S"controls"];

         if (controls != 0)

         {

            // We need to get a display name of

            // system.windows.forms so that we can pass

            // this information to CreateInstance.

            Assembly* swf = frm->GetType()->Module->Assembly;

 

            try

            {

               // Iterate through all of the <control> nodes.

               IEnumerator* en = controls->GetEnumerator();

               while (en->MoveNext())

               {

                  XmlNode* control = static_cast<XmlNode*>(en->Current);

                  // Each node must have a class name.

                  if (control->Attributes->ItemOf[S"class"] == 0)

                     continue;

                  // Create the specified control.

                  ObjectHandle* oh;

                  String* strCtrl = String::Concat(

                     S"System.Windows.Forms.", control->Attributes->ItemOf[S"class"]->Value);

                     oh = Activator::CreateInstance(swf->FullName, strCtrl);

                     Control* ctrl = dynamic_cast<Control*>(oh->Unwrap());

                  // Initialize the properties of the control

                  // from the <control> node attributes.

                  InitProperties(control, ctrl);

                  // Add the control to the form.

                  frm->Controls->Add(ctrl);

               }

            }

            catch(Exception*){/* do nothing */}

         }

         return frm;

      }

      return 0;

   }

private:

   // This is used to initialize the properties of a control

   // using the attributes in a node.

   void InitProperties(XmlNode* node, Control* ctrl);

};

This method parses the XML passed through the sec parameter. We won’t go into the fine details of how to use the .NET Framework XML classes. Instead, we will focus on what the code does with the data. The first action is to create the form, so the code first checks to ensure that the XML refers to the <window> section and then creates a form passing this object and the XML to a member function named InitProperties, which will parse through the element’s attributes and use these to initialize the properties on the Form object. We will show InitProperties in a moment. Next the code obtains the <controls> element, and for each one, it creates a control. Because Create does not know the type of control to create, it has to create instances dynamically through Activator::CreateInstance. We use the overloaded version of this method that has two strings: the name of the assembly that contains the type and the name of the type. The name of the assembly must be the full name of the system.windows.forms assembly. Because the Form class is in this assembly, we can get the assembly object with this line:

Assembly* swf = frm->GetType()->Module->Assembly;

The full name of the assembly is returned through the Assembly::FullName property. Activator::CreateInstance also needs the full name of the type, which we create by prefixing the control name with System.Windows.Forms., using a period as the separator. Activator::CreateInstance returns an ObjectHandle, and we can get the actual object by calling Unwrap and then casting to Control. We can then initialize this object with InitProperties before adding it to the form’s Controls collection. InitProperties looks like this:

 

// dynamicForms.cpp

private:

   void InitProperties(XmlNode* node, Control* ctrl)

   {  // Iterate through all the attributes.

      IEnumerator* en = node->Attributes->GetEnumerator();

      while (en->MoveNext())

      {

         XmlNode* attr = static_cast<XmlNode*>(en->Current);

         PropertyInfo* pi;

         // See if the control has a property with the same name.

         // Note that the capitalization used in the config file must be exactly right.

         pi = ctrl->GetType()->GetProperty(attr->Name);

         if (pi != 0)

         {

            // Enumerated values cannot be converted from strings

            // using the Convert class.

            if (pi->PropertyType->IsEnum)

            {

               // Initialize the property.

               Object* val = Enum::Parse(pi->PropertyType, attr->Value);

               pi->SetValue(ctrl, val, 0);

            }

            else

            {

               // Initialize the property.

               Object* val = Convert::ChangeType(attr->Value, pi->PropertyType);

               pi->SetValue(ctrl, val, 0);

            }

         }

      }

   }

In this code, we iterate through all the attributes of the XML node. We use the name as the name of the control’s property, and the value of the attribute as the value of the property. Once we have the property name, we use reflection to get information about the property, including its type. So that we can set the property, we need to convert the string value given in the configuration file to the actual type of the property. If the property takes a type other than an enumerated type, we can use Convert::ChangeType. For enumerated values, we have to use Enum::Parse. Using this handler is simple, as shown here:

Form* frm = static_cast<Form*>(ConfigurationSettings::GetConfig(S"appData/window"));

if (frm != 0)

   Application::Run(frm);

else

   MessageBox::Show("No form in config file");

 

Because the <window> section is a child section, we use appData/window as the name of the section.

 

Part 1 | Part 2 | Part 3 | Part 4 | Part 5 | Part 6 | Part 7 | Part 8

 


 

< C++ .Net System Prog. 3 | Main | C++ .Net System Prog. 5 >