4.5. An Advanced Program Example

In this section, we provide an advanced example to call DLL functions developed in the Visual C++ domain to perform a serial port testing. This example includes both domain and application models.

4.5.1. Serial Port Testing Configuration

Serial ports range from COM1 to however many COM ports your system has. In some computers, only a couple of ports are available to the user. Generally, COM1 or COM2 can be used by the user, and others are used by the system hardware. The testing for serial ports can be divided into two catalogs; single port closed-loop testing and dual port closed-loop testing. Single port closed-loop testing connects the receiving terminal (pin-2 in DB-9 connector) and the transmitting terminal (pin-3 in DB-9 connector) together. To perform the test, send data to the transmitting buffer, and then try to receive the same data from the receiving buffer. Because these two terminals have been connected together, in a normal case, the received data should be identical to the sent-out data.

Dual port closed-loop testing uses a null model, which means that we connect the transmitting terminal at the first port to the receiving terminal at the second port and connect the transmitting terminal at the second port to the receiving terminal at the first port. In this way, we can test both ports by sending out data from one transmitting terminal at one port, and try to receive this data from the receiving terminal at another port.

In this example, we use the single port closed-loop test to test COM1. Shut down your computer and install a DB-9 serial connector as shown in Figure 4-51. The definition of the pins on DB-9 is also shown in Figure 4-51. A loop-back test is used for this project, which means that pin-2 and pin-3 of a DB-9 serial terminal connector are connected together. In this way, the data sent out will be received back by the buffer of COM1 if the port is working properly.


Figure 4-51.


4.5.2. Develop a Domain Model Class—SerialPort

We want to develop a Domain Model, named SerialPort, for this project. This model is used to manage and control the program data for serial port COM1, such as the serial port number, baud rate, and data values to be sent out and received.

To create a new Domain Model class, select the Smalltalk namespace from the left pane, and click Class|Add Class|Fixed Size from the System Browser window. A new class prototype is created at the bottom space on this window, which is shown in Figure 4-52. The first line is the class declaration, which includes the class name. Following the #, enter SerialPort as the class name for this class. The second line defines the superclass for our Domain Model class. Our superclass should be UI.Model. Enter this between the braces. The third line indicates whether this class is a named or indexed type. Most classes are named type, which means that the name is an identifier for the class. Our class is a named type, so keep it unchanged on this line. The fourth line indicates whether this class is private or public. The default is public (private: false). Keep this default definition and make our class a public class. The fifth line declares the instance variables for this class. We have four instance variables here: cComPort, cBaudRate, cSendDdata, and cReadData. A lowercase c prior to each variable means current. All these variables are instance variables and only belong to an instance of this class. So add these variables between the single quotation marks one by one on that line to replace the original nominal variables, instVarName1 instVarName2. Each variable is separated by a space, as shown in Figure 4-53. The sixth line declares the class instance variables. In this example, no variable of this type is used. Keep this line empty. The seventh line includes namespaces or classes that will be used by this class. Currently, we don't need to use other classes, so keep it blank. The last line defines a category for this class to remind the user later on. You can put anything you like here. We typed Serial Port Testing.


Figure 4-52.



Figure 4-53.


After you finish modifying this class, right-click on this space and select Accept from the pop-up menu to save this modification. Your finished Domain Class should match the one that is shown in Figure 4-53.

Next, we will define these instance variables and generate the associated methods in this class. Right-click the third pane, which now is blank, and select Add from the pop-up menu (Operate menu in VisualWorks). In the resulting dialog box, enter accessing as the protocol and click OK. An accessing protocol appears on the third pane now. In the same way, create two other protocols: operate and initialize-release. Click the accessing protocol from the third pane, highlight the contents on the bottom space, and press the Delete key from the keyboard to delete them. Type

 cComPort
^cComPort.

to create an accessing method for variable cComPort.

Right-click this space and select Accept from the pop-up menu to save this coding. Then keep the accessing protocol selected, highlight the coding above, and press the Delete key from the keyboard to clean this space. Type

    cComPort: aValue
  cComPort := aValue.
self changed: #cComPort.

Right-click this space and select Accept from the pop-up menu to save this coding. In this way, we define the cComPort variable. Also, the purpose of self changed: is to send a changed: message to self to automatically update this variable if it is changed.

In a similar way, we can define another three variables—cBaudRate, cSendData, and cReadData—in the accessing protocol and generate the associated methods, as shown in Figure 4-54.


Figure 4-54.


Click initialize-release in the third pane, and clean the bottom space. Enter the following code to initialize these variables.

     initialize
  cComPort : = 1.
cBaudRate : = 9600.
  cSendData : = 0.
  cReadData : = 0.

Right-click on this space and select Accept from the pop-up menu to save this coding. An initialize method will be added on the fourth pane.

The operate protocol contains three control commands or actions:

  • Setup: used to call DLL functions to initialize and configure the serial port testing hardware.

  • Test: used to send a start command to DLL functions to begin the serial port testing.

  • Exit: used to send a message to the Object Engine to terminate the test program.

On the System Browser, keep the SerialPort class selected on the second pane and the operate protocol selected on the third pane, and delete the contents on the bottom space. Enter Setup and right-click on the bottom space, and select Accept on the pop-up menu to save this method.

In the same way, create two other methods: Test and Exit. The purpose of these methods is to directly interface to the DLL functions defined in the External Interface Class and, furthermore, to use these DLL functions to access the serial port software/hardware to complete the testing in the C/C++ domain. The body of these methods will be developed later on (see Section 4.5.5) based on the prototypes of the DLL functions defined in the External Interface Class in Section 4.5.4. All these methods will be activated by similar actions (Setup, Test, and Exit) defined in the Application Model, SerialPortGUI, which is discussed in the following section. The relationship between the Domain Model (SerialPort) and the Application Model (SerialPortGUI) is that the Application Model is a dependent of the Domain Model.

4.5.3. Develop a Graphic User Interface and an Application Model

Open a new GUI by clicking Tools|New Canvas from the Visual Launcher, and add four input fields. The four aspects are comPort, baudRate, sendData, and readData. Add three action buttons and enter the names for Actions as Setup, Test, and Exit. Install and name this GUI SerialPortGUI, and keep the default superclass, Application Model, unchanged. Define all widgets for this GUI; your finished GUI should match the one that is shown in Figure 4-55.


Figure 4-55.


The following list summarizes the action buttons and aspects used to create the GUI in Figure 4-55.

Action ButtonsAspects
Setup#SetupCom Port:#comPort
Test:#TestBaud Rate:#baudRate
Exit:#ExitSend Data:#sendData
  Read Data:#readData

The type for all input fields is Number, and the Format is single-integer.

