A. Motivation
While learning .NET Remoting (see Resources), I decided to consolidate the practice source code into a small number of files. To reduce typing mistakes, I also wanted to automate the process of compiling/re-compiling the executables. For both efficiency and ergonomics, this is a win-win situation (no pun intended, although most of us work with Windows daily). The prospect of repetitive typing is not appealing, especially to people who do not touch-type. Although the DOS command line recall and history buffer can be helpful, it is cumbersome to retrieve the proper DOS commands because of the issuance of intermediate/disbursed commands (i.e., the number of command history recall is not constant). An automated approach would be a welcome relief to aching wrists.
Admittedly somewhat complex for a small code base, this NMAKE demonstration will combine C# compilation options, DOS batch file, and DOS batch script commands.
B. Organization
The overall solution is assembled with the following parts:
- DOS batch file ("makefile.bat") -
This is the file being invoked from the DOS command prompt. It contains the preliminary validation of parameters and provides "test mode" (displaying the commands without execution). After successful validation, it invokes NMAKE with appropriate parameters.
- Macros file ("macros.def") -
For brevity, macro processing logic is separated into this file to reduce clutters in the main make file. This file performs the bulk of macro parameter validations and is included into the main make file by the NMAKE preprocessing directive (!INCLUDE). Please be aware that NMAKE macros are CASE-SENSITIVE!
- Make file ("makefile") -
This main make file contains the various description blocks for building the files. NMAKE preprocessing directives allow the selection of description blocks based upon the macros defined in the NMAKE command-line.
- Source files -
Majority of source files are the dependents of the description block. They include the C# source code, icon, and .NET configuration files. Conditional compilation is used to organize the C# code blocks.
Although conditional compilation is frowned upon by some, it can be a viable solution where performance is critical and/or resources are scarce (e.g., embedded devices and real-time applications). Clarity can be achieved through proper placement, organization, and simple logic expression. To accomplish this objective, Visual Studio code editor outlining feature is employed by enclosing code blocks using #region ... #endregion.

Figure 1. Using #region ... #endregion to enclose code blocks.
C. Data Flow
The primary interface to the user is the DOS batch file. It passes the appropriate command line parameters to NMAKE. To permit "dry-run" of the make file, "test mode" is provided to display the command sequence only.
Dry-run is performed by specifying "test" as the first parameter to the DOS batch file ("makefile.bat"). Behind the scenes, this is accomplished by using the "/N" option of NMAKE.

Figure 2. Content of "makefile.bat"
All parameters to the DOS batch file (excluding "test") are passed along to NMAKE. These parameters can include both macro definitions and targets. On the DOS command line, a macro definition is specified as "<macro name>=<value>". This distinguishes it from the target.

