Channels ▼


Porting Across UNIX and Win32

Source Code Accompanies This Article. Download It Now.

Dec00: Porting Across UNIX and Win32

Knowing system differences is the trick

Sakib is a doctoral fellow at the Indian Institute of Management, Calcutta, and is currently working in the Software Concept Laboratory of Infosys Technologies Limited. He can be contacted at [email protected]

Even though UNIX and Win32 are the most common desktop operating systems around, applications written for one platform usually do not work on the other. Consequently, thought and effort are needed to port an application from one OS to another. In this article, I'll describe the issues I encountered when porting a load-balancing application, first from Sun OS to Linux, then from Linux to Win32.

Because of the use of low-level system routines and the high amount of communication, load-balancing software is a good example for examining porting issues. The load-balancing tool I ported is called "XYALB" (see my article "Load Balancing for UNIX and Win32," DDJ, July 2000).

Porting from Sun OS to Linux

Different flavors of UNIX differ at the kernel level. As a result, if an application uses system-level functions, porting becomes difficult. For load measurement, for instance, XYALB uses the Sun OS 4.1.1 kvm routines. Linux, on the other hand, provides neither the kvm routines nor any direct equivalents to them. To implement equivalent routines in Linux, you need to know about process structures in both the systems.

In Sun OS, the process table is maintained as a linked list; see Figure 1(c). Each process has an entry in the linked list that contains information such as process ID (PID) and pointers to u-area, which contain further information about the processes such as open descriptors and signal masks. Descriptors contain information about the type of resource that they refer to (file, pipe, socket) and other associated information, such as permissions.

For Linux, the process information is kept as an array, see Figures 1(a) and 1(b), where each entry corresponds to a process. Each process entry (of type task_ struct) contains pointers through which you can obtain further information about resources associated with the process. For example, there is a pointer to a file structure (of type file_struct) that represents files opened by the process. So porting kvm_routine to Linux is basically equivalent to finding out the PID, looking for the task_struct entry corresponding to this process by searching the array of process entries, then looking for the information in the task_struct itself (or following the pointers in task_struct) until you find the desired parameter of the process. How do you know where the desired parameter is? For that, you need to go through the headers linux/sched.h, linux/module.h, and linux/types.h, which help you in identifying the path from the array of task_struct to the desired parameter.

The process array is accessible through the device /dev/kmem. You need to define __KERNEL__ in your source code to use this device. The task_struct array resides at a specific offset address, available as a value of "task" in /boot/ For example, in my system, it's 0x00111d61f4.

To get kernel information about a process, kvm_open prepares for reading kernel memory. Subsequently, kvm_proc reads the process structure of the desired process. Once you have read the process structure, you can use kvm_read to read specific information about the process by passing suitable arguments. In Linux, you have to open the /dev/kmem file. Using the returned file descriptor kfd, you can read the content of the symbol table. Listing One (findtask) is an equivalent of kvm_getproc that finds the entry for a process. Like kvm_ getproc, Listing One reads task_ struct. Structure task_struct has a member PID, which is used to find out whether you have read the task_struct of the intended process.

Once you've read the process's task_ struct, you can get other properties, such as user and group IDs (readcred in Listing One) and groups (readgroups in Listing One) from the task_struct itself. For determining files, sockets, pipes opened by the process, and their properties, you need to follow the appropriate pointers (see readfile in Listing One). You can also read process statistics from the task_ struct entry.

The process of porting the application to Win32 platforms was relatively difficult due to a number of differences, such as the process creation mechanism, the means of interprocess communication and process synchronization, resource handling, and so on.

Porting from Linux to Win32

For the sake of brevity, I'll concentrate on some of the more important porting issues. For other issues, see Win32 system Programming, by Johnson M. Hart (Addison-Wesley, 1997); "Using the Win32 API to Port Code from UNIX to Windows NT" (http:// v0001536.html); and "Moving Unix Applications to Windows," MSDN. While many of the C library functions will work on Windows without change (though you may like to use equivalent Win32 API functions such as ReadFile in place of fread to improve performance), code related to the aforementioned differences has to be ported. Hart is a good source for a directly equivalent Windows routine for some of the C library functions (examples, strcaseamp== _stricmp, bzero==memset). For other cases, you will need to dirty your hands a bit.

Whenever you open a file in a UNIX pipe, socket, or shared memory, you get an integer called "descriptor" that can be used for further work with these objects. In Windows, these objects are manipulated through handles. Though some handles are just integers (SOCKET handle), some are complex structures. A file handle is one such example. Unlike UNIX, you cannot use standard input, standard output, and standard error devices through file descriptors 0, 1, and 2, respectively. You have to use GetStdHandle(STD_ INPUT_HANDLE), GetStdHandle(STD_ OUTPUT_HANDLE), GetStdHandle(STD_ ERROR_HANDLE), respectively (available electronically; see "Resource Center," page 5).