In order to communicate between the SerialPortGUI Application Model and the SerialPort Domain Model, we need to define SerialPortGUI as a dependent of SerialPort. In this way, the Application Model can send input data entered by the user from widgets to the Domain Model to update the data in the Domain Model. Also, the Domain Model can feedback any change on the data to the Application Model by this dependent relationship.

To define the SerialPortGUI as a dependent of SerialPort, first create an accessing protocol in the SerialPortGUI class, and then add a new instance variable named serialport in an accessing protocol to the SerialPortGUI class as shown in Figure 4-56. You should first create an instance variable, serialport, and add it onto the tail of the instanceVariableName of the class SerialPortGUI before you finish the last step.


Figure 4-56.


First, we create an accessing method for the variable serialport, which will return an object serialport. Second, we define a value holder for the variable serialport. A changed: message will be sent to this variable if it is updated. Next, we need to initialize this variable to an instance of the SerialPort Domain Model, initialize it as a value holder, and add the Application Model as a dependent of the SerialPort, as shown in Figure 4-57.


Figure 4-57.


We need to create a method to remove this dependence. Add a system method named noticeOfWindowClose to handle this issue, as shown in Figure 4-58. When exiting the program, the Object Engine needs to call this initialize-release method, noticeOfWindowClose, to cancel this dependence.


Figure 4-58.


4.5.4. Develop an External Interface Class

We need to develop a subclass of the External Interface Class to contain all DLL functions created in the Visual C++ domain and use this class as an interface to communicate between Smalltalk and Visual C++ to complete this serial port testing.

Open an External Interface Finder dialog box by clicking Tools|DLL and C Connect from the Visual Launcher. Click New to open a Class Finder dialog box. In the resulting dialog box, keep the Smalltalk namespace selected in the left pane, enter SerialDLL in the Name: input field, and click OK to save this newly created class.

After we have generated a new External Interface Class, we need to declare prototypes of some DLL functions that are contained in this class. Because we want to test the serial port in a real-time mode, we need to define these functions as a thread mode. Real-time mode means that we can write some data into the port and simultaneously pick up or retrieve this data from the port.

VisualWorks supports the multiple-tasks environment by providing a thread processing method, which can be distinguished from the general function call by using a pseudo-qualifier, _threaded, prior to the function declaration in the C prototype.

In the Windows environment, the Smalltalk Object Engine is considered to be a separate process with one thread of control. The Object Engine utilizes a process schedule to manage all Smalltalk processes that currently are running on the Smalltalk platform. The problem is that if you don't use threads, and if one of the processes needs to call some DLL functions to interface to C/C++ functions or drives, a block will stop the current calling process and, furthermore, stop all processes in the sequence to wait for this calling process to finish its function call. After that, the Object Engine can recover the schedule of the process sequence and continue to run. This is no good for multiple-task processing systems. A solution is to use the thread in the Smalltalk platform, which means that if a process needs to call an external function, and possibly block the process sequence, this process needs to declare a thread-calling mode for the function. The Object Engine will assign a thread for this process during the process running. As this process calls some function in this thread mode, a thread is created and utilized by the Object Engine to complete that function call. The Object Engine will schedule the processes to continue to execute the following Smalltalk process and block the calling process until the function returns. In this way, all other Smalltalk processes can continue to run without waiting for the function call to be finished.

One point we want to make is that these threads will not directly map to each Smalltalk process run by the Object Engine but are rather used only for I/O calls via the DLL and C Connect interface. From the DLL and C Connect programmer's point of view, the Object Engine still runs as a single thread, with the threaded interconnect calls running alongside it. An illustration of the relationship between the Smalltalk processes and thread is shown in Figure 4-59.



Figure 4-59.


All threaded calls have nothing to do with the Smalltalk processes. The Object Engine, which is a single thread, is a coordinator that controls the processes and the threaded call during the program runs. If one of the processes needs a threaded call to interface an external I/O function, the Object Engine will make an arrangement and assign a thread for that process and allow that process to wait for that function call until it returns. At the same time, the Object Engine will schedule the following Smalltalk process to continue to run without being blocked and waiting for the I/O function; the block is handled by a thread right now.

In the External Interface Finder dialog box, keep the newly created class SerialDLL selected in the Class list, click procedures from the Category list, and click the New button to create the following DLL functions:

  • int Setup(int arg1, int arg2).

  • int_threaded Write(int arg1).

  • unsigned char_threaded Read(void).

  • int Close(void).

The first function is used to set up and configure the serial port to be tested. The following two functions, Write and Read, are used to send out and receive the data to and from the serial port, respectively. In front of these two functions, a pseudo-qualifier, _threaded, is used to indicate to the Object Engine that these two functions are threaded mode functions and need the Object Engine to assign threads to them. The last function, Close, is used to clean up the setup for the tested serial port.

To create the threaded mode function, in the opened New Procedure dialog box, enter Read in the Name: input field as the function's name. Select unsigned char as the return type from the Return Type: input field. Click OK to save this function. Now, you will find that the function you just created, Read, is displayed on the right-most pane titled Procedures. Select this function and click the Browse button to open a dialog box to take a close look at this function. In the resulting dialog box, select procedures from the middle pane, and Read from the right pane to open this function.


Your displayed function should look like

                 Read
     <C: unsigned char Read(void)>
^self externalAccessFailedWith: errCode

This function is not threaded mode, so we need to modify it to a threaded mode function. To do that, type _threaded just before the function name Read, and just after the return type unsigned char. Your finished threaded mode function should match the one shown in Figure 4-60.


Figure 4-60.


In a similar way, create the Write function, which is shown in Figure 4-61. The Write function has one argument that is a data value to be written into the serial port. Both Read and Write functions have an integer return type that will work as an indicator to show the execution result of calling these functions. 0 means successful and non-zero means that some error happened.


Figure 4-61.


The Setup function is shown in Figure 4-62. Two arguments are contained in the Setup function call; one is the COM port number and the other is the baud rate. The completed Close function is shown in Figure 4-63.


Figure 4-62.



Figure 4-63.


One point is that when you finish modifying the threaded mode function and closing the Browse dialog box, you will find that the modified function remains unchanged, which means that the _threaded pseudo-qualifier is not there (before your function name). Don't worry about that; this is because the External Interface Class is still not refreshed. Click some other menu item and click the procedures menu item again; you will find that the modified function is updated.


We have finished developing our External Interface Class and the declaration of the external DLL functions. Right now we just assume that these DLL functions are available to our program, and continue to code our program to finish it. In Section 4.5.6, we will develop our DLL functions using Visual C++ tools to finish this project.

4.5.5. Complete the Project with the Coding

Figure 4-64 shows the relationship between the Application Model, Domain Model, and the External Interface Class in our project.


Figure 4-64.


