Chapter 6: Developing Phase: Infrastructure Services

This chapter discusses the programming differences between UNIX and Microsoft® .NET Framework. These differences are addressed in the following categories:

  • Signals and events

  • Exception handling in .NET

  • Sockets and networking

  • Interprocess communication

  • Daemons versus services

  • Database connectivity

In addition, this chapter outlines the various options available for converting the UNIX code to .NET in each of these categories and illustrates the options with appropriate source code examples. This information will assist you in choosing the appropriate approach for migrating your application to .NET. You can also use the examples in this chapter as a basis for constructing your .NET application.

*

On This Page

Signals and Events Signals and Events
Exception Handling in .NET Exception Handling in .NET
Sockets and Networking Sockets and Networking
Interprocess Communication Interprocess Communication
Daemons vs. Services Daemons vs. Services
Database Connectivity Database Connectivity

Signals and Events

This section discusses the signal-specific implementations in UNIX and their suitable replacement mechanisms in .NET. It provides different alternatives for converting the signal-specific code on UNIX to event-specific code on .NET and suggests the pros and cons for each alternative.

The UNIX operating system supports a wide range of signals. UNIX signals are software interrupts that catch or indicate different types of events. Microsoft .NET Framework, on the other hand, supports a mechanism called events to provide the same functionality as UNIX signals.

Introduction to Events in .NET

An event is a message sent by an object to signal the occurrence of an action. The action could be caused by user interaction, such as a mouse click, or it could be triggered by some other program logic. The object that raises (triggers) the event is called the event sender. The object that captures the event and responds to it is called the event receiver.

Frequently, objects register themselves with another object to receive notifications for certain events. For example, consider an object that requires a notification when a Button object is clicked. The Button object offers a Click event that enables other objects to receive notification of the Click event. Objects that need to receive the notification create delegates (callback methods) and register themselves with the event. When the user clicks the button, the Button object fires the event, which sends the notification to all objects registered with the Button object.

An event handler is a method that is bound to an event. When the event is raised, the code within the event handler is executed.

In event communication, the event sender class does not know which object or method will receive (handle) the events it raises. What is needed is an intermediary (or a pointer mechanism) between the source and the receiver. The .NET Framework defines a special type (delegate) that meets this purpose and provides the functionality similar to function pointers in C++. Delegates are mainly used to handle the events in the .NET Framework.

Delegate

The delegate in .NET Framework is a class that can hold references to methods that match its signature. It is a type-safe function pointer or a callback method. Events and delegates are closely associated in .NET. An event is a message sent by an object based on the occurrence of some external interaction. Delegate is a form of object-oriented function pointer to invoke the function indirectly through its reference. Events are implemented using delegates. Event delegates are multicast, which means that the delegates can hold references to more than one event handling method, thus enabling flexibility and fine-grained control in event handling. It also acts as an event dispatcher for the class that raises the event by maintaining a list of registered event handlers for the event.

Delegates can be bound to a single method or to multiple methods, referred to as multicasting. Multicasting allows multiple events to be bound to the same method, thus allowing a many-to-one notification. The binding mechanism used with delegates is dynamic, which means that a delegate can be bound at run time to any method whose signature matches with that of the event handler. It can also be used on static methods and to call instance methods on objects. Delegates can also be chained together into a linked-list, so that calling through a delegate calls all the callback methods in the linked-list chain.

A more common use of delegates is to enable an object to implement a callback mechanism that other objects can "register." Implementing event notification registration typically entails having the callback method placed on a list of callback methods that is invoked when an event occurs. When events occur, the "event generating" object calls all the callback methods in its callback list. The event-generating object only looks for the signature of its callback method and does not care about any other details of the called object.

Event Model in .NET

This section describes the elements of the .NET event model and describes the various actions that can be performed:

  • Raising events

  • Raising multiple events

  • Consuming events

The event model in .NET has the following four elements:

  • A class that provides event data.

    public class EventNameEventArgs:EventArgs
    

{      //Declaration of Event Data      //Properties for the Event Data }

  • An event delegate.

    Note: The line has been split into multiple lines for readability.However, while trying it out on a system you must enter it as one line without breaks.

    public delegate void EventNameEventHandler(object 
    

sender,EventNameEventArgs e)

  • A class that raises the event.

    public class ControlName
    

{      //An event declaration      public event EventNameEventHandler EventName;      //A method named onEventName that raises the event protected virtual void OnAlarm(AlarmEvent e){...} }

  • A class that contains the event handler.

    Note: The line has been split into multiple lines for readability.However, while trying it out on a system you must enter it as one line without breaks.

    public class EventTest
    

{     //Wires the handler method to the event     Control.Event += new EventNameEventHandler (EventHandlerMethod);     //Method that handles the event     public void EventHandlerMethod(object sender, EventNameEventArgs e)     {...} }

Raising Events

To raise an event, the following elements are required:

  • A class that holds event data. This class must derive from System.EventArgs.

  • A delegate for the event.

  • A class that raises the event. This class must provide:

    • An event declaration.