Figure 3. A sample "makefile.bat" command line.
Within the make file, the target and macro definitions determine the desired build configuration. Additionally, the macros are passed along to the C# source file (through C# compiler options). The macros are also required within the C# source code for conditional compilation (see Figure 1).
For this example, only the definition/existence of the macros is needed. Thus, macros for conditional compilation can be assigned any arbitrary value, including null/empty string. To reduce typing, this is utilized on command line.
D. Details
DOS batch file ("makefile.bat") contains the following:
@ECHO OFF
if "%1" == "" goto End
if "%1" == "test" (if "%2" == "" (goto End) ELSE nmake /n /nologo /f
makefile %2 %3 %4 %5 %6 %7 %8 %9)
if NOT "%1" == "test" nmake /s /nologo /f makefile %1 %2 %3 %4 %5 %6 %7 %8
:End
if %ERRORLEVEL% EQU 0 echo Makefile completed.
As briefly described above, this file detects "test mode" and passes along the appropriate parameters to NMAKE. Positional parameter (e.g., "%1") is utilized to determine whether dry-run has been specified. The default number of positional parameters is sufficient for this example, due to the limited number of macros required. However, it is possible to extend the number of parameters by utilizing the "SHIFT" keyword.
Please pay close attention when specifying macros and targets because NMAKE is CASE-SENSITIVE. This can be the source of significant debug time.
The make file for this example consists of two files: the macros file and the main make file. Macros file ("macros.def") contains the following: (for clarity, this has been abridged)
#*** Consolidate CSC compiler switches ***
define=""
!IF DEFINED(CAO) && DEFINED(SOAPsuds) && DEFINED(ConfigFile)
define=$(defConfigFile) $(defCAO)
!ELSEIF DEFINED(CAO) && DEFINED(defCAO)
define=$(defCAO)
!ELSEIF DEFINED(SAO) && DEFINED(defSAO)
define=$(defSAO)
!ELSEIF DEFINED(CAO) || DEFINED(SAO)
!ERROR Invalid combination of macros.
!ENDIF
As the name suggests, the macros file contains the bulk of the code that processes macros. The conditional logic using the NMAKE preprocessing directives are illustrated here. Regrettably, code indentation is lacking, since the assignment of macro must begin with the first character (leading white spaces are not allowed).
It is important to stress once more: macros are CASE-SENSITIVE. This is a common source of bugs, and it is not easy to discover visually. The main make file ("makefile") contains the description blocks that define the build configuration. An abbreviated version is listed below:
#*** Obtain macro definitions from external file ***
!INCLUDE macros.def
!IF DEFINED(CAO) && DEFINED(SOAPsuds)
#*** Rules: CAO with SOAPsuds scenario ***
!MESSAGE Buliding CAO SOAPsuds...
all: Server.exe Meta.dll Client.exe
Server.exe: AssemblyInfo.cs Network.ico Server.cs
csc /t:exe /win32icon:Network.ico /m:Server.StartUp /out:$@
$(debug) $(define) /nologo
AssemblyInfo.cs Server.cs
!IF DEFINED(ConfigFile)
@copy config\Server_CAO_SOAPsuds.config Server.exe.config /v/y
!ENDIF
Meta.dll: Server.exe
soapsuds -ia:Server -oa:$@ -nowp
Client.exe: Meta.dll AssemblyInfo.cs Network.ico Client.cs
csc /t:exe /r:Meta.dll /win32icon:Network.ico
/m:Client.StartUp /out:$@ $(debug) $(define)
/nologo AssemblyInfo.cs Client.cs
!IF DEFINED(ConfigFile)
@copy config\Client_CAO_SOAPsuds.config Client.exe.config /v/y
!ENDIF
!ELSE
...
!ENDIF
#*** Rule: Clean-up generated/extraneous files ***
clean:
-@ echo Clean-up...
-@ if EXIST Shared.dll del /f /q Shared.dll
-@ if EXIST Shared.pdb del /f /q Shared.pdb
-@ if EXIST Meta.dll del /f /q Meta.dll
-@ if EXIST Client.exe del /f /q Client.exe
-@ if EXIST Client.pdb del /f /q Client.pdb
-@ if EXIST Client.exe.config del /f /q Client.exe.config
-@ if EXIST Server.exe del /f /q Server.exe
-@ if EXIST Server.pdb del /f /q Server.pdb
-@ if EXIST Server.exe.config del /f /q Server.exe.config
-@ if EXIST Server2.exe del /f /q Server2.exe
-@ if EXIST Server2.pdb del /f /q Server2.pdb
-@ if EXIST Server2.exe.config del /f /q Server2.exe.config
As mentioned previously, the bulk of macro processing has been separated into a macros file ("macros.def"). To re-assemble the code, NMAKE preprocessing directive (!INCLUDE) is needed to incorporate the contents of the macros file. In this example, macro processing needs to be performed first. Therefore, the inclusion of the macros file must be the first command statement.
Please note that description blocks are grouped together, using the conditional logic constructed with NMAKE preprocessing directives. Even though there are identical target names, they are not contained within the same bracketed group; therefore, collision is avoided. However, without the separation, the effect of description blocks having identical targets will be cumulative. For additional information, please refer to NMAKE documentation.
The macros started the journey from the DOS command line to NMAKE and finally arrives at the C# compiler of the command. Macros are consolidated into a macro named "define". Its instantiation is denoted by "$(define)" within the make file.
A special description block, with the target name of "all", is placed first. It contains no commands. This block enables the default creation of all targets in situations where no targets are specified. It contains all other targets as its dependents, with the exception of itself and the special target named "clean".
For cleaning up targets and intermediate files, another special description block is introduced as the last block. Its target is intuitively named "clean" and contains no associated dependents. The included commands perform the removal of these files. Please note that each command is prefixed by a command modifier ("-"). This signifies that any error returned by the associated command will be ignored. In the default configuration, NMAKE would terminate processing when it encounters errors in any command execution. Each command contains the DOS batch command for the file removal. Note the at sign (@) following the command modifier is part of DOS. It notifies the DOS command processor to disable the default echoing of commands. To avoid errors, the file removal command ("del ...") would not be executed unless the file exists.
To further enhance this example to be as self-contained as possible:
- Explicitly specify all referenced assemblies, without reliance on default inclusion by the "CSC.RSP" response file.
- Include the "vsvars32.bat" to initialize the Visual Studio .NET command prompt environment. This would allow the sample to run from the regular DOS command prompt.
To illustrate the typical content of the C# source code, the abbreviated listing of "Server.cs" is presented here:
using ...
namespace Server
{
...
#region Class_CAO_SOAPsuds
#if CAO && SOAPsuds
// Client-Activated-Object (CAO)
public class CAOobject: MarshalByRefObject, IDisposable
{
...
}
#endif
#endregion
class StartUp
{
/// <summary>The main entry point for the
application.</summary>
static void Main(string[] args)
{
#region Remote_Channel
#if ConfigFile
string configfile =
AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
RemotingConfiguration.Configure(configfile);
#else
HttpChannel chnl = new HttpChannel(1234);
ChannelServices.RegisterChannel(chnl);
#endif
#endregion
...
#region RemoteRegister_CAO_SOAPsuds
#if CAO && SOAPsuds
#if !ConfigFile
//*************************************************
// CAO
//*************************************************
// URL =
http://<hostname>:<port>/<ApplicationName>
RemotingConfiguration.ApplicationName = "ServerCAO";
RemotingConfiguration.RegisterActivatedServiceType(typeof(CAOobject));
#endif
#endif
#endregion
Console.WriteLine("Server.Main(): server started");
Console.ReadLine();
}
}
}
The C# source code utilizes the macros from the command line, in the form of compilation symbols. These symbols are derived from the macros by the make file and are necessary for conditional compilation of the C# source code.
As stated previously, the clarity of conditional compilation is dependent upon the strategic placement. With the assistance of Visual Studio .NET's code inlining feature (the #region ... #endregion construct), each code block is enclosed within an annotated/collapsible code region.
Exploration of .NET Remoting is beyond the scope of this article. For additional information, please consult information listed in the Resources/Links section.
E. Usage
With all said and done, the final objective is simplicity and lightweight. Sample applications are listed here to illustrate the ease of use. Please note that the commands must be executed from Visual Studio .NET's command prompt and within the directory where the source files are located.
To compile executable files for SAO/Singleton scenario:
C:\> makefile "SAO=" "Singleton="
The above is equivalent to the following:
C:\> makefile "SAO=" "Singleton=" all
To remove generated files, use the following command:
C:\> makefile clean
To review the commands without execution ("test mode"):
C:\> makefile test clean
Hopefully, these short-and-sweet commands will satisfy most critics.