The Application Model is only an interface and is used to receive input from the user. Based on the user's input, the Application Model will send the associated commands or messages to the Domain Model, and the latter will call the associated DLL functions defined in the External Interface Class to execute the commands sent by the Application Model. Also, the Domain Model will send feedback coming from the DLL functions to the Application Model—to the value holder of the widget in the GUI, to display and update widgets.

Recall at the end of Section 4.5.2 that we defined an operate protocol and three methods Setup, Test, and Exit, under that category in the Domain Model, and we mentioned that we will finish the method's body in this section after we complete development of the External Interface Class SerialDLL. Now, we are ready to fulfill this job.

4.5.5.1. Coding for the Application Model

In the last section, we defined the Application Model SerialPortGUI as a dependent of the Domain Model SerialPort. When the user clicks the Setup action button from the GUI, the Application Model should assign the value in the ComPort widget entered by the user to the associated variable defined in the Domain Model, cComPort, and send a message to the Domain Model to inform the latter to call the associated DLL function defined in the External Interface Class to initialize and configure the serial port to be tested. Now, open the Application Model SerialPortGUI class by clicking Browse|System from the Visual Launcher. Select the Smalltalk namespace from the left pane in the opened dialog box, select Serial PortGUI from the middle pane, and click actions from the third pane. Select the Setup method from the right-most pane to open it. Delete all contents from the bottom space and enter the code shown in Figure 4-65 into this space.


Figure 4-65.


The first line is a comment inserted by VisualWorks. The second line assigns the value of comPort, which is an input field widget defined in the GUI, to the variable cComPort defined in the Domain Model. That value is entered by the user as the program runs. Here, we consider the instance of Domain Model serialport as a value holder and use the value message to assign its variable. The third line is similar to the second line and assigns the value of the baud rate entered by the user to the associated variable cBaudRate, which is defined in the Domain Model. The fourth line sends a message to the Domain Model and informs the latter to begin to execute the Setup method, which will call the DLL function Setup() defined in the External Interface Class to set up and configure the serial port to be tested. Similarly, here the instance of the Domain Model serialport is considered to be a value holder and the value message is used to send out this message.

Right-click on this coding space and select the Accept item from the pop-up menu to save this method. Click Test from the right-most pane to code for this method as shown in Figure 4-66.


Figure 4-66.


The second line (the first line is a comment) sends a message (Test) to the Domain Model to inform the latter to begin to call the associated DLL functions defined in the External Interface Class to perform the serial port testing. The third line is used to delay the process 100 ms to make the serial port testing ready. You can modify this timing, and even have no delay. The fourth and fifth lines assign the updated data values, which are coming from the serial port in real time, to the widgets in the GUI to display them to the user. Here we use value: to assign these values from the associated variables in the Domain Model, which are enclosed by parentheses, to their partners in the Application Model. The parentheses are necessary because the variables defined inside the parentheses have a higher precedence and can be considered to be a single variable, and can be assigned to the desired destination variable.

Click Exit from the right-most pane to code for this method, as shown in Figure 4-67. This coding is very simple and is similar to the previous coding. It sends an Exit message to the Domain Model to inform the latter to terminate the program. Then it sends a closeRequest message to the Object Engine to request to close the program and GUI. The feedback will be executed by a self changed: method.


Figure 4-67.


4.5.5.2. Coding for the Domain Model

While still in the System Browser window, select the Domain Model SerialPort from the second pane. Click the operate protocol from the third pane and select the Setup method from the fourth pane to open its body. Delete all contents from the bottom space. The purpose of this Setup method is to call an associated DLL function Setup defined in the External Interface Class to set up and configure the serial port to be tested. Enter the code shown in Figure 4-68 into the space.


Figure 4-68.


First, we create a temporary variable rc, which is used to receive the returned value from the call of the DLL function Setup. Then a call of the Setup function is made with two arguments, cComPort and cBaudRate. Both arguments have been assigned values by the associated variables in the Application Model. A warning message will be displayed in a dialog format if this call returns any error. This Setup function is activated by a Setup message sent out from the Application Model when the user clicks the Setup action button in the GUI.

Keep the operate protocol selected from the third pane and select the Test method from the fourth pane to open its body. Delete all contents from the bottom space, and enter the code shown in Figure 4-69 into this space. Similarly, we first create a temporary variable rc to store the feedback from the calling of the DLL function Write. Then, we start to test the serial port by sending a sequence of integer data, cSendData, to the port. If this function call encounters any error, a dialog box with a warning message will be displayed to inform the user. Otherwise, the test will continue and the integer data will be sent to the serial port one by one until the maximum number is reached (255) when the user clicks the Test action button continuously. The data will be reset to 0 from the maximum number and the test will continue. A delay is optional but unnecessary. So the delay command here is enclosed by the quotation marks to comment it out.


Figure 4-69.


After successfully sending out each integer data to the serial port, we need to call another DLL function, Read, to receive that data from the serial port to check whether the test is successful. The last line in the coding fulfills this functionality. An instance variable, cReadData, is used to store the returned data read from the serial port.

Recall in the previous section in the Application Model, SerialPortGUI, that the Test method of that class will use these two instance variables, cSendData and cReadData, to update two input field widgets, sendData and readData, respectively. So the actual update happens in the Domain Model, actually in these two instance variables, cSendData and cReadData. Then, the Test method in the Application Model will use the value holder of each variable to update the widget in the GUI. So the value holder sets the connection between the variables in the Domain Model and the variables in the Application Model.

Keep the operate protocol selected from the third pane and select the Exit method from the fourth pane to open its body. Delete all contents from the bottom space, and enter the code shown in Figure 4-70 into this space. A temporary variable, rc, is used to store the returned value or status of the function calling, then a DLL function, Close, is called to clean up the setup of the serial port testing. A dialog box with a warning message will be displayed if this call encounters any error or mistake.


Figure 4-70.


Now, we finish the coding for both the Domain and Application Models of this project. It is time for us to take care of the Dynamic Link Library to be developed in the Visual C++ domain and to complete our project.

4.5.6. Develop a Dynamic Link Library in the Visual C++ Domain

The functionality of the Dynamic Link Library is to provide a way to process and respond to the function callings of the interface functions in the External Interface Class, to execute the associated commands in the Visual C++ domain, to complete the interface with the C-code functions and drives, and to perform the serial port testing. The following four interface functions should be contained in this Dynamic Link Library:

  • int Setup(int cPort, int bRate).

  • int Write(int sdata).

  • unsigned char Read().

  • int Close().

4.5.6.1. Definition of the Data Structure and Constants in the Header File

We need to define the following constants and macro for this DLL program, as shown in Figure 4-71. The first three constants are used for the protocol settings of the serial port communication. The following constants are used for the switches settings of the serial port structure. The macro msg(info) is used to simplify the message box function when used later in the program.