    • A method that raises the event.

The event data class and the event delegate class might already have been defined in the .NET Framework class library or in a third-party class library. In that case, you do not have to define these classes.

Raising Multiple Events

If a class needs to raise multiple events, the .NET Framework provides a construct called event properties that can be used with another data structure (of your choice) to store event delegates.

Event properties consist of event declarations accompanied by event accessors. Event accessors are methods that allow event delegate instances to be added or removed from the storage data structure. Note that the event properties are slower than event fields because each event delegate has to be retrieved before it can be invoked. The trade-off is between memory and speed. If the class defines many events that are infrequently raised, consider implementing event properties. Windows Forms controls and ASP.NET server controls use event properties instead of event fields.

The .NET Framework provides a data structure for storing event delegates, the System.ComponentModel.EventHandlerList class, which is used by classes in the .NET Framework that raise multiple events. You can use this class or define your own data structure for storage.

Consuming Events

To consume an event in an application, you must provide an event handler (an event-handling method) that executes program logic in response to the event and registers the event handler with the event source. This process is referred to as event wiring.

Each event handler provides two parameters that allow you to handle the event properly.

Note: The line has been split into multiple lines for readability.However, while trying it out on a system you must enter it as one line without breaks.

//Method that handles the event
public void EventHandlerMethod(object sender, 
EventNameEventArgs e)
{...}

The first parameter, sender, provides a reference to the object that raised the event. The second parameter, e in the earlier example, passes an object specific to the event that is being handled. By referencing the properties—and, sometimes—the methods of the object, you can obtain information such as the location of the mouse for mouse events or the data being transferred in drag-and-drop events.

You can create an event handler at design time within the Windows Forms Designer. Double-click the design surface (either the form or a control) to create an event handler for the default action for that item.

You can create an event handler at run time. This allows you to connect event handlers based on conditions in code at run time instead of having the event handlers connected when the program initially starts.

In the application design, it may be necessary to have a single event handler used for multiple events or the multiple events fire the same procedure. For example, it is a powerful time-saver to have a menu command fire the same event as a button on the form does if they expose the same functionality.

SIGINT Implementation

The following example is a simple instance of catching SIGINT to detect CTRL-C.

UNIX example: Managing SIGINT signal

Note: Some of the lines in the following code have been displayed on multiple lines for better readability.

#include <unistd.h>
#include <stdio.h>
#include <signal.h>
/* The intrpt function reacts to the signal passed in
the parameter signum. This function is called when a 
signal occurs. A message is output, and then the signal
handling for SIGINT is reset (by default generated by 
pressing CTRL-C) back to the default behavior.
*/
void intrpt(int signum)
{
printf("I got signal %d\n", signum);
(void) signal(SIGINT, SIG_DFL);
}
/* main intercepts the SIGINT signal generated when 
Ctrl-C is input. Otherwise, sits in an infinite loop,
printing a message once a second.
*/
int main()
{
(void) signal(SIGINT, intrpt);
while(1) {
printf("Hello World!\n");
sleep(1);
}
}

(Source File: U_EventHandling-UAMV4C6.01.c)

.NET example: SIGINT implementation

SIGINT is implemented in .NET by using the SetConsoleCtrlHandler, the Windows API that adds or removes an application-defined HandlerRoutine function from the list of handler functions for the calling process.

Note: Some of the lines in the following code have been displayed on multiple lines for better readability.

using System;
using System.Threading;
using System.Runtime.InteropServices;
namespace KeyPressEvents
{
/// <summary>
/// Class to catch console control events (Ctrl+C,
Ctrl+Break, etc) in C#.
/// Calls SetConsoleCtrlHandler() in Win32 API
/// </summary>
public class ConsoleAppCtrl: IDisposable
{
/// <summary>
/// Declaration of an enumeration (An enumeration list
that consists of a set of named constants)
/// The events that can be captured by 
SetConsoleCtrlHandler()
/// </summary>
public enum ConsoleEvent
{
CTRL_C = 0,
CTRL_BREAK = 1,
CTRL_CLOSE = 2,
CTRL_LOGOFF = 5,
CTRL_SHUTDOWN = 6
}
/// <summary>
/// Event Handler to be called when a console event
occurs.
/// </summary>
public delegate void ControlEventHandler(ConsoleEvent 
consoleEvent);
/// <summary>
/// Event fired when a console event occurs
/// </summary>
public event ControlEventHandler ControlEvent;
ControlEventHandler eventHandler;
/// <summary>
/// Create a new instance.
/// </summary>
public ConsoleAppCtrl()
{
eventHandler = new ControlEventHandler(Handler);
SetConsoleCtrlHandler(eventHandler, true);
}
~ConsoleAppCtrl()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
}
/// <summary>
/// Disposing the handler
/// </summary>
/// <param name="disposing"></param>
void Dispose(bool disposing)
{
if (eventHandler != null)
{
SetConsoleCtrlHandler(eventHandler, false);
eventHandler = null;
}
}
/// <summary>
/// Event Handler method that is called when the event
occurs
/// </summary>
/// <param name="consoleEvent"></param>
private void Handler(ConsoleEvent consoleEvent)
{
if (ControlEvent != null)
ControlEvent(consoleEvent);
}
/// <summary>
/// PInvoke usage of SetConsoleCtrlHandler present 
in kernel32.dll
/// </summary>
/// <param name="e"></param>
/// <param name="add"></param>
/// <returns></returns>
[DllImport("kernel32.dll")] // PInvoke usage of 
Windows API //SetConsoleCtrlHandler
static extern bool SetConsoleCtrlHandler
(ControlEventHandler e, bool add);
}
class ConsoleClass
{
/// <summary>
/// The Event handler method to capture the Console 
events that occur
/// </summary>
/// <param name="consoleEvent"></param>
public static int iFlag = 0;
public static void MyHandler(ConsoleAppCtrl.ConsoleEvent
consoleEvent)
{
switch(consoleEvent)
{
case ConsoleAppCtrl.ConsoleEvent.CTRL_C:
Console.WriteLine("Ctrl+C Captured");
iFlag = iFlag+1;
break;
case ConsoleAppCtrl.ConsoleEvent.CTRL_BREAK:
Console.WriteLine("Ctrl+Break Captured");
break;
default:
break;
}
}
public static void Main()
{
ConsoleAppCtrl cc = new ConsoleAppCtrl();
cc.ControlEvent += new ConsoleAppCtrl.ControlEventHandler
(MyHandler);
while (true)
{
Console.WriteLine("Hello World!");
Thread.Sleep(1000);
if(iFlag == 2)
{
break;
}
}
}
}
}

