///////////////////////////////////////////////////////////////////////// // // Filename: client.c // // Description: This file contains the source code for IPC performance. // This module is written using win32 API calls, and will // generate a console server app. // // Authors: Scott Holden (Translator from NT API to win32 API) // Mahesh Keni (Mahesh wrote this application using mostly // NT native API calls) // ///////////////////////////////////////////////////////////////////////// #include "rawcom.h" #include "client.h" /************************************************************************/ // Global variables /************************************************************************/ struct client Clients[MAXCLIENTS]; HANDLE Threads[MAXCLIENTS]; HANDLE EventHandle; PCHAR ServerName=NULL; USHORT ThreadError=0; BOOLEAN Failure = FALSE; USHORT NClients = 1; // number of clients USHORT MachineNumber = 1; // This client Number USHORT IPCType = NP; // Ipc Type to be used ULONG SendSize = 32; ULONG NumSends = 1; ULONG RecvSize = 32; ULONG NumRecvs = 1; ULONG Iterations = 100; // number of Iterations DWORD Timeout; CHAR Xport[9]; // Xport type name CHAR TestCmd = 'P'; // function pointers for redirecting calls according to Xport types NTSTATUS (* IPC_Initialize)(); NTSTATUS (* IPC_PerClientInit)(); NTSTATUS (* IPC_Connect_To_Server)(); NTSTATUS (* IPC_Disconnect_From_Server)(); NTSTATUS (* IPC_Cleanup)(); NTSTATUS (* IPC_Allocate_Memory)(); NTSTATUS (* IPC_DoHandshake)(); NTSTATUS (* IPC_ReadFromIPC)(); NTSTATUS (* IPC_WriteToIPC)(); NTSTATUS (* IPC_XactIO)(); NTSTATUS (* IPC_Deallocate_Memory)(); NTSTATUS (* IPC_ThreadCleanUp)(); // Later all of this should move under an union // Globals for NamedPipe /* OBJECT_ATTRIBUTES objectAttributes; UNICODE_STRING unicodePipeName; */ LPCSTR pipeName; ULONG Quotas = 32768; // read/write quota ULONG PipeType = PIPE_TYPE_MESSAGE; // pipe type ULONG PipeMode = PIPE_READMODE_MESSAGE; // read mode ULONG BlockorNot = PIPE_NOWAIT; // non blocking // Globals for NetBIOS USHORT LanaCount = 1; USHORT LanaBase = 0; UCHAR NameNumber = 1; CHAR LocalName[NCBNAMSZ]; CHAR RemoteName[NCBNAMSZ]; // GLobals for Sockets PCHAR HostName; int AddrFly; // For XNS socket support CHAR Remote_Net_Number[4]; // NetNumber CHAR Remote_Node_Number[6]; // Card address /************************************************************************/ void _CRTAPI1 main ( IN USHORT argc, IN PSZ argv[], IN PSZ envp[] ) { NTSTATUS mstatus; // DWORD Timeout; USHORT Cindex = 0; // client index OutputDebugString("Raw network client has started!\n"); // initialize the Client & Server name mstatus = Parse_Cmd_Line(argc, argv); if (!NT_SUCCESS(mstatus)) { exit(1); } // Based on the xport type setup all the function pointers Setup_Function_Pointers(); // do IPC dependent Initializing mstatus = IPC_Initialize(NClients, ServerName, CLI); if (!NT_SUCCESS(mstatus)) { exit(1); } // Now create worker threads to execute test scenarios // Create an event to synchronize all the client threads EventHandle = CreateEvent(NULL, // EventobjectAttributes, TRUE, // manual reset event FALSE, // initial state of the event is non-signalled NULL); // no name given to the event if (!EventHandle) { printf ("Failed to create an event err=%lx\n",mstatus); Cleanup(); exit(1); } printf("Creating %s Client threads..", Xport); for (Cindex = 0; Cindex < NClients; Cindex++) { // do appropriate per client initialization mstatus = IPC_PerClientInit(Cindex, CLI); // set up parameters for other client threads Clients[Cindex].c_client_num = Cindex; // client number Clients[Cindex].c_reqbuf.Iterations = Iterations; Clients[Cindex].c_reqbuf.SendSize = SendSize; Clients[Cindex].c_reqbuf.RecvSize = RecvSize; Clients[Cindex].c_reqbuf.NumSends = NumSends; Clients[Cindex].c_reqbuf.NumRecvs = NumRecvs; Clients[Cindex].c_reqbuf.TestCmd = TestCmd; Clients[Cindex].c_reqbuf.ClientNumber = Cindex; // use win32 API instead of NT native so that NetBIOS works fine Clients[Cindex].c_hThHandle = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)&CliService, (PUSHORT)&(Clients[Cindex].c_client_num), 0, (LPDWORD)&(Clients[Cindex].c_ThClientID)); Threads[Cindex] = Clients[Cindex].c_hThHandle; printf("%d..",Cindex+1); } // printf("\n"); // First delay the main thread to let worker threads to get ready mstatus = Delay_Trigger_Wait(); if (!NT_SUCCESS(mstatus)) { printf ("Failed in Delay_Trigger_Wait: err=%lx\n",mstatus); Cleanup(); } // If any error then interprete it otherwise display results if (ThreadError) { printf("Client Error=%d\n",ThreadError); } else { // display results for all the clients Display_Results(); } // now do the cleanup. i.e. clear all memory and close all handles IPC_Cleanup(); Cleanup(); exit(0); } // main /************************************************************************/ NTSTATUS Delay_Trigger_Wait(VOID) { NTSTATUS dstatus; DWORD Timeout = 1000; dstatus = SleepEx( Timeout, // 10 ms delay TRUE ); // alertable if (!NT_SUCCESS(dstatus)) { printf ("Failed on Delayed execution err=%lx\n",dstatus); return(dstatus); } dstatus = PulseEvent(EventHandle); if (!NT_SUCCESS(dstatus)) { printf ("Failed to Pulse an event err=%lx\n",dstatus); return(dstatus); } // then signals all the waiting threads to resume I/O to namedpipe // by sending a pulse event dstatus = Wait_For_Client_Threads(); if (!NT_SUCCESS(dstatus)) { printf ("Failed on wait err=%lx\n",dstatus); return(dstatus); } // printf("wait done. now do the cleanup work\n"); return(dstatus); } /************************************************************************/ VOID CliService( // provide Client service IN PUSHORT pTindex ) { NTSTATUS tstatus; USHORT tCindex; //IO_STATUS_BLOCK ioStatusBlock; USHORT error = 0; //UCHAR Retcode; ULONG Iterations; ULONG SendLen; ULONG RecvLen; DWORD StartTime; DWORD StopTime; DWORD Duration; BOOLEAN First = FALSE; tCindex = *pTindex; ThreadError = 0; // No error so far Failure = FALSE; MyDbgPrint("CLI: Connecting to Server\n"); tstatus = IPC_Connect_To_Server(tCindex); // First connect to the server ThreadError = 1; FAIL_CHECK_EXIT(PERFCLI, "Connect to Srv", tstatus); // now open the sync event and wait on it till set by the main thread tstatus = WaitForSingleObjectEx(EventHandle, INFINITE, TRUE); ThreadError = 2; FAIL_CHECK_EXIT(PERFCLI, "Wait for Event", tstatus); MyDbgPrint("CLI: Doing Handshake \n"); // Do the handshake before the actual test run. This will send a request // packet to the server with test details tstatus = IPC_DoHandshake(tCindex, CLI); ThreadError = 3; FAIL_CHECK_EXIT(PERFCLI, "Do Handshake ", tstatus); // Allocate memory required for recv and send buffers tstatus = IPC_Allocate_Memory(tCindex); ThreadError = 4; FAIL_CHECK_EXIT(PERFCLI, "Allocate Memory ", tstatus); // Now we are ready to run all the tests Iterations = Clients[tCindex].c_reqbuf.Iterations; StartTime = GetCurrentTime(); while (Iterations--) { if (Clients[tCindex].c_reqbuf.TestCmd == 'P') { // Client first sends X messages and Receives Y messages. tstatus = IPC_WriteToIPC(tCindex, &SendLen, CLI); ThreadError = 5; FAIL_CHECK(PERFCLI, "Write to IPC", tstatus); // Check for send length here tstatus = IPC_ReadFromIPC(tCindex, &RecvLen, CLI); ThreadError = 6; FAIL_CHECK(PERFCLI, "Receive from IPC", tstatus); // Check for Receive length and data integrity here } else { // for 'U' or 'T' tests do transaction type I/O // Note that First is not used by the client tstatus = IPC_XactIO(tCindex, &SendLen, &RecvLen, CLI,First); ThreadError = 65; FAIL_CHECK(PERFCLI, "Xact from IPC", tstatus); } } StopTime = GetCurrentTime(); Duration = StopTime - StartTime; Clients[tCindex].c_Duration = Duration; // in msecs // Deallocate all the memory tstatus = IPC_Deallocate_Memory(tCindex); if (!ThreadError) {ThreadError = 7;} FAIL_CHECK_EXIT(PERFCLI, "Deallocate Memory ", tstatus); // Disconnect from server tstatus = IPC_Disconnect_From_Server(tCindex); if (!ThreadError) { ThreadError = 8; } FAIL_CHECK_EXIT(PERFCLI, "Disconnect from Server ", tstatus); // Clear all flags so that we can print results if (!Failure) { ThreadError = 0; } // based on error return status TerminateThread(GetCurrentThread(), STATUS_SUCCESS); } /************************************************************************/ /*++ This routine is responsible for displaying results for all the client thread. It displays both per client and total throughput. --*/ VOID Display_Results(VOID) { USHORT Cindex = 0; ULONG TotTps = 0L; ULONG TotSendThrpt = 0L; ULONG TotRecvThrpt = 0L; ULONG CliThrput = 0L; ULONG Remainder = 0L; ULONG BytesSent = 0L; ULONG BytesRcvd = 0L; BytesSent = (Clients[Cindex].c_reqbuf.SendSize) * (Clients[Cindex].c_reqbuf.NumSends) * (Clients[Cindex].c_reqbuf.Iterations); BytesRcvd = (Clients[Cindex].c_reqbuf.RecvSize) * (Clients[Cindex].c_reqbuf.NumRecvs) * (Clients[Cindex].c_reqbuf.Iterations); printf("Total: Trans: %ld Send: %lu Bytes Receive: %lu Bytes\n", (Clients[Cindex].c_reqbuf.NumSends * Clients[Cindex].c_reqbuf.Iterations), BytesSent, BytesRcvd); // Display all the results for (Cindex = 0; Cindex < NClients; Cindex++) { if (!(Clients[Cindex].c_Duration)) { Clients[Cindex].c_Duration++; } // First put the TPS number // Get total TPS CliThrput = (Clients[Cindex].c_reqbuf.NumSends * 1000) * (Clients[Cindex].c_reqbuf.Iterations); CliThrput /= Clients[Cindex].c_Duration; TotTps += CliThrput; printf("Cli:%d Dura: %ld ms Throughput: %ld TPS ", Cindex, Clients[Cindex].c_Duration, CliThrput); // Now put the BPS number // First get Send BPS numbers CliThrput= BytesSent / (Clients[Cindex].c_Duration); // now calculate the remainder and get first three digits Remainder = BytesSent - (Clients[Cindex].c_Duration * CliThrput); Remainder = (Remainder*1000)/Clients[Cindex].c_Duration; CliThrput = (CliThrput * 1000) + Remainder; // duration was in msec TotSendThrpt += CliThrput; printf(" Send: %ld BPS ", CliThrput); // First get Receive BPS numbers CliThrput = BytesRcvd / (Clients[Cindex].c_Duration); // now calculate the remainder and get first three digits Remainder = BytesRcvd - (Clients[Cindex].c_Duration * CliThrput); Remainder = (Remainder*1000)/Clients[Cindex].c_Duration; CliThrput = (CliThrput * 1000) + Remainder; // duration was in msec TotRecvThrpt += CliThrput; printf(" RecvThrpt: %ld BPS; \n", CliThrput ); } printf("--------------------------------------------------------\n"); printf("Total Throughput: %ld TPS ",TotTps); printf(" Send-BPS: %ld BPS ",TotSendThrpt); printf(" Recv-BPS: %ld BPS\n",TotRecvThrpt); } /************************************************************************/ NTSTATUS Parse_Cmd_Line(USHORT argc, CHAR *argv[]) { USHORT i; CHAR *s; NTSTATUS pstatus = 0L; BOOLEAN doingok = TRUE; if (argc > 12) { printf("Too many arguments \n"); pstatus = -1L; } strncpy(Xport, NamePipe, 8); for (i=1; (doingok) && (i< argc); i++) { s = argv[i]; if ((*s == '/') && ((*(s+2) == ':') || (*(s+3) == ':') )) { s++; switch(*s) { case 'a' : // Net number for XNS:SPX case 'A' : RtlCopyMemory( Remote_Net_Number, get_network_number((PCHAR)s+2), 4); if (Remote_Net_Number[0] == 'X') { printf("incorrect net number: "); printf(" e.g. /a:11223344 (8 hex) \n"); Usage(argv[0]); pstatus = -1L; } break; case 'b' : case 'B' : LanaBase = (USHORT)atoi(s+2); break; case 'c' : // number of clients case 'C' : NClients = (USHORT)atoi(s+2); break; case 'l' : case 'L' : LanaCount = (USHORT)atoi(s+2); break; case 'm' : case 'M' : MachineNumber = (USHORT)atoi(s+2); break; case 'i' : // iterations case 'I' : Iterations = (USHORT)atoi(s+2); break; case 'n' : // number of Sends/Recvs case 'N' : switch(*(s+1)) { case 'r': case 'R': NumRecvs = (USHORT)atoi(s+3); break; case 's': case 'S': NumSends = (USHORT)atoi(s+3); break; default: doingok = FALSE; } break; case 'p' : // NamedPipe mode case 'P' : switch(*(s+2)) { case 'm': case 'M': PipeType = PIPE_TYPE_MESSAGE; PipeMode = PIPE_READMODE_MESSAGE; break; case 's': case 'S': PipeType = PIPE_TYPE_BYTE; PipeMode = PIPE_READMODE_BYTE; break; default: doingok = FALSE; } break; case 'r' : // Send size case 'R' : RecvSize = (USHORT)atoi(s+2); break; case 's' : // Send size case 'S' : SendSize = (USHORT)atoi(s+2); break; case 't' : // Test Command case 'T' : TestCmd =(UCHAR) *(s+2); switch(TestCmd){ case 't': TestCmd = 'T'; break; case 'u': case 'U': TestCmd = 'U'; break; case 'P': case 'T': break; default : printf("incorrect test command\n"); Usage(argv[0]); pstatus = -1L; } break; case 'h' : // Server Name or host IP address case 'H' : ServerName = (PCHAR)malloc(SRVNAME_LEN); strcpy(ServerName,(PCHAR)(s+2)); // if XNS:SPX or IPX then get Node Address if ((IPCType == SCXNS)|| (IPCType == SCIPX)){ RtlCopyMemory( Remote_Node_Number, get_node_number(ServerName), 6); if (Remote_Node_Number[0] == 'X') { printf("incorrect node number: "); printf("e.g. /h:112233445566 \n"); Usage(argv[0]); pstatus = -1L; } } break; case 'x' : // Xport Type case 'X' : strncpy(Xport, (PUCHAR) (s+2), 8); if (!_stricmp(Xport,NamePipe)) { IPCType = NP; break; } if (!_stricmp(Xport,NetBIOS)) { IPCType = NB; break; } if (!_stricmp(Xport,SocketXNS)) { IPCType = SCXNS; AddrFly = AF_NS; break; } if (!_stricmp(Xport,SocketTCP)) { IPCType = SCTCP; AddrFly = AF_INET; break; } if (!_stricmp(Xport,UDP)) { IPCType = SCUDP; AddrFly = AF_INET; break; } if (!_stricmp(Xport,IPX)) { IPCType = SCIPX; AddrFly = AF_NS; break; } if (!_stricmp(Xport,DGNetBIOS)) { IPCType = DGNB; break; } // bad choice of Xport doingok = FALSE; break; default : doingok = FALSE; } } else { doingok = FALSE; } } if (!doingok) { Usage(argv[0]); pstatus = -1L; } else { // if successful then if ((IPCType == SCXNS) || (IPCType == SCIPX)) { // make server name a 10 byte address RtlCopyMemory(ServerName,Remote_Node_Number,6); RtlCopyMemory(ServerName+6,Remote_Net_Number,4); } if (((IPCType != NB) && (IPCType != NP) && (IPCType != DGNB)) && (ServerName == NULL)) { printf("Please enter Server Address \n"); pstatus = -1L; } } return(pstatus); } /************************************************************************/ VOID Usage(char * PrgName) { fprintf(stderr, "Usage: %s [/c: ] [/h:] [/s:] [/r:] [/b:] [/l:]\n",PrgName); fprintf(stderr, " Opt Default Defines\n"); fprintf(stderr, " === ======= =======\n"); fprintf(stderr, " /c: 1 Number of clients\n"); fprintf(stderr, " /h: NULL SrvName/HostIPaddr.\n"); fprintf(stderr, " /Node Number for XNS\n"); fprintf(stderr, " /I: 1000 Number of Iterations\n"); fprintf(stderr, " /ns: 1 Number of Sends\n"); fprintf(stderr, " /nr: 1 Number of Receives\n"); fprintf(stderr, " /r: 32 Receive size\n"); fprintf(stderr, " /s: 32 Send size\n"); fprintf(stderr, " /t: NULL Special Test cmd,U,T\n"); fprintf(stderr, " /x: Nmp Xport(IPC)type\n"); fprintf(stderr, " Nmp/NetB/SockTCP/\n"); fprintf(stderr, " SockXNS/UDP/IPX/DGNetB\n"); fprintf(stderr, " For NNamedPipe: \n"); fprintf(stderr, " /p: m Nmp : Pipe Type m/s\n"); fprintf(stderr, " For NetBIOS: \n"); fprintf(stderr, " /b: 0 NetB: lana base\n"); fprintf(stderr, " /l: 1 NetB: lana count\n"); fprintf(stderr, " /m: 1 machine number\n"); fprintf(stderr, " For XNS: \n"); fprintf(stderr, " /a: 1 Net Number for XNS\n"); } /************************************************************************/ /*++ This routine sets up all the function pointers based on Xport type --*/ VOID Setup_Function_Pointers() { // I could do real OOP here and just set up one pointer to all functions // based on Xport type take the action switch(IPCType) { case NP: IPC_Initialize = NMP_Initialize; IPC_PerClientInit = NMP_PerClientInit; IPC_Connect_To_Server = NMP_Connect_To_Server; IPC_Disconnect_From_Server = NMP_Disconnect_From_Server; IPC_Cleanup = NMP_Cleanup; IPC_Allocate_Memory = NMP_Allocate_Memory; IPC_DoHandshake = NMP_DoHandshake; IPC_ReadFromIPC = NMP_ReadFromIPC; IPC_WriteToIPC = NMP_WriteToIPC; IPC_XactIO = NMP_XactIO; IPC_Deallocate_Memory = NMP_Deallocate_Memory; IPC_ThreadCleanUp = NMP_ThreadCleanUp; break; case NB: IPC_Initialize = NB_Initialize; IPC_PerClientInit = NB_PerClientInit; IPC_Cleanup = NB_Cleanup; IPC_Connect_To_Server = NB_Connect_To_Server; IPC_Disconnect_From_Server = NB_Disconnect_From_Server; IPC_Allocate_Memory = NB_Allocate_Memory; IPC_DoHandshake = NB_DoHandshake; IPC_ReadFromIPC = NB_ReadFromIPC; IPC_WriteToIPC = NB_WriteToIPC; IPC_XactIO = NB_XactIO; IPC_Deallocate_Memory = NB_Deallocate_Memory; IPC_ThreadCleanUp = NB_ThreadCleanUp; break; case SCTCP: IPC_Initialize = SCTCP_Initialize; IPC_PerClientInit = SCTCP_PerClientInit; IPC_Cleanup = SCTCP_Cleanup; IPC_Connect_To_Server = SCTCP_Connect_To_Server; IPC_Disconnect_From_Server = SCTCP_Disconnect_From_Server; IPC_Allocate_Memory = SCTCP_Allocate_Memory; IPC_DoHandshake = SCTCP_DoHandshake; IPC_ReadFromIPC = SCTCP_ReadFromIPC; IPC_WriteToIPC = SCTCP_WriteToIPC; IPC_XactIO = SCTCP_XactIO; IPC_Deallocate_Memory = SCTCP_Deallocate_Memory; IPC_ThreadCleanUp = SCTCP_ThreadCleanUp; break; case SCXNS: IPC_Initialize = SCXNS_Initialize; IPC_PerClientInit = SCXNS_PerClientInit; IPC_Connect_To_Server = SCXNS_Connect_To_Server; IPC_Disconnect_From_Server = SCXNS_Disconnect_From_Server; IPC_Cleanup = SCXNS_Cleanup; IPC_Allocate_Memory = SCXNS_Allocate_Memory; IPC_DoHandshake = SCXNS_DoHandshake; IPC_ReadFromIPC = SCXNS_ReadFromIPC; IPC_WriteToIPC = SCXNS_WriteToIPC; IPC_XactIO = SCXNS_XactIO; IPC_Deallocate_Memory = SCXNS_Deallocate_Memory; IPC_ThreadCleanUp = SCXNS_ThreadCleanUp; break; case SCUDP: IPC_Initialize = SCUDP_Initialize; IPC_PerClientInit = SCUDP_PerClientInit; IPC_Connect_To_Server = SCUDP_Connect_To_Server; IPC_Disconnect_From_Server = SCUDP_Disconnect_From_Server; IPC_Cleanup = SCUDP_Cleanup; IPC_Allocate_Memory = SCUDP_Allocate_Memory; IPC_DoHandshake = SCUDP_DoHandshake; IPC_ReadFromIPC = SCUDP_ReadFromIPC; IPC_WriteToIPC = SCUDP_WriteToIPC; IPC_Deallocate_Memory = SCUDP_Deallocate_Memory; IPC_ThreadCleanUp = SCUDP_ThreadCleanUp; break; case SCIPX: IPC_Initialize = SCIPX_Initialize; IPC_PerClientInit = SCIPX_PerClientInit; IPC_PerClientInit = SCIPX_PerClientInit; IPC_Connect_To_Server = SCIPX_Connect_To_Server; IPC_Disconnect_From_Server = SCIPX_Disconnect_From_Server; IPC_Cleanup = SCIPX_Cleanup; IPC_Allocate_Memory = SCIPX_Allocate_Memory; IPC_DoHandshake = SCIPX_DoHandshake; IPC_ReadFromIPC = SCIPX_ReadFromIPC; IPC_WriteToIPC = SCIPX_WriteToIPC; IPC_Deallocate_Memory = SCIPX_Deallocate_Memory; IPC_ThreadCleanUp = SCIPX_ThreadCleanUp; break; case DGNB: IPC_Initialize = DGNB_Initialize; IPC_PerClientInit = DGNB_PerClientInit; IPC_Cleanup = DGNB_Cleanup; IPC_Connect_To_Server = DGNB_Connect_To_Server; IPC_Disconnect_From_Server = DGNB_Disconnect_From_Server; IPC_Allocate_Memory = DGNB_Allocate_Memory; IPC_DoHandshake = DGNB_DoHandshake; IPC_ReadFromIPC = DGNB_ReadFromIPC; IPC_WriteToIPC = DGNB_WriteToIPC; IPC_XactIO = DGNB_XactIO; IPC_Deallocate_Memory = DGNB_Deallocate_Memory; IPC_ThreadCleanUp = DGNB_ThreadCleanUp; break; default : // problem here printf("Incorrect Xport selection\n"); } } /************************************************************************/ /*++ This routine makes the main thread wait for all the client threads to exit. --*/ NTSTATUS Wait_For_Client_Threads(VOID) { NTSTATUS wstatus; // LARGE_INTEGER MTimeout; DWORD MTimeout; // printf("Main thread waiting for Client threads "); printf("..Wait"); // MTimeout.LowPart = 0xFFFFFFFF; // MTimeout.HighPart = 0x7FFFFFFF; // -1L DIDN't work MTimeout = INFINITE; wstatus = WaitForMultipleObjectsEx( NClients, Threads, TRUE, MTimeout, // Default timeout TRUE); // Alertable printf("..Over..Results:\n"); if (!NT_SUCCESS(wstatus)) { printf ("Failed on wait err=%lx\n",wstatus); } return wstatus; } /************************************************************************/ VOID Cleanup(VOID) { USHORT Cindex = 0; // client index NTSTATUS exitstatus = 0; NTSTATUS cstatus; for (Cindex = 0; Cindex < NClients; Cindex++) { // terminate the thread cstatus = TerminateThread(Clients[Cindex].c_hThHandle, exitstatus); /* if (!NT_SUCCESS(cstatus)) { printf("Failed to terminate thread no:%d err=%lx\n", Cindex,cstatus); } */ } // printf("Terminated All Threads\n"); } /************************************************************************/