Figure 4-71.
							#define MAX_STRING            256
							#define NOPARITY                0
							#define ONESTOPBIT              0
							#define RTS_CONTROL_DISABLE  0x00
							#define RTS_CONTROL_ENABLE   0x01
							#define DTR_CONTROL_DISABLE  0x00
							#define DTR_CONTROL_ENABLE   0x01
							#define msg(info)  All of these local functions are used only inside
MessageBox(NULL,info,"",MB_OK)
						

We also need to define the following structures for the local functions in the DLL file, as shown in Figure 4-72. The enum structure is used to keep the error code clear, because we have quite a few error conditions to handle.

Figure 4-72.
							typedef struct
							{
							char   *pcBuffer;        /*Buffer to store the received data */
							int    iMaxChars;        /*Max number of characters to be received */
							int    piNumRcvd;        /*Actual number of characters received */
							char   cTermChar;        /*Terminal character */
							}CommPortClass;
							typedef struct
							{
							unsigned long  ulCtrlerID;           //serial port ID
							char       cEcho;                    //echo character
							char       cEORChar;                 //end of character
							long       lTimeout;                 //time out value
							long       lBaudRate;                //baud rate
							long       lDataBits;                //data bits
							}SerialCreate,*pSerialCreate;
							typedef enum
							{
							OK     =0,            //no error
							EC_TIMEOUT,           //system time out error
							EC_FOPEN,             //file open error
							EC_INVAL_CONFIG,      //invalid configuration error
							EC_TIMEOUT_SET,       //set structure time out error
							EC_RECV_TIMEOUT,      //receiver time out error
							EC_EXIT_CODE,         //get exit code error
							EC_WAIT_SINGLEOBJ,    //WaitForSingleObject function error
							EC_INVALIDPORT,       //invalid port error
							EC_WRITE_FAIL,        //write function error
							EC_CREATE_THREAD,     //create thread error
							EC_UNKNOWNERROR       //unknown error
							}ERR_CODE;
						

The following are the definitions for the global variables in this DLL file:

       HANDLE hPort;
     char* sPortName;
bool PortCreateflg = false;

The hPort is the handle variable and is used to reserve the newly created file handle. The sPortName stores the port name in a string format. The PortCreateflg is a Boolean variable, which records the status of the port creation (whether a port has been created or not).

The following are the definitions for the local functions used in this DLL file:

                  /* local functions */
ERR_CODE PortInitialize(LPTSTR lpszPortName, pSerialCreate pCreate);
      ERR_CODE PortWrite(char* bByte, int NumByte);
      ERR_CODE PortRead(CommPortClass *hCommPort);
        void WINAPI ThreadFunc(void*hCommPorts);

All of these local functions are used only inside the project and have no connection with the outside functions. That is why they are called local functions. The purpose of these local functions is to call the serial port drives and set the connection between the interface functions and the serial port I/O functions.

  • PortInitialize(): Initialize and configure the serial port based on the port name and baud rate arguments.

  • PortWrite(): Call the serial port drive to write the data byte (bByte) to the serial port. The number of bytes written is determined by the argument NumByte.

  • PortRead(): Call the serial port drive to read back the data written to the port. The argument is a structure pointer, which contains the necessary data buffer and character structure.

  • ThreadFunc(): Because a dead cycle may happen when the PortRead() function is called to read back the data from the port, a ThreadFunc() function is used to avoid a dead cycle.

The following are the definitions for the interface functions in this DLL file:

/*-------------------------------------------------------*/
       #define DllExport __declspec( dllexport )
/*-------------------------------------------------------*/
      DllExport int Setup(int cPort, int bRate);
                 DllExport int Read();
            DllExport int Write(int sdata);
                DllExport int Close();

__declspec(dllexport) is a popular declaration for the general-purpose DLL calling, so we define it as a macro (DllExport) to make the programming more convenient. Four interface functions are defined after this definition, and their functionality is straightforward. These interface functions will call the local functions previously defined to contact the serial port I/O functions to fulfill the testing.

A functional block diagram of this serial port test program is shown in Figure 4-73. This test is divided into three levels in software design. The interface functions will work as an interface between Smalltalk (the External Interface Class) and the Visual C++ Dynamic Link Library, which can be considered as level 1. The local functions belong to level 2, and they set a connection between the interface functions and the serial port I/O functions. The local functions also perform some initialization and preparation before they call the serial port I/O functions. The port I/O functions are considered as level 3, and they will talk to the I/O manager and, furthermore, to the port hardware to perform the port testing. All these definitions are integrated into the header file of this DLL program, which is named SerialDLL.h.


Figure 4-73.


Launch Visual C++ 6.0 and create a new project with the type of Win32 Dynamic-Link Library, and enter SerialDLL to the Project name: input field as the name for this project. Create a new location in the root directory; in our case, it is C:vwdll. Click OK to open this new project. Create a new header file named SerialDLL.h and enter all the previous definitions into this header file. The completed header file is shown in Figure 4-74.

Figure 4-74.
							/******************************************************************************
							*NAME       : SerialDLL.h
							*DESC.      : Header file for SerialDLL.cpp
							*PGRMER.    : Y.Bai
							*DATE       : 5/22/2002
							******************************************************************************/
							#ifndef _SERIALDLL_H_
							#define _SERIALDLL_H_
							#define MAX_STRING            256
							#define NOPARITY                0
							#define ONESTOPBIT              0
							#define RTS_CONTROL_DISABLE  0x00
							#define RTS_CONTROL_ENABLE   0x01
							#define DTR_CONTROL_DISABLE  0x00
							#define DTR_CONTROL_ENABLE   0x01
							#define msg(info)    MessageBox(NULL,info,"",MB_OK)
							typedef struct
							{
							unsigned long ulCtrlerID;           //serial port ID
							char       cEcho;                   //echo character
							char       cEORChar;                //end of character
							long       lTimeout;                //time out value
							long       lBaudRate;               //baud rate
							long       lDataBits;               //data bits
							}SerialCreate,*pSerialCreate;
							typedef struct
							{
							char *pcBuffer;            /* Buffer to store the received data */
							int   iMaxChars;           /* Max number of characters to be received */
							int   piNumRcvd;           /* Actual number of characters received */
							char  cTermChar;           /* Terminal character */
							}CommPortClass;
							typedef enum
							{
							OK       = 0,              //no error
							EC_TIMEOUT,                //system time out error
							EC_FOPEN,                  //file open error
							EC_INVAL_CONFIG,           //invalid configuration error
							EC_TIMEOUT_SET,            //set structure time out error
							EC_RECV_TIMEOUT,           //receiver time out error
							EC_EXIT_CODE,              //get exit code error
							EC_WAIT_SINGLEOBJ,         //WaitForSingleObject function error
							EC_INVALIDPORT,            //invalid port error
							EC_WRITE_FAIL,             //write function error
							EC_CREATE_THREAD,          //create thread error
							EC_UNKNOWNERROR            //unknown error
							}ERR_CODE;
							HANDLE hPort;
							char*  sPortName;
							bool   PortCreateflg = false;
							/*local functions */
							ERR_CODE PortInitialize(LPTSTR lpszPortName,pSerialCreate pCreate);
							ERR_CODE PortWrite(char*bByte,int NumByte);
							ERR_CODE PortRead(CommPortClass *hCommPort);
							void WINAPI ThreadFunc(void*hCommPorts);
							/*---------------------------------------------------------------------*/
							#define DllExport __declspec(dllexport )
							/*---------------------------------------------------------------------*/
							DllExport int Setup(int cPort,int bRate);
							DllExport int Read();
							DllExport int Write(int sdata);
							DllExport int Close();
							#endif
						

