//==========================================================================// // Includes // //==========================================================================// #include "setedit.h" // included by all source files #include "line.h" // external declarations for this file //#include // for _tcsncpy #include "fileutil.h" // for FileRead, FileWrite #include "pmemory.h" // for MemoryXXX (mallloc-type) routines #include "perfdata.h" // for UpdateSystemData, et al #include "perfmops.h" // for InsertLine #include "system.h" // for SystemAdd #include "utils.h" #include "counters.h" // CounterEntry #include // for strncpy #ifdef UNICODE #define _tcsncpy wcsncpy #else #define _tcsncpy strncpy #endif TCHAR LOCAL_SYS_CODE_NAME[] = TEXT("....") ; #define sizeofCodeName sizeof(LOCAL_SYS_CODE_NAME) / sizeof(TCHAR) - 1 // Local Function prototype PLINE ReadLine (PPERFSYSTEM *ppSystem, PPPERFSYSTEM ppSystemFirst, PPERFDATA *ppPerfData, HANDLE hFile, int LineType, PDISKLINE *ppDiskLine, DWORD *pSizeofDiskLine) ; //==========================================================================// // Exported Functions // //==========================================================================// PLINE LineAllocate (void) /* Effect: Allocate and initialize a Line data structure. Lines are used as the primary elements of both charts and alerts. Establish any representation invariants for the Line type. Also alllocate another structure, an array of data elements, iNumDataValues long. */ { // LineAllocate PLINE pLine ; pLine = MemoryAllocate (sizeof (LINESTRUCT)) ; if (pLine) { // don't need to zero these again since MemoryAllocate is using // GMEM_ZEROPOINT // pLine->pLineNext = NULL ; // pLine->pLineCounterNext = NULL ; // do want to do this since (FLOAT)0.0 is not 0 pLine->lnMinValue = pLine->lnMaxValue = pLine->lnAveValue = (FLOAT) 0.0 ; // we want to take 2 samples before plotting the first data pLine->bFirstTime = 2 ; } // if return (pLine) ; } // LineAllocate void LineFree (PLINE pLine) { // LineFree // free any memory allocated by this line if (pLine->lnSystemName) MemoryFree (pLine->lnSystemName) ; if (pLine->lnObjectName) MemoryFree (pLine->lnObjectName) ; if (pLine->lnCounterName) MemoryFree (pLine->lnCounterName) ; if (pLine->lnInstanceName) MemoryFree (pLine->lnInstanceName) ; if (pLine->lnParentObjName) MemoryFree (pLine->lnParentObjName) ; if (pLine->lnPINName) MemoryFree (pLine->lnPINName) ; if (pLine->lpszAlertProgram) MemoryFree (pLine->lpszAlertProgram) ; if (pLine->hPen) DeletePen(pLine->hPen); if (pLine->hBrush) DeletePen(pLine->hBrush); #if 0 if (pLine->lnValues) MemoryFree (pLine->lnValues) ; if (pLine->aiLogIndexes) MemoryFree (pLine->aiLogIndexes) ; #endif MemoryFree (pLine) ; } // LineFree void LineAppend (PPLINE ppLineFirst, PLINE pLineNew) { PLINE pLine ; if (!*ppLineFirst) *ppLineFirst = pLineNew ; else { // else for (pLine = *ppLineFirst ; pLine->pLineNext ; pLine = pLine->pLineNext) /* nothing */ ; pLine->pLineNext = pLineNew ; } // else } BOOL LineRemove (PPLINE ppLineFirst, PLINE pLineRemove) { PLINE pLine ; if (*ppLineFirst == pLineRemove) { *ppLineFirst = (*ppLineFirst)->pLineNext ; return (TRUE) ; } for (pLine = *ppLineFirst ; pLine->pLineNext ; pLine = pLine->pLineNext) { // for if (pLine->pLineNext == pLineRemove) { pLine->pLineNext = pLineRemove->pLineNext ; return (TRUE) ; } // if } // for return (FALSE) ; } // LineRemove int NumLines (PLINE pLineFirst) { // NumLines PLINE pLine ; int iNumLines ; if (!pLineFirst) return (0) ; iNumLines = 0 ; for (pLine = pLineFirst ; pLine ; pLine = pLine->pLineNext) { // for iNumLines++ ; } // for return (iNumLines) ; } // NumLines LPTSTR LineInstanceName (PLINE pLine) { if (pLine->lnObject.NumInstances <= 0) return (NULL) ; else return (pLine->lnInstanceName) ; } LPTSTR LineParentName (PLINE pLine) { if (pLine->lnObject.NumInstances <= 0) return (NULL) ; else if (pLine->lnInstanceDef.ParentObjectTitleIndex) return (pLine->lnPINName) ; else return (NULL) ; } void LineCounterAppend (PPLINE ppLineFirst, PLINE pLineNew) { PLINE pLine ; if (!*ppLineFirst) *ppLineFirst = pLineNew ; else { // else for (pLine = *ppLineFirst ; pLine->pLineCounterNext ; pLine = pLine->pLineCounterNext) /* nothing */ ; pLine->pLineCounterNext = pLineNew ; } // else } BOOL EquivalentLine (PLINE pLine1, PLINE pLine2) { // LineEquivalent return (pstrsame (pLine1->lnCounterName, pLine2->lnCounterName) && pstrsame (pLine1->lnInstanceName, pLine2->lnInstanceName) && pstrsame (pLine1->lnPINName, pLine2->lnPINName) && pstrsame (pLine1->lnObjectName, pLine2->lnObjectName) && pstrsamei (pLine1->lnSystemName, pLine2->lnSystemName)) ; } // LineEquivalent PLINE FindEquivalentLine (PLINE pLineToFind, PLINE pLineFirst) { PLINE pLine ; for (pLine = pLineFirst ; pLine ; pLine = pLine->pLineNext) { // for if (EquivalentLine (pLine, pLineToFind)) return (pLine) ; } // for return (NULL) ; } // FindEquivalentLine // This routine is used only to read the system name from a disk string // It is mainly for performance improvement. LPTSTR DiskStringReadSys (PDISKSTRING pDS) { // DiskStringReadSys LPTSTR lpszText ; LPTSTR pDiskSysName ; int iIndex ; BOOL bLocalSysName = FALSE ; if (pDS->dwLength == 0) { return (NULL) ; } if (pDS->dwLength == sizeofCodeName) { // check for LOCAL_SYS_CODE_NAME bLocalSysName = TRUE ; pDiskSysName = (LPTSTR)((PBYTE) pDS + pDS->dwOffset) ; for (iIndex = 0 ; iIndex < sizeofCodeName; iIndex++, pDiskSysName++) { if (*pDiskSysName != LOCAL_SYS_CODE_NAME[iIndex]) { bLocalSysName = FALSE ; break ; } } } if (bLocalSysName) { lpszText = MemoryAllocate ((lstrlen(LocalComputerName)+1) * sizeof(TCHAR)) ; if (lpszText) { lstrcpy (lpszText, LocalComputerName) ; } } else { lpszText = MemoryAllocate (sizeof (TCHAR) * (pDS->dwLength + 1)) ; if (lpszText) { _tcsncpy ((WCHAR *)lpszText, (WCHAR *)((PBYTE) pDS + pDS->dwOffset), pDS->dwLength) ; } } return (lpszText) ; } // DiskStringReadSys LPTSTR DiskStringRead (PDISKSTRING pDS) { // DiskStringRead LPTSTR lpszText ; if (pDS->dwLength == 0) { return (NULL) ; } lpszText = MemoryAllocate (sizeof (TCHAR) * (pDS->dwLength + 1)) ; if (!lpszText) { return (NULL) ; } _tcsncpy ((WCHAR *)lpszText, (WCHAR *)((PBYTE) pDS + pDS->dwOffset), pDS->dwLength) ; return (lpszText) ; } // DiskStringRead int DiskStringLength (LPTSTR lpszText) { if (!lpszText) return (0) ; else return (lstrlen (lpszText)) ; } PBYTE DiskStringCopy (PDISKSTRING pDS, LPTSTR lpszText, PBYTE pNextFree) { if (!lpszText) { pDS->dwOffset = 0 ; pDS->dwLength = 0 ; return (pNextFree) ; } else { pDS->dwOffset = (DWORD)(pNextFree - (PBYTE) pDS) ; pDS->dwLength = DiskStringLength (lpszText) ; _tcsncpy ((WCHAR *)pNextFree, (WCHAR *)lpszText, pDS->dwLength) ; return (pNextFree + pDS->dwLength * sizeof(TCHAR)) ; } } // DiskStringCopy void CounterName (PPERFSYSTEM pSystem, PPERFCOUNTERDEF pCounter, LPTSTR lpszCounter) { // CounterName //!! strclr (lpszCounter) ; lpszCounter [0] = TEXT('\0') ; QueryPerformanceName (pSystem, pCounter->CounterNameTitleIndex, 0, 256, lpszCounter, FALSE) ; } // CounterName PERF_OBJECT_TYPE UNALIGNED * LineFindObject (PPERFSYSTEM pSystem, PPERFDATA pPerfData, PLINE pLine) /* Effect: Set the lnObject field of pLine to the object with the name of lnObjectName, and return TRUE. Return FALSE if there is no such object. */ { // LineFindObject PERF_OBJECT_TYPE UNALIGNED *pObject ; pObject = (PERF_OBJECT_TYPE UNALIGNED *)GetObjectDefByName (pSystem, pPerfData, pLine->lnObjectName) ; if (pObject) { pLine->lnObject = *pObject ; return (pObject) ; } else return (NULL) ; } // LineFindObject PPERFCOUNTERDEF LineFindCounter (PPERFSYSTEM pSystem, PERF_OBJECT_TYPE UNALIGNED *pObject, PPERFDATA pPerfData, PLINE pLine) { // LineFindCounter UINT i ; PPERFCOUNTERDEF pCounter ; TCHAR szCounter [256] ; for (i = 0, pCounter = FirstCounter (pObject) ; i < pObject->NumCounters ; i++, pCounter = NextCounter (pCounter)) { // for CounterName (pSystem, pCounter, szCounter) ; if (strsame (szCounter, pLine->lnCounterName)) { pLine->lnCounterDef = *pCounter ; return (pCounter) ; } // if } // for return (NULL) ; } // LineFindCounter PPERFINSTANCEDEF LineFindInstance (PPERFDATA pPerfData, PERF_OBJECT_TYPE UNALIGNED *pObject, PLINE pLine) { // LineFindInstance PPERFINSTANCEDEF pInstance = NULL ; if ((pObject->NumInstances > 0) && pLine->lnInstanceName) { // instances exist, find the right one if (pLine->lnUniqueID != PERF_NO_UNIQUE_ID) { pInstance = GetInstanceByUniqueID(pObject, pLine->lnUniqueID, pLine->dwInstanceIndex) ; } else { pInstance = GetInstanceByName(pPerfData, pObject, pLine->lnInstanceName, pLine->lnPINName, pLine->dwInstanceIndex) ; } } if (pInstance) { pLine->lnInstanceDef = *pInstance ; } return (pInstance) ; } // LineFindInstance void ReadLines (HANDLE hFile, DWORD dwNumLines, PPPERFSYSTEM ppSystemFirst, PPLINE ppLineFirst, int LineType) { DWORD i ; PPERFDATA pPerfData ; PLINE pLine ; PPERFSYSTEM pSystem ; PDISKLINE pDiskLine = NULL ; DWORD SizeofDiskLine = 0 ; // bytes in pDiskLine pPerfData = AllocatePerfData () ; pSystem = *ppSystemFirst ; #if 0 if (!pSystem) { pSystem = SystemAdd (ppSystemFirst, LocalComputerName, NULL) ; pSystem = *ppSystemFirst ; //!! } UpdateSystemData (pSystem, &pPerfData) ; #endif pDiskLine = MemoryAllocate (FilePathLen) ; if (!pDiskLine) { // no memory to begin with, forget it DlgErrorBox (hWndMain, ERR_NO_MEMORY) ; return ; } SizeofDiskLine = FilePathLen ; for (i = 0 ; i < dwNumLines ; i++) { pLine = ReadLine (&pSystem, ppSystemFirst, &pPerfData, hFile, LineType, &pDiskLine, &SizeofDiskLine) ; if (pLine) InsertLine (pLine) ; } if (pDiskLine) { MemoryFree (pDiskLine); } MemoryFree (pPerfData) ; } // ReadLines void LineSetCounter (PLINE pLine, PPERFSYSTEM pSystem, PPERFCOUNTERDEF pCounter, PPERFINSTANCEDEF pInstance) /* Effect: Set the counter-specific portions of pLine to point to the desired counter. Called By: AddLine, ReadLine. */ { } PLINE ReadLine (PPERFSYSTEM *ppSystem, PPPERFSYSTEM ppSystemFirst, PPERFDATA *ppPerfData, HANDLE hFile, int LineType, PDISKLINE *ppDiskLine, DWORD *pSizeofDiskLine) /* Effect: Read in a line from the file hFile, at the current file position. Internals: The very first characters are a line signature, then a length integer. If the signature is correct, then allocate the length amount, and work with that. */ { // ReadLine PLINE pLine ; struct { DWORD dwSignature ; DWORD dwLength ; } LineHeader ; PERF_OBJECT_TYPE UNALIGNED *pObject ; PPERFCOUNTERDEF pCounter ; PDISKLINE pDiskLine ; // Local copy of the pointer #ifdef KEEP_IT int i ; int iCounterIndex ; int j ; PERF_COUNTER_BLOCK *pCounterBlock ; #endif PPERFINSTANCEDEF pInstance ; // PPERFINSTANCEDEF pInstanceParent ; // TCHAR szInstanceParent [256] ; // TCHAR szObjectParent [PerfObjectLen] ; pLine = NULL ; //=============================// // read and compare signature // //=============================// if (!FileRead (hFile, &LineHeader, sizeof (LineHeader))) return (NULL) ; if (LineHeader.dwSignature != dwLineSignature || LineHeader.dwLength == 0) { SetLastError (ERROR_BAD_FORMAT) ; return (NULL) ; } //=============================// // read and allocate length // //=============================// // if (!FileRead (hFile, &dwLength, sizeof (dwLength)) || dwLength == 0) // return (NULL) ; // check if we need a bigger buffer, // normally, it should be the same except the first time... if (LineHeader.dwLength > *pSizeofDiskLine) { if (*ppDiskLine) { // free the previous buffer MemoryFree (*ppDiskLine); *pSizeofDiskLine = 0 ; } // re-allocate a new buffer *ppDiskLine = (PDISKLINE) MemoryAllocate (LineHeader.dwLength) ; if (!(*ppDiskLine)) { // no memory, should flag an error... return (NULL) ; } *pSizeofDiskLine = LineHeader.dwLength ; } pDiskLine = *ppDiskLine ; //=============================// // copy diskline, alloc line // //=============================// if (!FileRead (hFile, pDiskLine, LineHeader.dwLength)) return (NULL) ; pLine = LineAllocate () ; if (!pLine) { return (NULL) ; } pLine->iLineType = pDiskLine->iLineType ; //=============================// // convert system information // //=============================// pLine->lnSystemName = DiskStringReadSys (&(pDiskLine->dsSystemName)) ; if (!pLine->lnSystemName) goto ErrorBadLine ; if (!*ppSystem || !strsamei (pLine->lnSystemName, (*ppSystem)->sysName)) { *ppSystem = SystemAdd (ppSystemFirst, pLine->lnSystemName, NULL) ; if (!*ppSystem) { SetLastError (ERROR_BAD_FORMAT) ; goto ErrorBadLine ; } UpdateSystemData (*ppSystem, ppPerfData) ; } // if //=============================// // convert object information // //=============================// pLine->lnObjectName = DiskStringRead (&(pDiskLine->dsObjectName)) ; if (!pLine->lnObjectName) goto ErrorBadLine ; pObject = LineFindObject (*ppSystem, *ppPerfData, pLine) ; if (!pObject) { SetLastError (ERROR_BAD_FORMAT) ; goto ErrorBadLine ; } //=============================// // convert counter information // //=============================// pLine->lnCounterName = DiskStringRead (&(pDiskLine->dsCounterName)) ; if (!pLine->lnCounterName) goto ErrorBadLine ; pCounter = LineFindCounter (*ppSystem, pObject, *ppPerfData, pLine) ; if (!pCounter) { SetLastError (ERROR_BAD_FORMAT) ; goto ErrorBadLine ; } //=============================// // convert instance info // //=============================// pLine->lnUniqueID = pDiskLine->dwUniqueID ; pLine->lnInstanceName = DiskStringRead (&(pDiskLine->dsInstanceName)) ; pLine->lnPINName = DiskStringRead (&(pDiskLine->dsPINName)) ; if (pLine->lnObject.NumInstances > 0 && pLine->lnInstanceName == NULL) { goto ErrorBadLine ; } pInstance = LineFindInstance (*ppPerfData, pObject, pLine) ; if (pInstance) { pLine->lnParentObjName = DiskStringRead (&(pDiskLine->dsParentObjName)) ; } else { pLine->bUserEdit = TRUE ; pLine->lnParentObjName = DiskStringRead (&(pDiskLine->dsParentObjName)) ; } //=============================// // convert chart information // //=============================// if (LineType == IDM_VIEWCHART) { pLine->Visual = pDiskLine->Visual ; pLine->hPen = CreatePen (pLine->Visual.iStyle, pLine->Visual.iWidth, pLine->Visual.crColor) ; pLine->iScaleIndex = pDiskLine->iScaleIndex ; pLine->eScale = pDiskLine->eScale ; } //=============================// // convert alert information // //=============================// if (LineType == IDM_VIEWALERT) { pLine->Visual = pDiskLine->Visual ; pLine->hBrush = CreateSolidBrush (pLine->Visual.crColor) ; pLine->bAlertOver = pDiskLine->bAlertOver ; pLine->eAlertValue = pDiskLine->eAlertValue ; pLine->lpszAlertProgram = DiskStringRead (&(pDiskLine->dsAlertProgram)) ; pLine->bEveryTime = pDiskLine->bEveryTime ; pLine->bAlerted = FALSE ; } //=============================// // Convert the nasty stuff // //=============================// pLine->lnCounterType = pCounter->CounterType; pLine->lnCounterLength = pCounter->CounterSize; // we don't need these line info since we will get it // from the first couple clock ticks... // If we decide to keep these line, just define KEEP_IT #ifdef KEEP_IT pLine->lnOldTime = (*ppPerfData)->PerfTime ; pLine->lnNewTime = (*ppPerfData)->PerfTime ; pLine->lnOldTime100Ns = (*ppPerfData)->PerfTime100nSec ; pLine->lnNewTime100Ns = (*ppPerfData)->PerfTime100nSec; pLine->lnPerfFreq = (*ppPerfData)->PerfFreq ; for (j = 0 ; j < 2 ; j++) { pLine->lnaCounterValue[j].LowPart = 0 ; pLine->lnaCounterValue[j].HighPart = 0 ; } if (pObject->NumInstances > 0 && pInstance) { pCounterBlock = (PERF_COUNTER_BLOCK *) ( (PBYTE) pInstance + pInstance->ByteLength); } else { pCounterBlock = (PERF_COUNTER_BLOCK *) ( (PBYTE) pObject + pObject->DefinitionLength); } if (pLine->lnCounterLength <= 4) pLine->lnaOldCounterValue[0].LowPart = * ( (DWORD FAR *) ( (PBYTE)pCounterBlock + pCounter[0].CounterOffset)); else { pLine->lnaOldCounterValue[0] = * ( (LARGE_INTEGER *) ( (PBYTE)pCounterBlock + pCounter[0].CounterOffset)); } // Get second counter, only if we are not at // the end of the counters; some computations // require a second counter iCounterIndex = CounterIndex (pCounter, pObject) ; if ((UINT) iCounterIndex < pObject->NumCounters - 1 && iCounterIndex != -1) { if (pLine->lnCounterLength <= 4) pLine->lnaOldCounterValue[1].LowPart = * ( (DWORD FAR *) ( (PBYTE)pCounterBlock + pCounter[1].CounterOffset)); else pLine->lnaOldCounterValue[1] = * ( (LARGE_INTEGER *) ( (PBYTE)pCounterBlock + pCounter[1].CounterOffset)); } // pLine->valNext = CounterFuncEntry ; pLine->valNext = CounterEntry ; pLine->lnaOldCounterValue[0] = pLine->lnaCounterValue[0]; pLine->lnaOldCounterValue[1] = pLine->lnaCounterValue[1]; #endif // KEEP_IT // pLine->valNext = CounterFuncEntry ; pLine->valNext = CounterEntry ; return (pLine) ; ErrorBadLine: if (!pLine) { LineFree (pLine) ; } return (NULL) ; } // ReadLine BOOL WriteLine (PLINE pLine, HANDLE hFile) { // WriteLine PDISKLINE pDiskLine ; DWORD dwSignature ; DWORD dwLength ; PBYTE pNextFree ; BOOL bConvertName ; //=============================// // write signature // //=============================// dwSignature = dwLineSignature ; if (!FileWrite (hFile, &dwSignature, sizeof (dwSignature))) return (FALSE) ; if (IsLocalComputer(pLine->lnSystemName)) { bConvertName = TRUE ; } else { bConvertName = FALSE ; } //=============================// // compute and allocate length // //=============================// dwLength = sizeof (DISKLINE) ; if (bConvertName) { dwLength += DiskStringLength (LOCAL_SYS_CODE_NAME) * sizeof (TCHAR) ; } else { dwLength += DiskStringLength (pLine->lnSystemName) * sizeof (TCHAR) ; } dwLength += DiskStringLength (pLine->lnObjectName) * sizeof (TCHAR) ; dwLength += DiskStringLength (pLine->lnCounterName) * sizeof (TCHAR) ; dwLength += DiskStringLength (pLine->lnInstanceName) * sizeof (TCHAR) ; dwLength += DiskStringLength (pLine->lnPINName) * sizeof (TCHAR) ; dwLength += DiskStringLength (pLine->lnParentObjName) * sizeof (TCHAR) ; dwLength += DiskStringLength (pLine->lpszAlertProgram) * sizeof (TCHAR) ; if (!FileWrite (hFile, &dwLength, sizeof (dwLength))) return (FALSE) ; pDiskLine = (PDISKLINE) MemoryAllocate (dwLength) ; if (!pDiskLine) return (FALSE) ; pNextFree = (PBYTE) pDiskLine + sizeof (DISKLINE) ; //=============================// // convert fixed size fields // //=============================// pDiskLine->iLineType = pLine->iLineType ; pDiskLine->dwUniqueID = pLine->lnUniqueID ; pDiskLine->Visual = pLine->Visual ; pDiskLine->iScaleIndex = pLine->iScaleIndex ; pDiskLine->eScale = pLine->eScale ; pDiskLine->bAlertOver = pLine->bAlertOver ; pDiskLine->eAlertValue = pLine->eAlertValue ; pDiskLine->bEveryTime = pLine->bEveryTime ; //=============================// // copy disk string fields // //=============================// if (bConvertName) { pNextFree = DiskStringCopy (&pDiskLine->dsSystemName, LOCAL_SYS_CODE_NAME, pNextFree) ; } else { pNextFree = DiskStringCopy (&pDiskLine->dsSystemName, pLine->lnSystemName, pNextFree) ; } pNextFree = DiskStringCopy (&pDiskLine->dsObjectName, pLine->lnObjectName, pNextFree) ; pNextFree = DiskStringCopy (&pDiskLine->dsCounterName, pLine->lnCounterName, pNextFree) ; pNextFree = DiskStringCopy (&pDiskLine->dsParentObjName, pLine->lnParentObjName, pNextFree) ; pNextFree = DiskStringCopy (&pDiskLine->dsInstanceName, pLine->lnInstanceName, pNextFree) ; pNextFree = DiskStringCopy (&pDiskLine->dsPINName, pLine->lnPINName, pNextFree) ; pNextFree = DiskStringCopy (&pDiskLine->dsAlertProgram, pLine->lpszAlertProgram, pNextFree) ; FileWrite (hFile, pDiskLine, dwLength) ; MemoryFree (pDiskLine) ; return (TRUE) ; //ErrorBadLine: MemoryFree (pDiskLine) ; return (FALSE) ; } // WriteLine // we are not doing printing. In case we need this // later, then define DO_PRINTING #ifdef DO_PRINTING int aiPrinterLineStyles [] = { PS_SOLID, PS_DASH, PS_DOT, PS_DASHDOT, PS_DASHDOTDOT } ; #define NumPrinterLineStyles() \ (sizeof (aiPrinterLineStyles) / sizeof (aiPrinterLineStyles [0])) COLORREF acrPrinterLineColors [] = { RGB (192, 192, 192), RGB (128, 128, 128), RGB (64, 64, 64), RGB (0, 0, 0) } ; #define NumPrinterLineColors() \ (sizeof (acrPrinterLineColors) / sizeof (acrPrinterLineColors [0])) #endif // DO_PRINTING HPEN LineCreatePen (HDC hDC, PLINEVISUAL pVisual, BOOL bForPrint) { // LineCreatePen HPEN hPen ; hPen = CreatePen (pVisual->iStyle, pVisual->iWidth, pVisual->crColor) ; return (hPen) ; } // LineCreatePen VOID FreeLines (PLINESTRUCT pLineFirst) { // FreeLines PLINESTRUCT pLine,next_line; for (pLine = pLineFirst; pLine; pLine = next_line) { next_line = pLine->pLineNext; LineFree (pLine) ; } } // FreeLines