Build a Counter Interface

Relationship between the DLL and the main (client) program.

The counter has a very simple interface that you see below. Value will be stored in an instance variable as a string, but the application class Count and its descendants. CLASS is used here since this class is defined in the DLL so CLASS translates to class _import in the client. The program is implemented as a dialog box application, so the constructor for this dialog class calls on the create function to create an appropriate counter.

CLASS Count
{
public:
    static Count* create();//This is the abstract factory method-it calls on clone.
    virtual void reset()=0;
    virtual void increment()=0;
    virtual void decrement()=0;
    virtual void printValue(char *)=0;// returns the value for the buffer above
    virtual Count* copy()=0;
protected:
    virtual Count* clone()=0;
    Count(){}
};

The create function in this class displays the dialog box below then uses the clone function to create the appropriate type of counter.


Count* Count::create()
{
	choicdlgTDLGClientXfer tb;

	tb.choices.AddString("Integer");
	tb.choices.AddString("Character",true);

	if(choicdlgTDLGClient(&tb,0,IDD_CHOICEDLG,::Module).Execute()==IDOK)
	{
		Count* types[]={&CharCount(),&IntCount()};
		TIntArray type=tb.choices.GetSelIndices();
		return types[type[0]]->clone();
	}
  	exit(0);
	return NULL;
}

Details of the selection process are found in the section describing the DLL.

Actual implementation of the client program.

Once the count has been created you must be able to perform the increment, decrement and clear operation. For each of the buttons there is a call to the application function that executes the appropriate operation.

void CntinterTDLGClient::BNDec ()
{
     // INSERT; Your code here.
     count->decrement();
     changeValue();
}


void CntinterTDLGClient::BNInc ()
{
     // INSERT; Your code here.

     count->increment();
     changeValue();
}


void CntinterTDLGClient::BNReset ()
{
    // INSERT>> Your code here.

     count->reset();
     changeValue();
}

The function changeValue adjusts the value shown in the value box. The standard Static Text Box function SetText is used to change the value.

void CntinterTDLGClient::changeValue()
{

     char buff[255];
     buff[0]='\0';
     count->printValue(buff);
     value->SetText(buff);
}

The client project must have count.lib included to run. This file is created automatically by the DLL build command. A library was created here because I didn't use AppExpertDLL. If I had used AppExpertDLL, the DLL could have been used to create the lib file, using a batch file with the implib command.

Building the DLL for this project

In this DLL I used AppExpert and ClassExpert to build the dialog box used to select the type of counter. I created a DLL project using New Project. I inserted the dialog class and its header as well as the resource files associated with this dialog. Then a created the application which was very simple so I didn't need to debug. I inserted this into the DLL project and wrote a DEF file for the project. When this was done I built the project and the lib file was created automatically. You could have done the same using the AppExpert and AppExpertDLL as we have before.

There is one aspect of this DLL that is important. Notice that except for the abstract class Count, all of the class definitions are in the cpp file. This hides changes in the number and types of descendants of Count from the application using Count.The only knowledge the appplication has of the contents of the DLL is what it sees in the header. If we had declared all of the descendent classes in the header, every time we add a class we would have to recompile the application. Hiding the classes in the cpp file elimintates the need to recompile the application to accommodate new classes.

One other problem with the procedure create is that we used a transfer buffer to access the dialog window list box. The transfer buffer contains a variable of TListBoxData. Contrary to the documentation, TListBoxData is not a descendent to TListBox. Thus to get the select value from the TIntArray returned by GetSelIndices has the first choice in location 0. Since we only have one choice, this choice is in location 0.

    tb.choices.AddString("Integer");
    tb.choices.AddString("Character",true);

    if(choicdlgTDLGClient(&tb,0,IDD_CHOICEDLG,::Module).Execute()==IDOK)
    {
        Count* types[]={&CharCount(),&IntCount()};
        TIntArray type=tb.choices.GetSelIndices();
        return types[type[0]]->clone();
    }