4.5.6.2. Definition of the Interface Function Body

Create a new C++ source file by clicking File|New from the Visual C++ 6.0 workspace, select the Files tab, and click C++ Source File from the list. Enter SerialDLL in the File name: input field as this source file name, and click OK to open this newly created source file. Enter the code shown in Figure 4-75 into this file as the interface function bodies.

Figure 4-75.
							/***************************************************************************************
							*NAME      : SerialDLL.cpp
							*DESC.     : DLL functions to interface to rs-232 serial port,called by Smalltalk.
							*PRGMER.   : Y.Bai
							*DATE      : 5/22/2002
							***************************************************************************************/
							#include <stdio.h>
							#include <windows.h>
							#include <stdlib.h>
							#include <string.h>
							#include "SerialDLL.h"
							DllExport int Setup(int cPort,int bRate)
							{
							ERR_CODE rc = OK;
							pSerialCreate pParam;
							pParam = new SerialCreate;             //create a new pointer structure
							pParam->lBaudRate = bRate;             //assign values to variables in structure
							pParam->lDataBits = 8;
							pParam->lTimeout = 3000;
							switch(cPort)                          //check the port number
							{
							case 1:
							sPortName = "COM1";            //port is COM1
							break;
							case 2:
							sPortName = "COM2";            //port is COM2
							break;
							case 3:
							sPortName = "COM3";            //port is COM3
							break;
							case 4:
							sPortName = "COM4";            //port is COM4
							break;
							default:
							return EC_INVALIDPORT;
							}
							if (PortCreateflg)                     //port flag is true means port was set
							{
							msg("Port has been Setup ");
							return rc;
							}
							rc = PortInitialize(sPortName,pParam); //call local func.to configure port
							if (rc != 0)
							msg("ERROR in PortInitialize()!");
							delete pParam;                         //clean pointer structure
							PortCreateflg = true;                  //set port creation flag
							return rc;
							}
							DllExport int Write(int sdata)
							{
							int numByte = 1;                       //define written length as 1 byte
							char sByte[8];
							ERR_CODE rc = OK;
							sprintf(sByte,"%c",sdata);             //convert integer to character
							rc = PortWrite(sByte,numByte);         //call local func.to write the character
							if (rc != 0)
							msg("ERROR in PortWrite()!");
							return rc;
							}
							DllExport int Read()
							{
							int idata;
							char cdata[8];
							ERR_CODE rc = OK;
							CommPortClass* commClass;
							commClass = new CommPortClass;         //create a new pointer structure
							commClass->iMaxChars = 1;              //assign value to the variable in structure
							rc = PortRead(commClass);              //call local func.to read back the data
							if (rc != 0)
							msg("ERROR in PortRead()!");
							sprintf(cdata,"%d",commClass->pcBuffer[0]);
							idata = atoi(cdata);                   //convert the character to integer
							delete commClass;                      //clean up structure
							return idata;
							}
							DllExport int Close()
							{
							ERR_CODE rc = OK;
							if (PortCreateflg)
							CloseHandle(hPort);                //clean up the port handler
							PortCreateflg = false;                 //reset the port creation flag
							return rc;
							}
						

The functionalities of the four interface functions are clearly described line by line in the comments, and they are straightforward. Each interface function body will call the associated local function to fulfill the desired functionality. Also, each interface function body performs some necessary data and structure initializations and assignments based on the arguments passed from the External Interface Class in the Smalltalk domain.

For this application, only four COM ports are allowed to be utilized: COM1 to COM4. You can modify these ports if you have a different configuration on your machine. One point you need to note is that the new keyword is used to create a new structure or pointer structure and assign the memory space for it. You have to use the delete keyword to clean the memory space you allocated for that structure when it is no longer to be used. So the new and delete keywords are partners.

A msg macro is used to display a warning message if the program encounters any error, and this method is very convenient for programmers to debug the code.

4.5.6.3. Definition of the Local Function Body

The next step is to define the local function body. The local function is used to contact the serial port drive, so it is a little complicated. The PortInitialize() local function body is shown in Figure 4-76. First, some data structures are declared at the beginning of this local function, and the function checks whether the port has been created. A port is created by using the function CreateFile() if no port has been generated, and the handle is returned and stored in the variable hPort.