Process Creation

Through a fork in UNIX, you can create a dummy process, and manipulate its properties, such as redirection of standard devices, setting signal handling properties, and so on. This dummy process is then converted into a process with the aforementioned properties and desired functionality by executing a new program through exec or its variants. Windows does not have forks. If you use exec variants in Windows, a new process is created, but it terminates the calling process. To create a new process without terminating the calling process, you have to use CreateProcess or a higher level routine, such as _spawnv or its variants.

Getting an operation equivalent to the fork exec combination of UNIX with modification of properties through intermediate dummy processes is not straightforward in Windows. Listing Two (available electronically) illustrates how you can perform intermediate tasks of standard input, standard output, and error redirection. Here, the first argument of CreateProcess specifies the program to run, and the second provides the command-line arguments. CreateProcess takes other process parameters as arguments (see Hart for details). However, the last two arguments require mention. The second to last argument is a pointer to a STARTUPINFORMATION structure, which specifies the standard devices for the process and other start-up information about the process environment. You need to set the hStdin, hStdout, and hStderr members of the STARTUPINFORMATION structure before passing its address to CreateProcess to redirect standard input, standard output, and standard error. The last argument is a pointer to a PROCESSINFORMATION structure, which is filled up by the created process. Once the process starts, it will contain the handle to the created process. To get the operation equivalent to a signal call on the intermediate dummy process in UNIX, you can utilize the handle to carry out those steps (see handle_ SIGCHLD in Listing Two, available electronically).

Interprocess Communication

The support of signal handling is poor in Windows, and UNIX provides a large number of signals. In Windows, you can perform the task of signal handling by a mechanism like Structured Exception Handling (SEH). The intention of SEH is to make the program robust to unexpected exceptions such as floating-point exceptions, access to privileged instructions, and the like. If the code is susceptible to such events, you put those events inside the _try{} block followed by a number of _except (exception1){} blocks to capture the desired exceptions. Code inside the curly braces states what needs to be done (such as freeing resources) for the corresponding exception (exception1, in this example). You can affect the normal flow of the program by generating exceptions through RaiseException. However, this mechanism of communication is limited to one thread. You cannot send signals from one thread to another, or one process to another as in UNIX.

SEH cannot perceive the occurrence of events such as a user logging off or pressing Control C for the intended action. You can use SetConsoleCtrlEvent to capture such events from the program. Here, you pass a handler function as the first argument to the call SetConsoleCtrlEvent; an argument to the handler function specifies what event is to be captured, and the handler routine itself specifies how to react to an occurrence of this event (see Listing Four; available electronically). As you may have guessed, you can actually generate these events (CTRL_ LOGOFF_EVENT, CTRL_C_EVENT, CTRL_ BREAK_EVENT) through GenerateConsoleCtrlEvent. However, communication is limited to processes sharing the same console.

Interprocess communication in Windows is as straightforward as its UNIX counterparts for pipes and sockets. For pipes, you need to create a pipe through CreatePipe (pipe in UNIX). Processes can interact by reading from and writing to the pipe through Readfile and WriteFile functions. Win32 provides the header file winsock.h and library wsock32.lib, which make it possible for most of the socket commands of UNIX to work on Win32. However, you should use WSAStartUp and WSACleanup to start and stop the Windows socket DLL.

Memory-mapped files in Win32 are an alternative to UNIX shared memory. In UNIX, you create shared memory with an Open call with the first argument SHARED. Open returns a descriptor to shared memory. In Win32, you create a mapping object through CreateFileMapping. The first argument specifies a handle to the file used for mapping, and subsequent arguments specify the access type and size limits of the mapping object. As a result of the call, you get a handle to the mapping object. The next step is to allocate a memory space and associate it with a process address space. In UNIX, you do it through mmap, which takes as parameters, among other things, the process space that is to be mapped and the descriptor for the shared memory. In Win32, MapViewOfFile is used to associate memory space to the file (see Listing Three; available electronically).

Synchronization of processes is possible through semaphore, mutex, or event objects. You can create an event or number of events through a call to CreateEvent, and make a call to WaitforSingleObject or WaitForMultipleObjects for a single or a set of events to occur. The process will block these calls until the desired events occur. You can programmatically generate these events by a call to SetEvent, with the event as an argument. For semaphore and mutex objects, the modus operandi is the same, only the routines for creating and releasing them differ.

Porting Daemons

Services in Win32 are equivalent to daemons in UNIX. Creating a daemon in UNIX is simple. You just need to run the program in the background without attaching it to the terminal. You can use nohup for detaching a program from the terminal on which it is being run. However, in Windows, the process of service creation is fairly complicated.

Windows NT Services