(Source File: N_EventHandling-UAMV4C6.01.cs)

Replacing UNIX Signals Within .NET

This section explains how to replace the UNIX signal with the .NET event mechanism. UNIX uses signals to send alerts to processes when specific events occur. A UNIX application uses the kill function to activate signals internally. .NET does not support signals. Therefore, you have to rewrite the existing code to use another form of event notification in .NET.

The following example code illustrates how you can redevelop the UNIX code in .NET. It shows a simple main process that forks a child process, which issues the SIGALRM signal. The parent process catches the alarm and outputs a message when the signal is received.

UNIX example: Managing the SIGALRM signal

Note: Some of the lines in the following code have been displayed on multiple lines for better readability.

#include <unistd.h>
#include <stdio.h>
#include <signal.h>
static int alarm_fired = 0;
/* The alrm_bell function simulates an alarm clock. */
void alrm_bell(int sig)
{
alarm_fired = 1;
}
int main()
{
int pid;
/* Child process waits for 5 sec's before sending
SIGALRM to its parent. */
printf("alarm application starting\n");
if((pid = fork()) == 0) {
sleep(5);
kill(getppid(), SIGALRM);
exit(0);
}
/* Parent process arranges to catch SIGALRM with a 
call to signal and then waits for the child process
to send SIGALRM. */
printf("waiting for alarm\n");
(void) signal(SIGALRM, alrm_bell);
pause();
if (alarm_fired)
printf("Ring...Ring!\n");
printf("alarm application done\n");
exit(0);
}

(Source File: U_EventHandling-UAMV4C6.02.c)

.NET example: Managing the SIGALRM signal

The Timer class in .NET can function as the SIGALARM signal in UNIX. The following example in Managed C++ illustrates how the UNIX example is implemented in .NET.

Note: Some of the lines in the following code have been displayed on multiple lines for better readability.

#include "stdafx.h"
#using <mscorlib.dll>
using namespace System;
using namespace System::Threading;
__gc class CppTimer
{
public:
CppTimer()
{
}
void execTimer(Object *stateInfo)
{
AutoResetEvent* objAutoEvt = dynamic_cast
<AutoResetEvent*>(stateInfo);
Console::WriteLine(S"Ring...Ring!");
//Sets the state of the specified event to signaled.
objAutoEvt->Set();
}
};
void main()
{
CppTimer* objCppTimer = new CppTimer();
//The object of the AutoResetEvent class notifies the 
waiting thread that an event has occured.
AutoResetEvent* objAutoReset = new AutoResetEvent(false);
//TimerCallback delegate represents the method that 
handles the calls from a Timer object.
TimerCallback* objTimerCallBack = new TimerCallback
(objCppTimer, &CppTimer::execTimer);
Console::WriteLine(S"alarm application starting");
//The Timer class represents a mechanism for executing 
a method at specific intervals
Timer* objTimer = new Timer(objTimerCallBack,
objAutoReset,5000,0);
//WaitOne method blocks the current thread until the 
current WaitHandle receives a signal.
objAutoReset->WaitOne(-1,false);
objTimer->Dispose();
Console::WriteLine(S"alarm application done");
}

(Source File: N_EventHandling-UAMV4C6.02.cpp)

Note More information on event handling and code samples for handling and raising events is available at https://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconevents.asp.

Exception Handling in .NET

This section describes the exception handling mechanism in .NET and its advantages and provides some examples of the exception handling process.

An exception is any error condition or unexpected behavior encountered by an executing program. Exceptions can be raised because of a fault in your code or in the code that you call (such as a shared library), unavailable operating system resources, and unexpected conditions that the common language runtime (CLR) encounters (such as code that cannot be verified). Applications can recover from some of these conditions, but not others. Generally, applications can recover from most application exceptions, but not from most run-time exceptions.

In .NET Framework, an exception is an object that inherits from the Exception class. An exception is thrown from an area of code where a problem has occurred. The exception is passed up the stack until the application handles it or the program terminates. All .NET Framework operations indicate failure by throwing exceptions.