Figure 4-76.
							ERR_CODE PortInitialize(LPTSTR lpszPortName,pSerialCreate pCreate)
							{
							DWORD                dwError;
							DCB                  PortDCB;
							ERR_CODE             ecStatus = OK;
							COMMTIMEOUTS         CommTimeouts;
							unsigned char        dBit;
							// check if the port has been created...
							if (PortCreateflg)
							{
							msg("Port has been initialized!");
							return ecStatus;
							}
							// Open the serial port.
							hPort = CreateFile(lpszPortName,// Pointer to the name of the port
							GENERIC_READ |GENERIC_WRITE,
							// Access (read/write)mode
							0,             // Share mode
							NULL,          // Pointer to the security attribute
							OPEN_EXISTING, // How to open the serial port
							0,             // Port attributes
							NULL);         // Handle to port with attribute to copy
							//If it fails to open the port,return error.
							if (hPort ==INVALID_HANDLE_VALUE )
							{
							// Could not open the port.
							dwError = GetLastError();
							msg("Unable to open the port");
							CloseHandle(hPort);
							return EC_FOPEN;
							}
							PortCreateflg = TRUE;
							PortDCB.DCBlength = sizeof(DCB);
							// Get the default port setting information.
							GetCommState(hPort, &PortDCB);
							// Change the DCB structure settings.
							PortDCB.BaudRate = pCreate->lBaudRate;  // Current baud rate
							PortDCB.fBinary = TRUE;                 // Binary mode; no EOF check
							PortDCB.fParity = TRUE;                 // Enable parity checking.
							PortDCB.fOutxCtsFlow = FALSE;           // No CTS output flow control
							PortDCB.fOutxDsrFlow = FALSE;           // No DSR output flow control
							PortDCB.fDtrControl = DTR_CONTROL_ENABLE; //DTR flow control type
							PortDCB.fDsrSensitivity = FALSE;        // DSR sensitivity
							PortDCB.fTXContinueOnXoff = TRUE;       // XOFF continues Tx
							PortDCB.fOutX = FALSE;                  // No XON/XOFF out flow control
							PortDCB.fInX = FALSE;                   // No XON/XOFF in flow control
							PortDCB.fErrorChar = FALSE;             // Disable error replacement.
							PortDCB.fNull = FALSE;                  // Disable null stripping.
							PortDCB.fRtsControl = RTS_CONTROL_ENABLE; // RTS flow control
							PortDCB.fAbortOnError = FALSE;          // Do not abort reads/writes on error.
							dBit = (unsigned char)pCreate->lDataBits;
							PortDCB.ByteSize = dBit;                // Number of bits/bytes, 4-8
							PortDCB.Parity = NOPARITY;              // 0-4=no,odd,even,mark,space
							PortDCB.StopBits = ONESTOPBIT;          // 0,1,2 = 1, 1.5, 2
							// Configure the port according to the specifications of the DCB structure.
							if (!SetCommState (hPort, &PortDCB))
							{
							// Could not create the read thread.
							dwError = GetLastError();
							msg("Unable to configure the serial port");
							return EC_INVAL_CONFIG;
							}
							// Retrieve the time-out parameters for all read and write operations on the port.
							GetCommTimeouts(hPort, &CommTimeouts);
							// Change the COMMTIMEOUTS structure settings.
							CommTimeouts.ReadIntervalTimeout = MAXDWORD;
							CommTimeouts.ReadTotalTimeoutMultiplier = 0;
							CommTimeouts.ReadTotalTimeoutConstant = 0;
							CommTimeouts.WriteTotalTimeoutMultiplier = 10;
							CommTimeouts.WriteTotalTimeoutConstant = 1000;
							// Set the time-out parameters for all read and write operations on the port.
							if (!SetCommTimeouts (hPort, &CommTimeouts))
							{
							// Could not create the read thread.
							dwError = GetLastError();
							msg("Unable to set the time-out parameters");
							return EC_TIMEOUT_SET;
							}
							EscapeCommFunction(hPort, SETDTR);
							EscapeCommFunction(hPort, SETRTS);
							return ecStatus;
							}
						

After a port is created, the serial communication data structures are generated, such as DCB and COMMTIMEOUTS, which are used to set the serial data structures. Then, the current DCB and COMMTIMEOUTS data structures are retrieved and modified based on our application. Finally, two functions are called to set DTR and RTS communication signals in the rs-232 port.

The local function PortWrite() is shown in Figure 4-77. It has two arguments, *bByte and NumByte, which are the starting address of bytes to be written to the port and the number of the bytes to be written, respectively. The WriteFile() function is called to interface the low-level drive to write bytes into the port. A message box with a piece of warning information will be displayed if this call encounters any error.

Figure 4-77.
							/***************************************************************************
							* NAME      : PortWrite()
							* DESC.     : Write data to the port
							* DATE      : 11/9/00
							* PRGMER.   : Y. Bai
							***************************************************************************/
							ERR_CODE PortWrite(char* bByte, int NumByte)
							{
							DWORD dwError;
							DWORD dwNumBytesWritten;
							ERR_CODE ecStatus = OK;
							if(!WriteFile (hPort,             // Port handle
							bByte,             // Pointer to the data to write
							NumByte,           // Number of bytes to write
							&dwNumBytesWritten,// Pointer to the number of bytes written
							NULL))             // Must be NULL for Windows CE
							{
							// WriteFile failed. Report error.
							dwError = GetLastError ();
							msg("ERROR in PortWrite ..");
							return EC_WRITE_FAIL;
							}
							return ecStatus;
							}
						

The local function PortRead() needs to call a thread to retrieve the data written to the port. The purpose of using a thread to handle retrieving data is to avoid a dead cycle if no event occurs to the serial port. The PortRead() function is shown in Figure 4-78. It has one argument, *hCommPort, which is a pointer structure. This pointer structure will be passed to the thread as a null pointer parameter later on when the PortRead() creates that thread. The purpose of the thread was previously mentioned. First a thread is created, and then a thread function is suspended and will not start to execute until a ResumeThread() function is called. The newly created thread returns a handler, hThread, which will be used by all other components to access the thread.

Figure 4-78.
							/*****************************************************************************
							* NAME     : PortRead()
							* DESC.    : Read data from serial port
							* PGRMER.  : Y. Bai
							* DATE     : 11/10/00
							*****************************************************************************/
							ERR_CODE PortRead(CommPortClass *hCommPort)
							{
							HANDLE hThread;                // handler for port read thread
							DWORD IDThread;
							DWORD Ret, ExitCode;
							DWORD dTimeout = 5000;         // define time out value: 5 sec.
							ERR_CODE ecStatus = OK;
							if(!(hThread = CreateThread(NULL,      // no security attributes
							0,                 // use default stack size
							(LPTHREAD_START_ROUTINE) ThreadFunc,
							(LPVOID)hCommPort, // parameter to thread function
							CREATE_SUSPENDED,  // creation flag - suspended
							&IDThread) ) ) // returns thread ID
							{
							msg("Create Read Thread failed");
							return EC_CREATE_THREAD;
							}
							ResumeThread(hThread);               // start thread now
							Ret = WaitForSingleObject(hThread, dTimeout);
							if (Ret == WAIT_OBJECT_0)
							{
							// data received & process it...
							// Need to do nothing because the data is stored in the hCommPort in Thread Func.
							// close thread handle
							CloseHandle(hThread);
							}
							else if (Ret == WAIT_TIMEOUT)
							{
							// time out happened, warning & kill thread
							Ret = GetExitCodeThread(hThread, &ExitCode);
							msg("Time out happened in PortRead() ");
							if (ExitCode == STILL_ACTIVE)
							{
							TerminateThread(hThread, ExitCode);
							CloseHandle(hThread);
							return EC_RECV_TIMEOUT;
							}
							else
							{
							CloseHandle(hThread);
							msg("ERROR in GetExitCodeThread: != STILL_ACTIVE ");
							ecStatus = EC_EXIT_CODE;
							}
							}
							else
							{
							msg("ERROR in WaitFor SingleObject ");
							ecStatus = EC_WAIT_SINGLEOBJ;
							}
							return ecStatus;
							}
						

After the ResumeThread() function is executed, the thread starts to run. The detailed contents of a thread function will be discussed in the next section. The functionality of the thread function is to retrieve the data written to the serial port and store the data in a buffer defined in the *hCommPort structure for further processing.