NT services must implement the servicestart function, which carries out the actual operation that the service is to perform. Hence, if you have an existing UNIX daemon, you can modify its main (see Example 1) to work on both UNIX and Windows, respectively.

You need to define a few other routines for full-fledged service. You should always have a main routine -- a starting point of execution. main parses command-line options and calls appropriate routines for installing, removing, or running the service. Use the ReportStatusToManager function to report the status of the service to the Service Control Manager (SCM).

When the argument is "install," main calls the routine CmdInstallService to register the service with the SCM for the first time. CmdInstallService calls OpenSCManager to get a handle to the SCM database. Using the handle, it then creates the service through a call to CreateService, which takes SZSERVICENAME (the name of the service), SZSERVICEDISPLAYNAME (the name as displayed on the console), and other properties as arguments.

To run the service in debugging mode as a console application, you need to pass debug as the first argument to servicename. Additional arguments serve as arguments to the application program. The main routine, in this case, calls routine CmdDebugService with the second and latter arguments. CmdDebugService sets the routine (ControlHandler) to handle console events through SetConsoleHandler. Subsequently, service is started through servicestart.

The routine servicestop stops the service by setting the event hServerStopEvent. Servicestop can be called by the SCM through the SERVICE_CONTROL_STOP event or by pressing Control C if the service is running in debug mode.

When a service is started through the SCM, service_main is called. It sets up the handler routine (service_control) for SCM control events followed by a call to servicestart to start the service.

To remove the service from the SCM database, you need to use the "remove" option. The main routine in that case calls CmdRemoveService, which gets a handle to the SCM database through OpenSCManager, and opens the service through OpenService. Then controlservice is called to stop the service, and deleteservice is called to remove the service from SCM database. Finally, it clears the handles for the service.

Windows 95 Services

Unlike Windows NT, Windows 95 does not provide a service API. However, you can run the application program as a service in a roundabout way by first creating a registry key: \HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices (see RegCreateKey in Listing Four; available electronically).

To add a service, you just write the program name as the registry key value (see RegSetValueEx in Listing Four). To start the service, you need to run the program as a detached process, which can be done by using DETACHED_PROCESS as the argument to CreateProcess. However, such processes do not have any attached consoles, so they stop when the user logs off. To make sure that it is not terminated, you have to register the service with a Windows kernel. To accomplish this, get access to the kernel process address through a call to GetProcAddress. Then use RegSrvProc with the PID as the argument to get the corresponding process registered.

If you have the PID, stopping the service is easy. You call OpenProcess with the PID as an argument to get the handle to the process, and then call terminateProcess with the handle as an argument. If you do not have the PID, you can use the module name. But in this case, you will have to call CreateToolhelp32Snapshot to get information about all running processes, and check the entries one by one. When the name matches, you can extract the PID and carry out the steps, as in the previous case, to terminate the process. Listing Four also illustrates how you can create a common service for both Windows 95 and NT.


Porting across UNIX platforms is considerably easier than porting from UNIX to Win32. This is expected, as UNIX systems share a common design philosophy and provide lot of similarities at the application layer. Still, the Win32 API helps in a limited way when porting from UNIX to Win32. What's important is for you to be aware of the issues that result from the differences in the designs of UNIX and Win32.


Listing One

/***************CODE FOR SUN OS**********************************/
/* The kvm routines are used to read certain information from the kernel */
kvm_t *mptr;                /* Pointer to kmem */
struct proc *process;       /* Pointer to proc structure */
struct user *user;          /* Pointer to user structure */
struct file ofile;          /* File structure of an object */
struct file *fileptr;       /* Pointer to above structure */
struct ucred credits;       /* Process credentials */
/*Open /dev/kmem */
if (!(mptr = kvm_open((char *)0, (char *)0, (char *)0,O_RDONLY,
(char *)0 ))) exit (-1);

/*read process-structure of the process with pid what->proc_nr*/
process = kvm_getproc(mptr, what->proc_nr);