Traditionally, error-handling models relied on either the specific language’s unique way of detecting errors and locating handlers for them or on the error handling mechanism provided by the operating system. The runtime implements exception handling with the following features:

  • Handles exceptions without regard for the language that generates the exception or the language that handles the exception.

  • Does not require any particular language syntax for handling exceptions, but allows each language to define its own syntax.

  • Allows exceptions to be thrown across processes and even computer boundaries.

Exceptions offer several advantages over other methods of error notification, such as return codes. These advantages include:

  • Failures do not go unnoticed.

  • Invalid values do not continue to propagate through the system.

  • Programmers do not have to check return codes.

  • Exception-handling code can be easily added to increase program reliability.

  • The exception handling of the runtime is faster than Windows-based C++ error handling.

Exception Handling Model

The runtime uses an exception-handling model based on Exception objects and protected blocks of code. An Exception object is created to represent an exception when it occurs.

.NET supports structured exception handling (SEH), which helps you create and maintain programs with robust, comprehensive error handlers. The SEH code is designed to detect and respond to errors during execution by combining a control structure known as try-catch-finally. Using the try-catch-finally statements, you can protect blocks of code that have the potential to raise errors.

During execution, the CLR creates an exception information table for each executable. Each method of the executable has an associated array of exception handling information (which can be empty) in the exception information table. Each entry in the array describes a protected block of code, any exception filters associated with that code, and any exception handlers (catch statements). This exception table is extremely efficient in terms of performance, use of processor time, and memory use when an exception does not occur. The resources are used only when an exception occurs. The exception information table represents the following four types of exception handlers for protected blocks:

  • A "finally" handler that executes whenever the block exits, whether that occurs by typical control flow or by an unhandled exception. This is useful when a cleanup or closure of resources is critical before continuing to the next section of code.

  • A fault handler that must execute if an exception occurs, but does not execute on completion of typical control flow.

  • A type-filtered handler that handles any exception of a specified class or any of its derived classes.

  • A user-filtered handler that runs user-specified code to determine whether the exception should be handled by the associated handler or should be passed to the next protected block.

Each language implements these exception handlers according to its specifications. For example, Microsoft Visual Basic® .NET provides access to the user-filtered handler through a variable comparison (using the When keyword) in the catch statement; C# does not implement the user-filtered handler.

When an exception occurs, the runtime begins the following two-step process:

  1. The runtime searches the array for the first protected block that:

    1. Protects a region that includes the currently executing instruction.

    2. Contains an exception handler or contains a filter that handles the exception.

  2. If a match occurs, the runtime creates an Exception object that describes the exception. The runtime then executes all finally or fault statements between the statement where the exception has occurred and the statement handling the exception. The order of exception handlers is important; the innermost exception handler is evaluated first. The exception handlers can access the local variables and local memory of the routine that catches the exception; but intermediate values, generated at the time the exception is thrown, are lost.

    If no match occurs in the current method, the runtime searches each caller of the current method and continues this path all the way up the stack. If no caller has a match, the runtime allows the debugger to access the exception. If the debugger does not attach to the exception, the runtime raises the UnhandledException event. If there are no listeners for the UnhandledException event, the runtime dumps a stack trace and ends the program.

UNIX example: Exception handling

Note: Some of the lines in the following code have been displayed on multiple lines for better readability.

#include <iostream.h>
int main () {
  try
  {
    char * str;
    str = new char [100];
    if (str == NULL) throw "New was unable to allocate
memory";
     int i = 0;
    for (;;)
    {
      if (i>9) throw i;
      str[i]='z';
      i++;
    }
  }
  catch (char * strEx)
  {
    cout << "Exception Caught: " << strEx << endl;
  }
  catch (int i)
  {
    cout << "Exception Caught: " <<"Index No." ;
    cout << i << " is out of range" << endl;
  }
  return 0;
}

(Source File: U_ExcepHandle-UAMV4C6.01.cpp)

.NET example: Exception handling

Note: Some of the lines in the following code have been displayed on multiple lines for better readability.