A WaitForSingleObject() function is called after the thread runs. This function is used to monitor and wait for one of two events (components) to happen: the running thread, which is associated with the WAIT_OBJECT_0 when it is normally completed and terminated, and the time out, which is associated with the event WAIT_TIMEOUT, and which will happen if the program enters a dead cycle. If the first event happens (WAIT_OBJECT_0), it means that the thread has successfully obtained the data written to the serial port and stored that data in the buffer defined in the *hCommPort data structure, and the function PortRead() will close the thread handler and exit the program because the data has been obtained. If the second event happens (WAIT_TIMEOUT), it means that the program enters a dead cycle and no event triggers the serial port to indicate a data is ready. In that case, the function will also terminate the thread and close the thread handler and return an error signal to the calling function. In any other case, an error status is returned to the calling function to indicate that an error is encountered when executing this PortRead() function.

4.5.6.4. Definition of the Thread Function

The thread function is the real body of the thread, which will execute the task assigned by the thread. In our application, we use a thread to check and retrieve the data written to the port and store the data in the buffer defined in the data structure. The advantage of using a thread to handle this job is to avoid a dead cycle, which would happen if no event triggers the port. The thread function is shown in Figure 4-79.

Figure 4-79.
							/****************************************************************************************
							* NAME    : ThreadFunc()
							* DESC.   : The reason to use this thread is to overcome the hang up introduced by the
							*         : WaitCommEvent() function, which waits for only the comm. event, not synchro-
							*         : nition event. The program will be dead-cycle without this thread.
							* PRMR.   : Y. Bai
							* DATE    : 12/4/00
							***************************************************************************************/
							void WINAPI ThreadFunc(void* hCommPorts)
							{
							char Byte;
							BOOL bResult, fDone;
							int nTotRead = 0;
							DWORD dwCommModemStatus, dwBytesTransferred;
							CommPortClass* CommPorts;
							ERR_CODE ecStatus = OK;
							CommPorts = (CommPortClass* )hCommPorts;
							// Specify a set of events to be monitored for the port.
							SetCommMask(hPort, EV_RXCHAR | EV_CTS | EV_DSR | EV_RLSD | EV_RING);
							fDone = FALSE;
							while (!fDone)
							{
							// Wait for an event to occur for the port.
							WaitCommEvent(hPort, &dwCommModemStatus, 0 );
							// Re-specify the set of events to be monitored for the port.
							SetCommMask(hPort, EV_RXCHAR | EV_CTS | EV_DSR |EV_RLSD| EV_RING);
							if (dwCommModemStatus & EV_RXCHAR||dwCommModemStatus & EV_RLSD)
							{
							// received the char_event & loop to wait for the data.
							do
							{
							// Read the data from the serial port.
							bResult = ReadFile(hPort, &Byte, 1, &dwBytesTransferred, 0);
							if (!bResult)
							{
							msg("ERROR in ReadFile !");
							fDone = TRUE;
							break;
							}
							else
							{
							// Display the data read.
							if (dwBytesTransferred == 1)
							{
							if (Byte == 0x0D ||Byte == 0x0A)    // null char or LF
							{
							CommPorts->piNumRcvd = nTotRead;
							fDone = TRUE;
							break;
							}
							CommPorts->pcBuffer[nTotRead] = Byte;
							nTotRead++;
							if (nTotRead == CommPorts->iMaxChars)
							{
							fDone = TRUE;
							break;
							}
							}
							else
							{
							if (Byte == 0x0D ||Byte == 0x0A)    // null char or CR
							{
							msg("Received null character ");
							fDone = TRUE;
							break;
							}
							}
							}
							}while (dwBytesTransferred == 1); //while (nTotRead < pRecv->iMaxChars);
							}   // if
							}      // while
							return;
							}
						

The thread has an argument, hCommPorts, whose type is a null pointer. This means that this argument can be adopted and converted to any data type. This argument is passed to the thread by the CreateThread() function; it is a data structure that is assigned values by the interface function Read() before it calls the PortRead() local function.

After we declare some local variables and data structures, we need to convert that null pointer parameter, hCommPorts, to the destination data structure, CommPortClass. Next, we need to assign a set of event masks for the serial port controller (UART) by using a SetCommMask() system function. We combine a sequence of event masks by using an or operator to allow multiple events responses, such as received-a-char, DSR, or CTS to be activated, or a ring to be received. If any one of these things happens, the UART considers that an event has occurred.

Then we use a while loop and a system function, WaitCommEvent(), to continuously monitor those events and process the event if any of the events happen. Please note that this function only monitors the serial communication events, not synchronous events. If a received-a-char event happens, we call ReadFile() to read back this character data and store it in a buffer by using


CommPorts->pcBuffer[nTotRead] = Byte

The buffer is defined in a data structure CommPorts, and the local variable, nTotRead, represents the total number of bytes read. Next, we need to check whether we have reached the maximum number of characters we are supposed to read. If we have, we will set the while loop condition flag and exit the loop. Besides checking the maximum number of characters, we also need to check whether we received the null or CR character. In some applications, the end of a received string is marked by a null or CR character. The while loop will also be terminated if either case happens.

After we store the received character data into the buffer, we can terminate the while loop, and, furthermore, end the thread. An associated event, WAIT_OBJECT_0, will be sent to the WaitForSingleObject() function in the PortRead() local function to inform that function that the reading thread is successfully completed and the data has been obtained and stored in a buffer. The PortRead() will return an OK status to the interface function, Read(), to indicate that the PortRead() function is successfully completed. The interface function will pick up the data stored in the buffer, convert it into a suitable format, and return it to the External Interface Class in the Smalltalk domain.

4.5.6.5. Definition of the Definition File

Before we can finish our Dynamic Link Library, we need to generate a definition file for this DLL. The purpose of the definition file is to export all interface functions to the public so anyone can “see” the interface functions by following the definition file.

The definition file for our application is shown in Figure 4-80. The first line is used to declare the name of the Dynamic Link Library. Our four interface functions are listed following the keyword EXPORTS. In this way, all those interface functions can be seen by the public.

Figure 4-80.
LIBRARY SerialDLL.dll 
EXPORTS 
Setup 
Read 
Write 
Close

This definition file is important in the interface between Smalltalk and the DLL developed in Visual C++. Without it, the External Interface Class cannot identify the interface functions at all as your program runs. This bug is difficult to find in some situations, and only an error message, “The External Objects Cannot be Found”, is displayed in the External Interface Class if this error happens. So be careful when you develop a DLL and try to use it to interface with Smalltalk.


4.5.6.6. Build and Install the Complete DLL File

Now click Build|Build SerialDLL.dll from the Visual C++ 6.0 workspace to build our Dynamic Link Library. No error will be encountered if everything is fine. The SerialDLL.dll file will be generated in the Debug folder (you can change the configurations by clicking Build|Set Active Configurations to the Release version if you like).