/* read the credentials of the process  */
if(kvm_read(mptr, process->p_cred, (char *)&credits, 
sizeof (credits)) != sizeof (credits)){
/*you can verify for effective and real uids and gids, and user 
groups of the process using members cr_uid, cr_gid, cr_ruid, 
cr_rgid, cr_groups[] of credits */
/*Given a file/socket descriptor (what->sock_nr), if you want to 
determine properties of the corresponding object, you need to read
the ofile structure of the object, but for this you have to first 
read user structure from the kernel */
if((user = kvm_getu(mptr, process)) == NULL){
/*Now, read the ofile structure */
if (what->sock_nr < NOFILE_IN_U) {/* if the  file structure is in 
the user-area */
if(kvm_read(mptr, user->u_ofile_arr[what->sock_nr], 
(char *) &ofile, sizeof (struct file)) != 
sizeof (struct file)){
else/* if the  file structure is not in the user-area */{
if (kvm_read(mptr, (user->u_ofile + ((what->sock_nr - 
NOFILE_IN_U) * sizeof (struct file *))),(char *) 
&fileptr, sizeof (struct file *)) != 
sizeof (struct file *)){
if (kvm_read(mptr, fileptr, (char *) &ofile, 
sizeof (struct file)) != sizeof (struct file)){
/* Now we can use the structure to extract further details of the object 
(example type of the object, permissions, group owner etc. if the object is 
file, port, domain, protocol etc. if the object is socket etc.)*/
/******************CODE FOR LINUX***********************************/
* Unlike SUN OS where, task structures are maintained as linked list*       
* Linux  keeps an array of entires for task structures.  Additional *
* information (such as files opened etc.) are available through a   *
* pointer in the entry.                                             *
#define __KERNEL__ 
#define TASK 0x001d61f4
#ifdef OLDLNUX
#define FD0 ((char *)&ts.files->fd[0] - (char *)&ts)
    #define AD(fd) (task_addr +FD0+4*(fd))
    #define FILES ((char *)&ts.files - (char *)&ts)
    #define FD0 ((char *)&fs.fd[0] - (char *)&fs)
    #define AD(fd) (readvalz(task_addr +FILES)+FD0+4*(fd))
int kfd;                    /* File Pointer to /dev/kmem */
int task_addr;      
struct task_struct ts;      /*task structure*/  
struct files_struct fs;     /*file system structure*/
struct file ofile;          /*File structure of an object */
struct file *fileptr;       /*Pointer to above structure */
struct uid_credit{          /*process credential*/
    unsigned short uid,euid,suid,fsuid;
    unsigned short gid,egid,sgid,fsgid;
int procgrps[NGROUPS];  /*process groups*/
*   read an entry from the array in symbol table                   *
int readval(int ad){
    int val,r;
    if (lseek(kfd,ad,SEEK_SET)<0){
        perror("lseek"); exit(1);
        if (r<0) perror("read");
        else fprintf(stderr,"Error reading ..\n");
    return val;
*   read value at specified location, error if value is 0          *
int readvalz(int ad){
    int r=readval(ad);
    if(r==0) fprintf(stderr,"Error reading ..\n");
    return r;
* to read the additional information, we require the id of the      * 
* process corresponding to this entry                               *
void readtask(int ad){
 int r;
 if (lseek(kfd,ad,SEEK_SET)<0)
        perror("lseek"), exit(1);
 if((r=read(kfd,&ts,sizeof(struct task_struct)))!=sizeof(struct task_struct)){
        if (r<0) perror("read");
        else fprintf(stderr,"Error reading ..\n");
*   find the task structure of specified process                   *
void findtask(int pid){
    int kadr;
/*TASK is starting address for symbol table: differs from installation to 
 * installation. Its value can be found from /boot/ */
        if (kadr>=TASK+4*NR_TASKS){ 
/*NR_TASK indicates number of tasks*/ 
            fprintf(stderr,"Proc not found ..\n"); 
        if ( break;
*   read process credentials                                       *
void readcred(struct uid_credit *credits){
    int r;
    int ad= task_addr +(char *)&ts.uid-(char *)&ts;
    if (lseek(kfd,ad,SEEK_SET)<0)
        perror("lseek"), exit(1);
    if((r=read(kfd,credits,sizeof(struct uid_credit)))!=
                                             sizeof(struct uid_credit)){
        if (r<0) perror("read");
        else fprintf(stderr,"Error reading ..\n");
*   read process user groups                                       *
void readgroups(int *procgrps){
    int r;
    int ad= task_addr +(char *)&ts.groups[0]-(char *)&ts;
    if (lseek(kfd,ad,SEEK_SET)<0)
        perror("lseek"), exit(1);
        if (r<0) perror("read");
        else fprintf(stderr,"Error reading ..\n");
*   read file structure for a given kernel object                  *
void readfile(int sock_nr, struct file *fileptr){
    int r;
    int ad=AD(sock_nr);
    if (lseek(kfd,ad,SEEK_SET)<0)
        perror("lseek"), exit(1);
if((r=read(kfd,(char *)fileptr,sizeof(struct files_struct)
))!=sizeof(struct files_struct)){
        if (r<0) perror("read");
        else fprintf(stderr,"Error reading ..\n");
if (kfd<0) { perror("open"); exit(1);}
/* read the task structure first */
/* read the credentials of the process */
/* read the user groups of the process*/
/*you can verify for effective and real uids and gids, and user 
groups of the process using members uid, gid, euid, egid of 
credits and procgrps */
/* read the ofile structure for the give descriptor (what->
sock_nr) */
/* Now we can use the structure to extract further details of the object 
(example type of the object, permissions, group owner etc. if the object is 
file, port, domain, protocol etc. if the object is socket etc.)*/

Back to Article

Listing Two

int ForkAndExec(int sock, char **argvv, int *sockpid) { 
char chReadBuffer[64]; 	BOOL bSuccess; int j;   
HANDLE hOutFile; 	HANDLE hReadPipe, hWritePipe, hWritePipe2; 
char szArgs[256]; 		char *p; DWORD cchReadBuffer; 

/* create the log file where we will save all output from child */ 
if ((hOutFile = CreateFile("\\\\.\\C:\\yalb_win\\tmp", 
printf("Error %d\n",GetLastError()); 
/* set up the security attributes for the anonymous pipe */ 
saPipe.nLength = sizeof(SECURITY_ATTRIBUTES);
saPipe.lpSecurityDescriptor = NULL; 
saPipe.bInheritHandle = TRUE; 
/* create the anonymous pipe */ 
if (!(bSuccess = CreatePipe(&hReadPipe, &hWritePipe, &saPipe, 0))) 
printf("Error %d\n",GetLastError());

/* Now we need to change the inheritable property for the readable 
end of the pipe. */ 
if (!(bSuccess = DuplicateHandle(GetCurrentProcess(),hReadPipe, 
printf("Error %d\n",GetLastError()); 

if (!(bSuccess=DuplicateHandle(GetCurrentProcess(),hWritePipe, 
GetCurrentProcess(), &hWritePipe2, 0, TRUE, 
DUPLICATE_SAME_ACCESS))) printf("Error %d\n",GetLastError()); 

/* Set up the STARTUPINFO structure */ 
memset(&si, 0, sizeof(si)); 		si.cb = sizeof(si); 
/* Set up the command-line buffer */ 
memset(szArgs, 0, sizeof(szArgs)); 	strcpy(szArgs, *argvv); 

p = strchr(szArgs, 0); 
for (j = 0; argvv[j]!=NULL; j++){ 
    strcat(p, argvv[j]); strcat(p, " "); 
/*file handles specified in the STARTUPINFO structure will
be inheritied by the child. */ 
si.hStdInput = hWritePipe2; si.hStdOutput = hWritePipe; 
si.hStdError = hWritePipe2; si.dwFlags = STARTF_USESTDHANDLES;
bSuccess = SetStdHandle(STD_INPUT_HANDLE, hWritePipe2); 
  	bSuccess = SetStdHandle(STD_OUTPUT_HANDLE, hWritePipe); 
  	bSuccess = SetStdHandle(STD_ERROR_HANDLE, hWritePipe2); 
bSuccess = DuplicateHandle(GetCurrentProcess(),
GetStdHandle(STD_INPUT_HANDLE), GetCurrentProcess(),NULL, 
  /* create the child process */ 
if ((bSuccess = CreateProcess(NULL, szArgs, NULL, NULL, TRUE, 0, 
NULL, NULL, &si, &pi)) ==NULL) 
printf("Error %d\n",GetLastError()); 
*sockpid=pi.dwProcessId;  		CloseHandle(pi.hThread); 
/*We can close our instances of the inheritable pipe write handle*/ 
bSuccess = CloseHandle(hWritePipe); 
bSuccess = CloseHandle(hWritePipe2); 

/* read from the pipe until we get an ERROR_BROKEN_PIPE */ 
for (;;){ 
bSuccess = ReadFile(hReadPipe, chReadBuffer, 
sizeof(chReadBuffer), &cchReadBuffer, NULL); 
    	if (bSuccess && cchReadBuffer) { 
send(sock, chReadBuffer,cchReadBuffer, 0);
		/* write buffer (of specified length) to console */ 
      		printf("%.*s", cchReadBuffer, chReadBuffer); 
if (cchReadBuffer<64) break; 
/* close the trace file, pipe handles */ 

Back to Article

Listing Three

#ifdef WIN32
	HANDLE hIn,hInMap;	LPSTR addr_time;	
static HANDLE shared_mem; 
	caddr_t addr_time; 	static int shared_mem; 
#ifdef WIN32
hIn=CreateFile ("\\tmp\\yalb\\yalb.shared", GENERIC_READ|
	printf ("Error in creating File yalb.shared  %d\n", 
timeval) + sizeof(float) + 2*sizeof(int) + sizeof(float),NULL);
	printf ("Error in creating file mapping for yalb.shared  
%d\n", GetLastError());
	printf ("Error in  mapping file for yalb.shared  %d\n", 
	*last_update = (struct tm *) addr_time;

#ifdef UNIX
	shared_mem = open(SHARED,O_RDWR | O_CREAT, 0600);
	shared_mem = open(SHARED,O_RDWR | O_CREAT, 0666);
if (shared_mem < 0){
	syslog(LOG_ERR, "Load-Daemon: Shared memory not available: %m");
ftruncate (shared_mem, 4096);

addr_time = mmap ((caddr_t)0, (size_t) (sizeof(struct timeval) + 
sizeof(float) + 2*sizeof(int) + sizeof (float) + sizeof(int)),
PROT_WRITE | PROT_READ, MAP_SHARED, shared_mem, (off_t)0 );   
if (addr_time == (caddr_t)-1)
syslog(LOG_ERR, "Load-Daemon: getting shared segment (%m)");
last_update = (struct timeval *) addr_time;

Back to Article

Listing Four

#ifdef WIN32
VOID ServiceStart (DWORD argc, LPTSTR *argv) 
#ifdef UNIX
int main(int argc,char *argv[])
{ /*actual work of service defined here*/
#define SZAPPNAME            "ldaemon" 
#define SZSERVICENAME        "ldaemon" 
#define SZDEPENDENCIES       "" 

#ifdef WIN95 
#include <tlhelp32.h>
#define SERVICE_KEY "Software\\Microsoft\\Windows\\
DWORD dwErr = 0; 	BOOL bDebug = FALSE; 	TCHAR  szErr[256]; 
/* function prototypes */
/*ServiceStart must be defined by in code. The service should use
ReportStatusToSCMgr to indicate progress.  This routine must also be 
used by StartService()to report to SCM when the service is running*/ 
VOID ServiceStart(DWORD dwArgc, LPTSTR *lpszArgv); 
VOID ServiceStop(); 
BOOL ReportStatusToSCMgr(DWORD dwCurrentState, DWORD dwWin32ExitCode,
DWORD dwWaitHint); 
void AddToMessageLog(LPTSTR lpszMsg); 
VOID WINAPI service_ctrl(DWORD dwCtrlCode); 
VOID WINAPI service_main(DWORD dwArgc, LPTSTR *lpszArgv); 
VOID CmdInstallService(); 
VOID CmdRemoveService(); 
VOID CmdDebugService(int argc, char **argv); 
BOOL WINAPI ControlHandler ( DWORD dwCtrlType ); 
LPTSTR GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize ); 
const char *CmdLine=".\\ldaemon -debug"; 
/* set according to application to be run*/

/* main() either performs the command line task, or call 
StartServiceCtrlDispatcher to register the main service thread.When 
the this call returns, the service has stopped, so exit. */ 

void _CRTAPI1 main(int argc, char **argv) { 
SERVICE_TABLE_ENTRY dispatchTable[] = 
        { NULL, NULL } 
if ( (argc > 1) && ((*argv[1] == '-')|| (*argv[1] == '/')) ){ 
        	if ( _stricmp( "install", argv[1]+1 ) == 0 ) { 
        	else if ( _stricmp( "remove", argv[1]+1 ) == 0 ) { 
        	else if ( _stricmp( "debug", argv[1]+1 ) == 0 ) { 
            		bDebug = TRUE; 
            		CmdDebugService(argc, argv); 
        	else { 
            		goto dispatch;
    	/* else call StartServiceCtrlDispatcher */
        	printf( "%s -install to install the service\n", SZAPPNAME ); 
        	printf( "%s - remove to remove the service\n", SZAPPNAME ); 
printf( "%s -debug <params>to run as a console app for 
debugging\n", SZAPPNAME ); 
        	printf( "\nStartServiceCtrlDispatcher being called.\n" ); 
        	printf( "This may take several seconds.  Please wait.\n" ); 
        	if (!StartServiceCtrlDispatcher(dispatchTable)) 
            	AddToMessageLog(TEXT("StartServiceCtrlDispatcher failed.")); 
/* actual initialization of the service */ 
void WINAPI service_main(DWORD dwArgc, LPTSTR *lpszArgv) { 
 	sshStatusHandle = RegisterServiceCtrlHandler( 
TEXT(SZSERVICENAME), service_ctrl); 
 	if (!sshStatusHandle) goto cleanup; 
    	ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; 
    	ssStatus.dwServiceSpecificExitCode = 0; 
     	// report the status to the service control manager. 
    	if (!ReportStatusToSCMgr(SERVICE_START_PENDING,NO_ERROR,3000)) 
goto cleanup; 
     	ServiceStart( dwArgc, lpszArgv ); 

if (sshStatusHandle) 
(VOID)ReportStatusToSCMgr(SERVICE_STOPPED, dwErr, 0); 
/* service_ctrl is called by the SCM whenever ControlService() is 
called on this service. */ 
VOID WINAPI service_ctrl(DWORD dwCtrlCode) { 
switch(dwCtrlCode) { 
/* SERVICE_STOP_PENDING should be reported before setting 
the Stop Event to avoid a race condition. */
           	ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 0); 
            	ServiceStop();       return; 
ReportStatusToSCMgr(ssStatus.dwCurrentState, NO_ERROR, 0); 
/* ReportStatusToSCMgr(): Sets the current status of the service 
and reports it to the Service Control Manager */ 
BOOL ReportStatusToSCMgr(DWORD dwCurrentState, DWORD dwWin32ExitCode,
DWORD dwWaitHint) { 
    	static DWORD dwCheckPoint = 1; BOOL fResult = TRUE; 
     	if ( !bDebug ) { 
        	if (dwCurrentState == SERVICE_START_PENDING) 
ssStatus.dwControlsAccepted = 0; 
            	ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; 
        	ssStatus.dwCurrentState = dwCurrentState; 
        	ssStatus.dwWin32ExitCode = dwWin32ExitCode; 
        	ssStatus.dwWaitHint = dwWaitHint; 
		if ((dwCurrentState==SERVICE_RUNNING)||
( dwCurrentState ==SERVICE_STOPPED)) 
            		ssStatus.dwCheckPoint = 0; 
            		ssStatus.dwCheckPoint = dwCheckPoint++; 
 		if (!(fResult = SetServiceStatus( sshStatusHandle, 
&ssStatus))) { 
    	return fResult; 
/* AddToMessageLog: Allows any thread to log an error message */ 
VOID AddToMessageLog(LPTSTR lpszMsg) { 
TCHAR   szMsg[256];     HANDLE  hEventSource; LPTSTR  lpszStrings[2]; 
if ( !bDebug ) { 
dwErr = GetLastError(); 
 	hEventSource = RegisterEventSource(NULL, TEXT(SZSERVICENAME)); 
 	_stprintf(szMsg, TEXT("%s error: %d"), TEXT(SZSERVICENAME), dwErr); 
      	lpszStrings[0] = szMsg;         lpszStrings[1] = lpszMsg; 
if (hEventSource != NULL) { 
ReportEvent(hEventSource, EVENTLOG_ERROR_TYPE, 0, 0,
NULL, 2, 0, lpszStrings, NULL); 
             	(VOID) DeregisterEventSource(hEventSource); 
void CmdDebugService(int argc, char ** argv) 
    DWORD dwArgc; 
    LPTSTR *lpszArgv; 
#ifdef UNICODE 
    lpszArgv = CommandLineToArgvW(GetCommandLineW(), &(dwArgc) ); 
    dwArgc   = (DWORD) argc; 
    lpszArgv = argv; 
    _tprintf(TEXT("Debugging %s.\n"), TEXT(SZSERVICEDISPLAYNAME)); 
    SetConsoleCtrlHandler( ControlHandler, TRUE ); 
    ServiceStart( dwArgc, lpszArgv ); 
/* Handles console control events */ 
BOOL WINAPI ControlHandler ( DWORD dwCtrlType ) 
    switch( dwCtrlType ) { 
        case CTRL_BREAK_EVENT: 
        case CTRL_C_EVENT: 
            _tprintf(TEXT("Stopping %s.\n"),TEXT(SZSERVICEDISPLAYNAME)); 
            return TRUE; 
    return FALSE; 

HANDLE  hServerStopEvent = NULL; 

VOID ServiceStop(){ 
    if ( hServerStopEvent ) SetEvent(hServerStopEvent); 

#ifdef WIN95 
#include <tlhelp32.h>
#define SERVICE_KEY "Software\\Microsoft\\Windows\\
#ifdef WIN95
BOOL AddService(const char* Name, const char *CmdLine) { 
HKEY    hKey;     LONG    Status;     BOOL    Result  = FALSE; 
Status = RegCreateKey(HKEY_LOCAL_MACHINE, SERVICE_KEY, &hKey); 
if(ERROR_SUCCESS == Status) { 
Status  = RegSetValueEx(hKey, Name, 0, REG_SZ, 
(CONST BYTE*)CmdLine, strlen(CmdLine)+1); 
        	if(ERROR_SUCCESS == Status) Result  = TRUE; 
 	return Result; 
#define NUM_ATTEMPTS_EXISTS     20 
#define SLEEP_TIME              500 
DWORD StartWin95Service(char* file_name) { 
DWORD   dwPriority;     		DWORD dwResult=(DWORD)-1; 
int                 ii; 
/* Initialize STARTUPINFO structure. */ 
siStartInfo.cb          = sizeof(STARTUPINFO);     
siStartInfo.lpReserved  = NULL; 
siStartInfo.lpReserved2 = NULL;   	siStartInfo.cbReserved2 = 0; 
siStartInfo.lpDesktop   = NULL;  
siStartInfo.dwFlags     = STARTF_USESHOWWINDOW; 
/* Create the process   */ 
if(FALSE == CreateProcess(file_name, NULL, NULL, NULL,FALSE,
DETACHED_PROCESS | dwPriority, NULL, NULL, &siStartInfo,&piProcInfo)) 
return -1; 
for(ii = 0; ii < NUM_ATTEMPTS_EXISTS; ii++){ 
if(dwResult == STILL_ACTIVE) break; 
else Sleep(SLEEP_TIME); 
        if(dwResult == STILL_ACTIVE) { 
            	SetPriorityClass(piProcInfo.hProcess, dwPriority); 
            	dwResult = piProcInfo.dwProcessId; 
        else  dwResult    = (DWORD)-1; 
return dwResult; 
/* mark a process as a Win95 "service" */ 
BOOL RegisterService (DWORD AppProcessId){ 
BOOL    Result  = FALSE;     int     nstat;     HMODULE hKernelLib;  
typedef DWORD (WINAPI*ExRegSrvProc) (DWORD dwProcessId, DWORD dwType); 
ExRegSrvProc RegSrvProc; 
hKernelLib = GetModuleHandle("KERNEL32.DLL"); 
if(NULL != hKernelLib) { 
RegSrvProc= (ExRegSrvProc)GetProcAddress(hKernelLib, 
    	if(NULL != RegSrvProc) { 
nstat = RegSrvProc(AppProcessId, 0x00000001); 
            	if(1 == nstat) Result  = TRUE; 
return Result;
void CmdRemoveService(){ 
DWORD  dwProcID;	HANDLE hProcess, hProcessSnap, hModuleSnap; 
BOOL bModuleFound = FALSE; 
/* Take snapshot of all processes */ 
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); 
pe32.dwSize = sizeof(PROCESSENTRY32); 
if (Process32First(hProcessSnap, &pe32)) { 
do  { 
hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,
                me32.dwSize = sizeof(MODULEENTRY32); 
                Module32First(hModuleSnap, &me32); 
                do { 
                    	if(strcmp(me32.szExePath, SZSERVICENAME)==0){ 
                       	bModuleFound = TRUE; 
}while (Module32Next(hModuleSnap, &me32)); 
               CloseHandle (hModuleSnap); 
if(bModuleFound) break; 
} while (Process32Next(hProcessSnap, &pe32)); 
CloseHandle (hModuleSnap); 
if(bModuleFound) { 
      	TerminateProcess(hProcess, -1); 
     	printf("Process terminated"); 
else	printf("Process not found"); 
/*CmdInstallService(): Installs the service */ 
void CmdInstallService() { 
SC_HANDLE   schService;	SC_HANDLE   schSCManager; 
TCHAR szPath[512]; 
if ( GetModuleFileName( NULL, szPath, 512 ) == 0 ) { 
_tprintf(TEXT("Unable to install %s - %s\n"), 
TEXT(SZSERVICEDISPLAYNAME), GetLastErrorText(szErr, 256)); 
#ifdef WIN95
if (AddService(SZSERVICENAME,CmdLine)==TRUE) { 
         _tprintf(TEXT("%s installed.\n"),TEXT(SZSERVICEDISPLAYNAME)); 
else _tprintf(TEXT("CreateService failed \n")); 
if ( schSCManager ) { 
        if ( schService ) { 
            	_tprintf(TEXT("%s installed.\n"),
_tprintf(TEXT("CreateService failed-%s\n"), 
GetLastErrorText(szErr, 256)); 
_tprintf(TEXT("OpenSCManager failed - %s\n"),GetLastErrorText(szErr,256)); 

#ifdef WINNT
/* CmdRemoveService(): Stops and removes the service */ 
void CmdRemoveService(){ 
SC_HANDLE   schService; 	SC_HANDLE   schSCManager; 
if ( schSCManager ) { 
schService=OpenService(schSCManager, TEXT(SZSERVICENAME),
 	if (schService) { 
if ( ControlService( schService, SERVICE_CONTROL_STOP,
&ssStatus)) { 
                	_tprintf(TEXT("Stopping %s."), 
                	Sleep( 1000 ); 
while( QueryServiceStatus( schService,&ssStatus)){ 
                    		if (ssStatus.dwCurrentState == 
                        		Sleep( 1000 ); 
                   		else  break; 
                	if (ssStatus.dwCurrentState == SERVICE_STOPPED) 
_tprintf(TEXT("\n%s stopped.\n"), 
_tprintf(TEXT("\n%s failed to stop.\n"), TEXT(SZSERVICEDISPLAYNAME) ); 
            	/* remove the service */
if( DeleteService(schService) ) 
                	_tprintf(TEXT("%s removed.\n"),
_tprintf(TEXT("DeleteService failed - %s\n"),
_tprintf(TEXT("OpenService failed - %s\n"),
_tprintf(TEXT("OpenSCManager failed - %s\n"), 

Back to Article

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.