#include "stdafx.h"
#using <mscorlib.dll>
using namespace System;
int main()
{
try
{
// managed char data type declaration
char __gc* str;
str = new char[100];
int i=0;
for(;;)
{
// throwing a user defined exception if i exceeds 9
if(i>9)
throw i;
str[i] = 'Z';
i++;
}
}
//Catching the user defined exception
catch(int i)
{
    Console::WriteLine(S"User defined exception thown 
:{0} ", i.ToString());
}
//Catching the Sytem thrown exceptions - For example
in the statement //"str = new char[100]"
//if new was unable to allocate memory, then a 
NullReferenceException //would have thrown.
catch(Exception* objExcp)
{
Console::WriteLine(S"Exception Caught : {0} ", 
objExcp->Message);
}
__finally
{
Console::WriteLine(S"Statement in finally block – always
executed");    
}
return 0;
}

(Source File: N_ExcepHandle-UAMV4C6.01.cpp)

Note More information on exception handling in .NET is available at https://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconExceptionHandlingFundamentals.asp.

Sockets and Networking

This section describes the sockets- and networking-related implementation mechanisms in .NET and details how to implement sockets- and networking-related code in the .NET application.

The .NET Framework class library includes two namespaces that help you with networking; these are the System.Net and System.Net.Sockets namespaces.

The System.Net classes:

  • Provide a simple, yet complete solution for writing networked applications in managed code.

    Using a layered approach, the System.Net classes enable applications to access the network with varying levels of control, depending upon the needs of the application. The spectrum covered by these levels includes nearly every scenario on the Internet today, from a fine-grained control over sockets to a generic request/response model. In addition, the model is extensible; thus the model can continue to work with your application as the Internet evolves.

  • Expose a robust implementation of the Hypertext Transfer Protocol (HTTP).

    With a large share of Web traffic today going over the HTTP, the importance of HTTP as an application protocol is significant. The System.Net classes support most of the HTTP 1.1 features. The advanced features include pipelining, chunking, authentication, preauthentication, encryption, proxy support, server certificate validation, connection management, and HTTP extensions.

  • Are designed for writing scalable, high-performance middle-tier applications.

    The System.Net classes were designed specifically for a common Web server scenario; a single client browser request makes multiple requests to back-end or external servers. This scenario requires a robust middle-tier networking stack that can stand up to a high load. Such features as connection management, pipelining, keep-alive, and asynchronous send and receive ensure strong support for the middle tier. In addition, because the System.Net classes are part of an overall framework, integration with ASP+ features such as impersonation and caching is seamless.

WebRequest/WebResponse classes and HTTP classes are found in the System.Net namespace, while TCP/UDP and Sockets are found in the System.Net.Sockets namespace.

UNIX example: Server using sockets 

Note: Some of the lines in the following code have been displayed on multiple lines for better readability.

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
void error(char *);
int main(int argc,char *argv[])
{
    int sockfd,newsockfd,portno,clilen;
    char buffer[256];
    struct sockaddr_in serv_addr,cli_addr;
    int n;
    printf("Server Running........\n");
    if(argc<2)
    {
        fprintf(stderr,"ERROR, no port provided\n");
        exit(1);
    }
    sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(sockfd<0)
        error("ERROR Opening Socket");
    bzero((char *) &serv_addr,sizeof(serv_addr));
    portno=atoi(argv[1]);
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=INADDR_ANY;
    serv_addr.sin_port=htons(portno);
    if(bind(sockfd,(struct sockaddr *) &serv_addr,sizeof
(serv_addr))<0)
        error("Error in Binding");
    listen(sockfd,5);
    clilen=sizeof(cli_addr);
    newsockfd=accept(sockfd,(struct sockaddr *) &cli_addr
,&clilen);
    if(newsockfd<0)
        error("ERROR on Accept");
    bzero(buffer,256);
    n=read(newsockfd,buffer,255);
    if(n<0) 
        error("ERROR Reading from Socket");
    printf(" %s\n",buffer);
    printf("the above message was received from the 
client\n");
    char szBuf1[256];
    bzero(szBuf1,256);
    strcpy(szBuf1,"from server to client: hi 
client\0");
    n=write(newsockfd,szBuf1,256);
    printf("n:%d",n);
    if(n<0)
        error("ERROR Writing to Socket");
    return 0;
}
void error(char *msg)
{
    perror(msg);
    exit(0);
}

(Source File: U_Sockets-UAMV4C6.01.c)

.NET example: Server using sockets

Note: Some of the lines in the following code have been displayed on multiple lines for better readability.

#include "stdafx.h"
#using <mscorlib.dll>
using namespace System;
using namespace System::Net;
using namespace System::Net::Sockets;
using namespace System::Text;
using namespace System::IO;
int main()
{
    String* args[] = Environment::GetCommandLineArgs();
    String* serverName = Dns::GetHostName();
    Char asciiChars[] = new Char[S"From the Server- Hi 
Client"->Length];
    asciiChars = S"From the Server- Hi Client"->ToCharArray();
    TcpListener *objTCP = new TcpListener(Convert::ToInt32
(args[1]));
    objTCP->Start();
Console::WriteLine("Server named: {0} waiting on port: {1} ",
serverName,args[1]);
    Socket *objSock = objTCP->AcceptSocket();
    int byteNum = 0;
    String* strRec = "";
    if(objSock->Connected)
    {
            Console::WriteLine(S"Client Connected");
            NetworkStream *objNs = new NetworkStream
(objSock,true);
            StreamReader *objReader = new StreamReader
(objNs);    
            String* readLine = objReader->ReadLine();
Console::WriteLine("Message from the client {0}", readLine);
            StreamWriter *objWriter = new 
StreamWriter(objNs);
            objWriter->Write(asciiChars);
            objWriter->Flush();
    }
    objSock->Close();
    return 0;
}

(Source File: N_Sockets-UAMV4C6.01.cpp)

For High-Performance Computing (HPC) applications, Microsoft provides Windows Compute Cluster Server 2003 as a member of the Windows Server family. It is a specialized 64-bit version server running Windows Server 2003 to support high-performance software. It enables workers to perform multinode workload computing and also supports execution of parallel applications based on the Message Passing Interface (MPI) standard. The latest versions of Visual Studio are designed for parallel computing and parallel debugging capability with MPI support.

Note More information on the Windows Compute Cluster Server 2003 is available at https://www.microsoft.com/windowsserver2003/hpc/.
More information on the System.Net namespace is available at
https://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemnet.asp.
More information on the System.Net.Sockets namespace is available at
https://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfSystemNetSockets.asp.

Interprocess Communication

The operating system designed for multiprocessing or multitasking provides a mechanism for sharing the data between different applications, known as interprocess communication (IPC). This section describes various IPC mechanisms available in the UNIX and .NET environments and provides examples of these processes. The different forms of IPC that are described are:

  • Process pipes

  • Named pipes

  • Shared memory

  • Memory-mapped files

  • Message queues

Sockets, discussed in the previous section, can also be used for IPC. .NET provides extensive support for implementation of sockets. Process pipes, named pipes, memory-mapped files, and shared memory can be implemented in .NET by invoking their respective Microsoft Win32® APIs through P/Invoke. .NET also supports the other forms of IPC, including message queuing (such as Microsoft Message Queuing and IBM MQSeries), .NET Remoting, and COM+.

MPI can also be used as an IPC mechanism for high-performance applications. MPI is a standard application programming interface (API) and specification for message passing, designed for high-performance computing scenarios. The Microsoft MPI in Windows Compute Cluster Server 2003 uses the Winsock Direct protocol for best performance and CPU efficiency.

Note More information on the Microsoft MPI is available at https://technet2.microsoft.com/WindowsServer/en/Library/4cb68e33-024b-4677-af36-28a1ebe9368f1033.mspx.

This section describes how you can convert UNIX code that uses the different forms of IPC. It also introduces new methods of IPC that are not available in UNIX but might provide a better solution that meets the IPC requirements of your application.

Process Pipes

Process pipes are not supported in .NET but can be implemented in .NET using the P/Invoke services. The following example in UNIX shows the implementation of process pipes in UNIX.

UNIX example: Implementation of process pipes

Note: Some of the lines in the following code have been displayed on multiple lines for better readability.

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
    FILE *read_fp;
    char buffer[BUFSIZ + 1];
    int chars_read;
    memset(buffer, '\0', sizeof(buffer));
    read_fp = (FILE *) popen("uname -a", "r");
    if (read_fp != NULL) {
        chars_read = fread(buffer, sizeof(char), BUFSIZ
, read_fp);
        if (chars_read > 0) {
            printf("Output is:\n%s\n", buffer);
        }
        pclose(read_fp);
        exit(EXIT_SUCCESS);
    }
    exit(EXIT_FAILURE);
}

(Source File: U_ProcessPipes-UAMV4C6.01.c)

For implementing this example in .NET, the code has to be first implemented in Win32 by changing the header files and the function names for popen and pclose. After writing the code successfully in Win32, it is compiled into a DLL (ProcessPipes.dll), and then it can be accessed from any .NET project, using the P/Invoke services. The following example shows how the earlier code is written using the Win32 API and then accessed from a C# project using the DllImport attribute.

Win32 example: Implementation of process pipes

Note: Some of the lines in the following code have been displayed on multiple lines for better readability.

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
processPipes()
{
    FILE *read_fp;
    char buffer[BUFSIZ + 1];
    size_t chars_read;
    memset(buffer, '\0', sizeof(buffer));
    read_fp = (FILE *)_popen("uname -a", "r");
    if (read_fp != NULL) {
        chars_read = fread(buffer, sizeof(char), BUFSIZ
, read_fp);
        if (chars_read > 0) {
            printf("Output is:\n%s\n", buffer);
        }
        _pclose(read_fp);
        exit(EXIT_SUCCESS);
    }
    exit(EXIT_FAILURE);
}

(Source File: W_ProcessPipes-UAMV4C6.01.cpp)

.NET example: Using Win32 implementation of process pipes

# using <mscorlib.dll>
using namespace System;
using namespace System.Runtime.InteropServices;
[DllImport("ProcessPipes.dll",CharSet=CharSet::Ansi)]
extern void processPipes();
int main()
{
    processPipes();
return 0;
}

(Source File: N_ProcessPipes-UAMV4C6.01.cpp)

Named Pipes

In this section, examples are given to illustrate how named pipes are implemented in UNIX and .NET. Named pipes are sometimes referred to as first-in-first-out (FIFO) and are generally half-duplex pipes as only one-way communication is possible.

Note More information on the implementation of named pipes for IPC in .NET is available at https://support.microsoft.com/?kbid=871044.

UNIX example: Implementation of named pipes

Note: Some of the lines in the following code have been displayed on multiple lines for better readability.

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME "/tmp/my_fifo"
int main(int argc, char *argv[])
{
    int fd;
    int i;
if (argc < 2) {
    fprintf(stderr, "Usage call once with \"r\" and
then with \"w\"\n");
    exit(EXIT_FAILURE);
}
// Check if the FIFO exists and create it if necessary.
    if (access(FIFO_NAME, F_OK) == -1) {
        fd = mkfifo(FIFO_NAME, 0777);
        if (fd != 0) {
            fprintf(stderr, "Could not create
fifo %s\n", FIFO_NAME);
            exit(EXIT_FAILURE);
        }
    }
    printf("Process %d opening FIFO\n", getpid());
// Open FIFO and output status result
    if (strncmp(argv[1], "r", 1) )
        fd = open(FIFO_NAME, O_RDONLY | O_NONBLOCK);
    else if (strncmp(argv[1], "w", 1) )
        fd = open(FIFO_NAME, O_WRONLY | O_NONBLOCK);
    else {
        fprintf(stderr, "Usage call once with \"r\"
and then with \"w\"\n");
        exit(EXIT_FAILURE);
    }
    printf("Process %d file descriptor: %d\n", getpid()
, fd);
    sleep(3);
// Close FIFO
    if (fd != -1) close(fd);
    printf("Process %d finished\n", getpid());
    exit(EXIT_SUCCESS);
}

(Source File: U_NamedPipes-UAMV4C6.01.c)

The following example creates a named pipe from .NET by making a P/Invoke call to the Win32 API function CreateNamedPipe().

.NET example: Implementation of named pipes

Note: Some of the lines in the following code have been displayed on multiple lines for better readability.

using namespace System;
using namespace System::Runtime::InteropServices;
using namespace System::Diagnostics;
using namespace System::Threading;
[DllImport("Kernel32.dll", CharSet=CharSet::Ansi)]
extern int CreateNamedPipe(char* lpName, int dwOpenMode,
int dwPipeMode, int nMaxInstances, int nOutBufferSize,
int nInBufferSize, int nDefaultTimeOut, 
IntPtr lpSecurityAttributes); 
[DllImport("msvcrt.dll", CharSet=CharSet::Ansi)]
extern int _open(char * lpName, int rdOnly);
[DllImport("msvcrt.dll", CharSet=CharSet::Ansi)]
extern void _close(int fd);
[DllImport("msvcrt.dll", CharSet=CharSet::Ansi)]
extern int _getpid();
int main()
{
    const short FILE_ATTRIBUTE_NORMAL = 0x00000080;
    const int FILE_FLAG_NO_BUFFERING = 0x20000000;
    const int FILE_FLAG_WRITE_THROUGH = 0x80000000;
    const short PIPE_ACCESS_DUPLEX = 0x00000003;
    const short PIPE_READMODE_MESSAGE = 0x00000002;
    const short PIPE_TYPE_MESSAGE = 0x00000004;
    const short PIPE_WAIT = 0x00000000;
    const short INVALID_HANDLE_VALUE = -1;
    const int RDONLY = 0x0000;
    char* pipeName = "\\\\.\\pipe\\mynamedpipe";
    int pipeHandle = 0;
    int fd = -1;
    int processId = _getpid();
pipeHandle = CreateNamedPipe(pipeName,PIPE_ACCESS_DUPLEX
|FILE_FLAG_WRITE_THROUGH,PIPE_WAIT|PIPE_TYPE_MESSAGE
|PIPE_READMODE_MESSAGE,10,10000,2000,5000, IntPtr::Zero);
    if(pipeHandle == INVALID_HANDLE_VALUE)
    {
Console::WriteLine(S"Could not create FIFO {0}", 
Convert::ToString(pipeName));
        return 0;
    }
    Console::WriteLine(S"Process {0} opening FIFO"
, processId.ToString() );
    fd = _open(pipeName,RDONLY);
Console::WriteLine(S"Process {0} file descriptor: {1}", 
processId.ToString(),fd.ToString());
    Thread::Sleep(5000);
    if(fd!=-1)
    {
        (void)_close(fd);
        Console::WriteLine(S"Pipe {0} closed", 
                Convert::ToString(pipeName));
    }
    Console::WriteLine(S"Process {0} finished", 
processId.ToString());
    return 0;
}

(Source File: N_NamedPipes-UAMV4C6.01.cpp)

Shared Memory and Memory-Mapped Files

Shared memory allows two or more threads or processes to share a region of memory. It is generally considered the most efficient method of IPC because data is not copied as part of the communication process. Instead, both the client and the server access the same physical area of memory. The System V IPC mechanisms for shared memory include the shm*() APIs, namely shmat, shmctl, shmdt and shmget. The signatures of these methods are given as follows:

Int shmget(key_t key, size_t size, int shmflg)
void *shmat(int shmid, const void *shmaddr, int shmflg)
int shmdt(const void *shmaddr)
int shmctl(int shmid, int cmd, struct shmid_ds *buf)

UNIX example: Creating a shared memory area and mapping it

A simple example of creating a shared memory area and mapping it in UNIX is as follows:

if ( (fd = open("/dev/zero", O_RDWR)) < 0)
    err_sys("open error");
if ( (area = mmap(0, SIZE, PROT_READ | PROT_WRITE, 
MAP_SHARED, fd, 0)) == (caddr_t) -1)
    err_sys("mmap error");
close(fd);    // can close /dev/zero now that it's mapped

The .NET Framework class library does not provide any direct implementation for shared memory. Win32/Win64 does not support the shm*() APIs. However, MKS and Cygwin support the shm*() APIs for Win32/Win64. Win32/Win64 supports memory-mapped files and memory-mapped page files, which can be used to translate the UNIX code with the shm*() APIs. UNIX shm*() API calls identify the shared memory section with an identification number and Windows memory mapping calls identify with a character string name. The Win32 API functions CreateFileMapping and MapViewOfFile are used for this. It involves making P/Invoke calls to these functions for the .NET implementation of the shared memory. How they can be imported in a C# or Managed C++ .NET project using the DllImport attribute is shown in the following code example.

CreateFileMapping

Note: Some of the lines in the following code have been displayed on multiple lines for better readability.

[DllImport("Kernel32.dll", SetLastError = true)]
extern IntPtr CreateFileMapping(IntPtr hFile,IntPtr
lpFileMappingAttributes,
IntPtr flProtect,IntPtr dwMaximumSizeHigh,IntPtr 
dwMaximumSizeLow,IntPtr lpName);

MapViewOfFile

Note: Some of the lines in the following code have been displayed on multiple lines for better readability.

[DllImport("Kernel32.dll", SetLastError = true)]
extern IntPtr MapViewOfFile(IntPtr hFileMappingObject,
IntPtr dwDesiredAccess,
IntPtr dwFileOffsetHigh,IntPtr dwFileOffsetLow,IntPtr
dwNumberOfBytesToMap);

Note More information on the CreateFileMapping and the MapViewOfFile functions is available at https://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/createfilemapping.asp and
https://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/mapviewoffile.asp.

Message Queues

Microsoft Windows out-of-the box does not support UNIX System V style message queues. However, Windows supports message queues through the use of Microsoft Message Queuing (MSMQ). Additionally, System V-style support is available from third-party products like MKS or Cygwin. MSMQ is used in Windows to perform message queuing in your application. Message queuing is covered comprehensively in other Microsoft documentation and, therefore, is only briefly described here.

Note More information on Microsoft Message Queuing is available at https://www.microsoft.com/windows2000/technologies/communications/msmq/default.mspx.

Message queuing technology enables applications running at different times to communicate across heterogeneous networks and systems that may be temporarily offline. Applications send messages to queues and read messages from queues. Message Queuing provides guaranteed message delivery, efficient routing, security, and priority-based messaging. It can be used to implement solutions for both asynchronous and synchronous scenarios requiring high performance.

Note More information on implementation of MSMQ technology in .NET is available at https://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/bdadotnetasync1.asp.

Daemons vs. Services

This section provides an overview of the UNIX daemons and Windows services, and explains their similarities and differences.

A daemon in UNIX is a process that runs in the background to provide service to other applications and does not require a user interface. A service on Windows is the equivalent of an UNIX daemon. Normally, a daemon is started when the system is booted and runs without supervision until the system is shut down. Similarly, Windows services enable you to create long-running executable applications that run in their own Windows sessions. These services can be automatically started when the computer boots to continue across the logon sessions, can be paused and restarted, and do not show any user interface. Services are ideal for use on a server or for long-running functionality that does not interfere with other users who are working on the same computer. It is possible to run services in the security context of a specific user account that is different from the logged-on user or the default computer account.

Note More information about services and Windows sessions is available at https://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/about_services.asp.

Using Microsoft Visual Studio .NET or the Microsoft .NET Framework SDK, you can easily create services by creating an application that is installed as a service. This type of application is called a Windows Service application. With .NET Framework features, you can create services, install services, start or stop services, and otherwise control their behavior.

The System.ServiceProcess namespace provides classes that allow you to implement, install, and control Windows Service applications. Implementing a service involves inheriting from the ServiceBase class and defining the specific behavior to be processed when start, stop, pause, and continue commands are passed as well as custom behavior and actions when the system shuts down.

Services are installed using an installation tool, such as InstallUtil.exe. The System.ServiceProcess namespace provides installation classes that write service information to the registry. The ServiceProcessInstaller class provides an encompassing class, which installs components common to all the services in an installation. For each service, create an instance of the ServiceInstaller class to install service-specific functionality.

The ServiceController class enables you to connect to an existing service and manipulate or get information about it. This class is typically used in an administrative capacity and enables you to start, stop, pause, continue, or perform custom commands on a service. The ServiceBase class defines the processing that a service performs when a command occurs; the ServiceController is the agent that enables you to call those commands on the service.

Note More information on creating, installing, and controlling Windows Service applications is available at https://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbcon/html/vbconintroductiontontserviceapplications.asp.

Database Connectivity

Database connectivity in UNIX applications is typically achieved using either ODBC (Open Database Connectivity) drivers or OCI (Oracle Call Interface, which is used for Oracle databases only).

ODBC provides access to an RDBMS (Relational Database Management System) using a standard data access interface. ODBC uses an RDBMS-specific driver to interact with the underlying data source.

OCI is a native library with a set of low-level APIs that allows fast access to the Oracle database.

.NET ships with ADO.NET, a data access technology that utilizes the features of CLR and the .NET Framework classes. The ADO.NET object model provides two basic components: the DataSet and DataProvider.

.NET data providers are available for all ODBC-compliant data sources (such as Oracle, Sybase, and DB2), Microsoft SQL Server™, and other OLE DB data sources.

To port the database access code in your application to .NET, you could rewrite the code to make use of ADO.NET. However, if your UNIX application uses direct OCI calls to connect to the Oracle database, consider making P/Invoke calls to OCI to reuse the existing code.

Note More information on access to the Oracle database in ADO.NET is available at https://www.fawcette.com/vsm/2003_01/magazine/features/beauchemin/.

Download

Get the UNIX Custom Application Migration Guide

Update Notifications

Sign up to learn about updates and new releases

Feedback

Send us your comments or suggestions