Open Windows Explorer, and create a new folder under the root: C:stdll. Return to the Visual C++ domain, and click File|Open to open the Open dialog box. Make sure that the project name, SerialDLL, is in the Look in: input field. Double-click the Debug folder from the list, click the down-arrow from the Files of type: input field on the bottom, and go all the way down to the last item, All Files (*.*). Click this item and you will find the resulting DLL file, SerialDLL.dll, in the list. Right-click on this DLL file and click Copy from the pop-up menu, and then click the Cancel button to close the Open dialog box.

Open Windows Explorer, find the newly created C:stdll folder, and click this folder. Then click Edit|Paste to paste this DLL file to this folder. Now, we have finished building the DLL file and installing it in our newly created folder, C:stdll.

Finally, you need to set this new folder to your system path and make it as a new system path variable to your machine. In this way, the computer can locate this DLL file when it is used by your system as your program runs. You can also save the DLL file SerialDLL.dll directly into your default system path, such as C:WINDOWSSYSTEM in Windows Me or 2000. In that way, you don't need to take care of the path, and the computer can automatically locate the DLL file for you as your program runs.

4.5.7. Debug and Test the Project

Most items have been built, and our program is ready to be tested. One more thing we need to do is to configure the External Interface Class and set the connection between the functions defined in the External Interface Class and the interface functions defined in the DLL in the Visual C++ domain.

Perform the following steps to fulfill this configuration. In the Smalltalk domain, open the External Interface Finder by clicking Tools|DLL and C Connect from the VisualWorks Launcher. From the Category list, click the library files item, and click the New button to open the Library File: dialog box, which is shown in Figure 4-81. Enter SerialDLL.dll in the input field as the name for this library file, and click OK to save this setting. You can find this newly added library file from the Library files list on the right-most side, as shown in Figure 4-82.


Figure 4-81.



Figure 4-82.


Next, click the library directories item, which is located just below the library files item, and click the New button to open the Library Directory: dialog box, which is shown in Figure 4-83. Enter C:stdll into the input field as the library directory. Remember, we did save our DLL file in this folder before. Click OK to save this setting. You will find this setting displayed on the Library directories list, as shown in Figure 4-84.


Figure 4-83.



Figure 4-84.


One point you need to pay attention to is that after you complete this configuration, especially for the library file setting, you cannot delete or update that DLL file from Windows Explorer because that DLL file has been hooked with the External Interface Class in the Smalltalk domain. In other words, that DLL has been shared or used by Smalltalk. If you want to make some modifications to the DLL file, you have to first delete that DLL file from the library file in the External Interface Class by selecting it, and then clicking the Remove button to remove it. Then you can delete the old DLL file from Windows Explorer and copy a new one to the C:stdll folder. You can create a new library file and enter the same name (SerialDLL.dll) in the library file input field to recover this library file.


Now, everything is ready for us to run and test our project in the Smalltalk domain. You can activate the project in two ways. One way is to run it from the Resource Finder. In the Resource Finder, select our Application Model (SerialPortGUI) from the Class list, and then click the Start button to run the project, as shown in Figure 4-85.


Figure 4-85.


Another way to run our project is to type the words shown in Figure 4-86 into the workspace, highlight it, right-click on it, then select Do it from the pop-up menu, as shown in Figure 4-86. Your running project is shown in Figure 4-87.


Figure 4-86.



Figure 4-87.


After opening your project, enter 1 in the Com Port input field as the com port number, and enter 9600 into the Baud Rate input field as the baud rate. Then click the Setup action button to set up and configure the serial port. Click Test to begin testing the port by sending a sequence of integers to COM1, and reading back the same data from the port. The sent-out data and read-back data will be displayed in the Send Data and Read Data input fields, respectively. Continue to click the Test button to test more data. Click the Exit action button to terminate the program.

You can add or modify the delay time between sending out and reading back the data. The effect is that the data displayed on the Read Data input field will be delayed for a certain time, which is equal to the delay time you set in the program. The reading back data will be one number less than that of sending out if this delay time is over 100 ms. Now we have finished developing this serial test project and are ready to save it.

4.5.8. File Out Project and Save the Project as a Parcel

We want to file out all classes we developed for this project. We need to file out those classes one by one; first is the Domain Model, SerialPort. To do this, open the System Browser, select Smalltalk as the namespace from the left pane, and select SerialPort as the class from the second pane. Deselect all protocols from the third pane. Then click Class|File Out As . . . from the menu bar to open the Save As dialog box, which is shown in Figure 4-88.


Figure 4-88.


The default directory is image; of course, you can select any directory you like. The class name SerialPort.st has been displayed in the File name: input field. Click the Save button to save this class.

In a similar way, file out two other classes, SerialPortGUI and SerialDLL. You will find that all those classes have been filed out and stored in the C:vw5i4image directory on your machine. Now package our project as a parcel. Refer to Section 4.3.7 and package our class one by one in the following manner. From the Visual Launcher window, click Browse|All Parcels to open the Parcel Class Browser dialog box. On the top-left pane, right-click the mouse to open a pop-up menu, and select the New item. A small prompter asks for a name for the new parcel. Enter SerialPort as the parcel name, and click OK. The parcel name SerialPort is added to the list and is selected. From the middle pane, try to find our SerialPort class, and select it. Right-click the mouse to open a pop-up menu, then select Parcel|Move to|SerialPort.

To write the parcel out as a file, make sure SerialPort is selected in the parcel list. Right-click the mouse to open a pop-up menu, then select Save . . .. The Parcel Publishing dialog box displays. The default checkbox is save source file. Keep this unchanged, and click OK to write your parcel as a file. The default directory for storing the parcel file is the current directory, usually the image directory. Now, open the image folder on your computer; you will find that two files are there: SerialPort.pcl and SerialPort.pst. The former is a pcl file and the latter is an office data file.

In a similar way, package the class SerialPortGUI and SerialDLL. Now we have finished filing out and packaging our project. All projects, including the Dynamic Link Libraries, developed in this chapter are stored on the accompanying CD-ROM. You can find them in the Chapter 4VisualWorks folder. The contents of the folder are arranged in the following way:

Chapter 4
 Visual Works
  SimpleRandomSimpleRandom project including the DLL file
  RandonAnalysisRandomAnalysis project including the DLL file
  SerialPortSerialPort project including the DLL file
  stdllAll DLL files utilized by projects in this folder
  vwdllAll DLL files' source code
  SmallMatlabSmalltalk and Matlab project including the DLL file

Each folder contains the class file-out files (.st), parcel file (.pcl), and office data file (.pst). All DLL files used by projects in this folder are stored in the stdll folder. All source codes of the DLL files are located in the vwdll folder.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.8.162