diff options
Diffstat (limited to 'general/PLX9x5x/test/plx.cpp')
| -rw-r--r-- | general/PLX9x5x/test/plx.cpp | 1104 |
1 files changed, 1104 insertions, 0 deletions
diff --git a/general/PLX9x5x/test/plx.cpp b/general/PLX9x5x/test/plx.cpp new file mode 100644 index 00000000..c5bae510 --- /dev/null +++ b/general/PLX9x5x/test/plx.cpp @@ -0,0 +1,1104 @@ +/*++ + +Copyright (c) Microsoft Corporation + +Module Name: + + plx.cpp + +Abstract: + + This module implements the PLX class which tests the DMA of PLX devices. + + Example usage: + plx.exe /wr /wb=100 # write-then-read once with a buffer of 100 bytes + plx.exe /thread # repeat write-then-read for default 5000 millisecs. + plx.exe /thread /time=1000 # repeat write-then-read for 1000 millisecs. + + NOTE: The /quite option will suppress most non-error messages. + NOTE: The options and parameters are case sensitive. + +Environment: + + User Mode Win2k or Later + +--*/ + +#define INITGUID + +#include "plx.hpp" + +// +// Define the spin count to be used for critical sections. The value +// specified below is arbitrary. Change it based on your requirements. +// +#define SPIN_COUNT_FOR_CS 0x4000 + +int g_TimeUp =0; + +DWORD WINAPI +ReadThreadProc( + LPVOID lpParameter + ) +{ + ULONG bytes; + PTHREAD_CONTEXT Context = (PTHREAD_CONTEXT)lpParameter; + + while(!g_TimeUp) { + + if(ReadFile(Context->hDevice, + Context->Buffer, + Context->BufferSize, + &bytes, + NULL)) { + + if (!Context->quite) { + printf("Read sucessful.\n"); + } + + } else { + + printf("Read failed.\n"); + ExitProcess(1); + } + } + ExitThread(0); +} + +DWORD WINAPI +WriteThreadProc( + LPVOID lpParameter + ) +{ + ULONG bytes; + PTHREAD_CONTEXT Context = (PTHREAD_CONTEXT)lpParameter; + + while(!g_TimeUp) { + + if(WriteFile(Context->hDevice, + Context->Buffer, + Context->BufferSize, + &bytes, + NULL)) { + + if (!Context->quite) { + printf("Write sucessful.\n"); + } + + } else { + + printf("Write failed.\n"); + ExitProcess(1); + } + } + ExitThread(0); +} + +int __cdecl +main( + _In_ int argc, + _In_reads_(argc) char* argv[] + ) +{ + PLX Plx; + BOOL status = TRUE; + ULONG test = MENU_TEST; + + if (!Plx.Initialize()) { + printf("Failied to Initialize Test class.\n"); + printf("exit(%u)\n", Plx.Status); + exit(Plx.Status); + } + + if(argc > 1) { + + for(int i=1; (i < argc) && status; i++) { + + char delims[] = "-/="; + char delims2[] = "="; + char *command; + char *data; + char *state = NULL; + + data = NULL; + + #pragma prefast(suppress:6385, "i < argc-1 before it is incremented below"); + command = strtok_s(argv[i], delims, &state); + if(command == NULL) { + status = FALSE; + break; + } + + if(strcmp(command, "rb") == 0) { + + data = strtok_s(NULL, delims2, &state); + + if (!data && i < argc-1) { + data = argv[++i]; + } + + ULONG size = atol(data); + if (size > 0) { + Plx.SetReadBufferSize(size); + } else { + status = FALSE; + } + + } else if(strcmp(command, "wb") == 0) { + + ULONG size = 0; + data = strtok_s(NULL, delims2, &state); + + if (!data && i < argc-1) { + data = argv[++i]; + } + if (data) { + size = atol(data); + } + + if (size > 0) { + Plx.SetWriteBufferSize(size); + } else { + status = FALSE; + } + + } else if (strcmp(command, "bs") == 0) { + data = strtok_s(NULL, delims2, &state); + + if (!data && i < argc-1) { + data = argv[++i]; + } + + ULONG size = atol(data); + if(size > 0) { + Plx.SetWriteBufferSize(size); + } else { + status = FALSE; + } + + } else if(strcmp(command, "wt") == 0) { + + test = WRITE_TEST; + + } else if(strcmp(command, "rt") == 0) { + + test = READ_TEST; + + } else if(strcmp(command, "quite") == 0) { + + Plx.Quite = TRUE; + + } else if(strcmp(command, "thread") == 0) { + + test = THREAD_TEST; + + } else if (strcmp(command, "time") == 0) { + + data = strtok_s(NULL, delims, &state); + + if(!data && i < argc-1) { + #pragma prefast(suppress:6385, "i < argc-1 before it is incremented"); + data = argv[++i]; + } + ULONG size = (NULL != data) ? atol(data) : 0; + Plx.SetThreadLifeTime(size); + + } else { + status = FALSE; + } + + if (!Plx.Quite) { + if (data) { + printf("Arg %d: Command: %s Parameter: %s\n", i, command, data); + } else { + printf("Arg %d: Command: %s\n", i, command); + } + } + } + } + + if (status) { + + switch (test) { + case READ_TEST: + Plx.ReadTest(); + break; + + case WRITE_TEST: + Plx.WriteTest(); + break; + + case READ_WRITE_TEST: + Plx.ReadWriteTest(); + break; + + case THREAD_TEST: + Plx.ThreadedReadWriteTest(); + break; + + case MENU_TEST: + default: + Plx.Menu(); + } + + } else { + + printf("Invalid command line parameter.\n"); + Plx.Status = 1; + } + + printf("exit(%u)\n", Plx.Status); + + exit( Plx.Status ); +} + +PLX::PLX() +{ + ReadBuffer = WriteBuffer = NULL; + hDevInfo = pDeviceInterfaceDetail = NULL; + hDevice = INVALID_HANDLE_VALUE; + console = TRUE; + Contexts = NULL; + Threads = NULL; + ProcessorCount = 0; + CSInitialized = FALSE; + ThreadTimer = 5000; // 5000 milliseconds (5 seconds) + + Quite = FALSE; + Status = 0; +} + +PLX::~PLX() +{ + + if (CSInitialized) { + DeleteCriticalSection(&CriticalSection); + } + if (hDevInfo) { + SetupDiDestroyDeviceInfoList(hDevInfo); + } + + if (pDeviceInterfaceDetail) { + free(pDeviceInterfaceDetail); + } + + if (Contexts) { + _Analysis_assume_(ProcessorCount <= ThreadCount); + for(int i = 0; i < ProcessorCount; i++) { + if (Contexts[i].Buffer) { + delete Contexts[i].Buffer; + } + } + + delete Contexts; + Contexts = NULL; + } + + if (hDevice != INVALID_HANDLE_VALUE) { + CloseHandle(hDevice); + hDevice = INVALID_HANDLE_VALUE; + } +} + +BOOL +PLX::Initialize() +{ + BOOL retValue = TRUE; + + retValue = SetBufferSizes(DEFAULT_READ_BUFFER_SIZE); + if (!retValue) { + return retValue; + } + + retValue = GetDevicePath(); + if (!retValue) { + return retValue; + } + + if (!CSInitialized) { + retValue = InitializeCriticalSectionAndSpinCount(&CriticalSection, SPIN_COUNT_FOR_CS); + if (!retValue) { + printf("InitializeCritialSection failed.\n"); + Status = GetLastError(); + return retValue; + } + CSInitialized = TRUE; + } + + return retValue; +} + +void +PLX::Menu() +{ + int menu = -1; + + while(menu != 0 && pDeviceInterfaceDetail) { + printf("\n" + " 1- Read from device\n" + " 2- Write from device\n" + " 3- Read/Write from device\n" + " 4- Read/Write Thread Test\n" + " 5- Select new device\n" + " 6- Change Buffer Size\n" + " 7- Compare Read/Write Buffers\n" + " 8- Display Read/Write Buffers\n" + " 9- Change Thread Lifetime\n" + "10- Command Line Options\n" + " 0- Quit\n"); + + if (scanf_s("%d", &menu) == 0) { + break; + } + + switch(menu) { + case READ_TEST: // 1 + ReadTest(); + break; + + case WRITE_TEST: // 2 + WriteTest(); + break; + + case READ_WRITE_TEST: // 3 + ReadWriteTest(); + break; + + case THREAD_TEST: // 4 + ThreadedReadWriteTest(); + break; + + case DEVICE_PATH: // 5 + GetDevicePath(); + break; + + case SET_SIZE: // 6 + ULONG size; + printf("\nEnter new buffer size: "); + if (scanf_s("%u", &size) != 0) { + SetBufferSizes(size); + } + break; + + case COMPARE_BUFFERS: // 7 + CompareReadWriteBuffers(); + break; + + case DISPLAY_BUFFERS: // 8 + DisplayReadWriteBuffers(); + break; + + case THREAD_TIME: // 9 + printf("\nEnter new Thread Lifetime (ms): "); + if (scanf_s("%u", &ThreadTimer) == 0) { + break; + } + break; + + case COMMAND_LINE: // 10 + printf("Command Line Options\n" + " Set Read Buffer Size: '/rb=xx'\n" + " Set Write Buffer Size: '/wb=xx'\n" + " Set Both Buffer Sizes: '/bs=xx'\n" + " Perform Write Test: '/wt'\n" + " Perform Read Test: '/rt'\n" + " Perform Read/Write Test: '/wr'\n" + " Perform Read/Write Thread Test: '/thread'\n"); + break; + + default: + break; + } + } +} + +BOOL +PLX::ThreadedReadWriteTest() +{ + BOOL status = TRUE; + DWORD_PTR pAffinity, sAffinity; + int i; + + HANDLE hThread; + HANDLE hProcess = GetCurrentProcess(); + + GetProcessAffinityMask(hProcess, &pAffinity, &sAffinity); + ProcessorCount = 0; + + while(pAffinity) { + ProcessorCount++; + pAffinity = pAffinity >> 1; + } + + if (ProcessorCount == 1) { + ThreadCount = DEFAULT_THREAD_COUNT; + } else { + ThreadCount = ProcessorCount; + } + + Contexts = new THREAD_CONTEXT[ThreadCount]; + Threads = new HANDLE[ThreadCount]; + + if (Contexts == NULL || Threads == NULL) { + return FALSE; + } + + if (hDevice == INVALID_HANDLE_VALUE) { + status = GetDeviceHandle(); + } + + if (!Quite) { + printf("Creating %d threads...\n", ThreadCount); + } + + pAffinity = 1; + + for(i = 0; i < ThreadCount; i++) { + + if ((i % 2) == 0) { + + // + // Create Read Thread + // + Contexts[i].hDevice = hDevice; + Contexts[i].BufferSize = ReadBufferSize; + Contexts[i].Buffer = new UCHAR[ReadBufferSize]; + Contexts[i].quite = Quite; + + hThread = CreateThread(NULL, + 0, + ReadThreadProc, + &Contexts[i], + 0, + NULL); + + if (NULL == hThread) { + printf( "Failed to create thread %d\n", i ); + this->Status = 1; + break; + + } else { + Threads[i] = hThread; + } + + } else { + + // + // Create Write Thread + // + Contexts[i].hDevice = hDevice; + Contexts[i].BufferSize = WriteBufferSize; + Contexts[i].Buffer = new UCHAR[WriteBufferSize]; + Contexts[i].quite = Quite; + + hThread = CreateThread(NULL, + 0, + WriteThreadProc, + &Contexts[i], + 0, + NULL); + + if (NULL == hThread) { + printf( "Failed to create thread %d\n", i ); + this->Status = 1; + break; + + } else { + Threads[i] = hThread; + } + } + + // + // Set Affinity + // + SetThreadAffinityMask(Threads[i], pAffinity); + + pAffinity = pAffinity << 1; + } + + if (i != ThreadCount) { + + // + // some create thread failed, bail out + // + printf( "Some CreateThread was failed, stop\n" ); + g_TimeUp = 1; + + } else { + + // + // wait till either all quit or time is due + // + DWORD error; + + error = WaitForMultipleObjects(i, Threads, TRUE, ThreadTimer); + + if (error == WAIT_TIMEOUT) { + + // + // Stop the threads if time is up + // + g_TimeUp = 2; + error = WaitForMultipleObjects(i, Threads, TRUE, 100000) ; + + if (error) { + printf("WaitForMultipleObjects[%d] error %u\n", i, error); + } + } else { + if (error) { + printf("WaitForMultipleObjects[%d] error %u\n", i, error); + } + } + } + + if (Contexts) { + for (i = 0; i < ThreadCount; i++) { + if (Contexts[i].Buffer) { + delete Contexts[i].Buffer; + } + } + + delete Contexts; + Contexts = NULL; + } + + if (hDevice != INVALID_HANDLE_VALUE) { + CloseHandle(hDevice); + hDevice = INVALID_HANDLE_VALUE; + } + + return status; +} + +BOOL +PLX::ReadTest() +{ + BOOL status = TRUE; + ULONG bytes = 0; + + if (!ReadBuffer) { + status = FALSE; + } + + if ((status == TRUE) && (hDevice == INVALID_HANDLE_VALUE)) { + status = GetDeviceHandle(); + } + + if (status) { + if (ReadFile(hDevice, + ReadBuffer, + ReadBufferSize, + &bytes, + NULL)){ + + EnterCriticalSection(&CriticalSection); + if (!Quite) { + printf("Read sucessful.\n"); + } + LeaveCriticalSection(&CriticalSection); + + } else { + + EnterCriticalSection(&CriticalSection); + printf("Read failed.\n"); + this->Status = 1; + LeaveCriticalSection(&CriticalSection); + } + } + + if (hDevice != INVALID_HANDLE_VALUE) { + CloseHandle(hDevice); + hDevice = INVALID_HANDLE_VALUE; + } + + return status; +} + +BOOL +PLX::WriteTest() +{ + ULONG bytes = 0; + BOOL status = TRUE; + + if (!WriteBuffer) { + status = FALSE; + } + + if ((status == TRUE) && (hDevice == INVALID_HANDLE_VALUE)) { + status = GetDeviceHandle(); + } + + if (status) { + FillMemory(WriteBuffer, WriteBufferSize, 0xAB); + + if (WriteFile(hDevice, + WriteBuffer, + WriteBufferSize, + &bytes, + NULL)) { + + EnterCriticalSection(&CriticalSection); + if (!Quite) { + printf("Write sucessful.\n"); + } + LeaveCriticalSection(&CriticalSection); + + } else { + + EnterCriticalSection(&CriticalSection); + printf("Write failed.\n"); + this->Status = 1; + LeaveCriticalSection(&CriticalSection); + } + } + + if (hDevice != INVALID_HANDLE_VALUE) { + CloseHandle(hDevice); + hDevice = INVALID_HANDLE_VALUE; + } + + return status; +} + +BOOL +PLX::ReadWriteTest() +{ + return (WriteTest() && ReadTest() && CompareReadWriteBuffers()); +} + +BOOL +PLX::CompareReadWriteBuffers() +{ + BOOL status = TRUE; + ULONG size; + PUCHAR WTraverse; + PUCHAR RTraverse; + + if (ReadBufferSize <= WriteBufferSize) { + size = ReadBufferSize; + } else { + size = WriteBufferSize; + } + + WTraverse = WriteBuffer; + RTraverse = ReadBuffer; + + for(ULONG i = 0; i < size; i++) { + if (*WTraverse++ != *RTraverse++) { + status = FALSE; + } + } + + if (status) { + if (!Quite) { + printf("Buffers are identical\n"); + } + } else { + printf("Buffers not identical\n"); + this->Status = 1; + } + + return status; +} + +void +PLX::DisplayReadWriteBuffers() +{ + if (!Quite) { + + PUCHAR WTraverse; + PUCHAR RTraverse; + + WTraverse = WriteBuffer; + RTraverse = ReadBuffer; + + printf("Write: "); + for(ULONG i = 0; i < WriteBufferSize; i++) { + printf("%X ", *WTraverse++); + } + + printf("\n\n\nRead: "); + for(ULONG i = 0; i < ReadBufferSize; i++) { + printf("%X ", *RTraverse++); + } + printf("\n"); + } +} + +BOOL +PLX::SetReadBufferSize(ULONG size) +{ + BOOL status = TRUE; + + if (ReadBuffer) { + free(ReadBuffer); + } + + ReadBufferSize = size; + + ReadBuffer = (PUCHAR)malloc(ReadBufferSize); + + if (!ReadBuffer) { + status = FALSE; + } + + return status; +} + +BOOL +PLX::SetWriteBufferSize(ULONG size) +{ + BOOL status = TRUE; + + if (WriteBuffer) { + free(WriteBuffer); + } + + WriteBufferSize = size; + + WriteBuffer = (PUCHAR)malloc(WriteBufferSize); + + if (!WriteBuffer) { + status = FALSE; + } + + return status; +} + +BOOL +PLX::SetBufferSizes(ULONG size) +{ + BOOL status; + + status = SetReadBufferSize(size); + if (status) { + status = SetWriteBufferSize(size); + } + + return status; +} + +BOOL +PLX::GetDevicePath() +{ + SP_DEVICE_INTERFACE_DATA DeviceInterfaceData; + SP_DEVINFO_DATA DeviceInfoData; + + ULONG size; + int count, i, index; + BOOL status = TRUE; + TCHAR *DeviceName = NULL; + TCHAR *DeviceLocation = NULL; + + // + // Retreive the device information for all PLX devices. + // + hDevInfo = SetupDiGetClassDevs(&GUID_PLX_INTERFACE, + NULL, + NULL, + DIGCF_DEVICEINTERFACE | + DIGCF_PRESENT); + + // + // Initialize the SP_DEVICE_INTERFACE_DATA Structure. + // + DeviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + // + // Determine how many devices are present. + // + count = 0; + while(SetupDiEnumDeviceInterfaces(hDevInfo, + NULL, + &GUID_PLX_INTERFACE, + count++, //Cycle through the available devices. + &DeviceInterfaceData) + ); + + // + // Since the last call fails when all devices have been enumerated, + // decrement the count to get the true device count. + // + count--; + + // + // If the count is zero then there are no devices present. + // + if (count == 0) { + printf("No PLX devices are present and enabled in the system.\n"); + this->Status = 1; + return FALSE; + } + + // + // Initialize the appropriate data structures in preparation for + // the SetupDi calls. + // + DeviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA); + + // + // Loop through the device list to allow user to choose + // a device. If there is only one device, select it + // by default. + // + i = 0; + while (SetupDiEnumDeviceInterfaces(hDevInfo, + NULL, + (LPGUID)&GUID_PLX_INTERFACE, + i, + &DeviceInterfaceData)) { + + // + // Determine the size required for the DeviceInterfaceData + // + SetupDiGetDeviceInterfaceDetail(hDevInfo, + &DeviceInterfaceData, + NULL, + 0, + &size, + NULL); + + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + printf("SetupDiGetDeviceInterfaceDetail failed, Error: %u", GetLastError()); + this->Status = 1; + return FALSE; + } + + pDeviceInterfaceDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA) malloc(size); + + if (!pDeviceInterfaceDetail) { + printf("Insufficient memory.\n"); + this->Status = 1; + return FALSE; + } + + // + // Initialize structure and retrieve data. + // + pDeviceInterfaceDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + status = SetupDiGetDeviceInterfaceDetail(hDevInfo, + &DeviceInterfaceData, + pDeviceInterfaceDetail, + size, + NULL, + &DeviceInfoData); + + free(pDeviceInterfaceDetail); + + if (!status) { + printf("SetupDiGetDeviceInterfaceDetail failed, Error: %u", GetLastError()); + this->Status = 1; + return status; + } + + // + // Get the Device Name + // Calls to SetupDiGetDeviceRegistryProperty require two consecutive + // calls, first to get required buffer size and second to get + // the data. + // + SetupDiGetDeviceRegistryProperty(hDevInfo, + &DeviceInfoData, + SPDRP_DEVICEDESC, + NULL, + (PBYTE)DeviceName, + 0, + &size); + + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + printf("SetupDiGetDeviceRegistryProperty failed, Error: %u", GetLastError()); + this->Status = 1; + return FALSE; + } + + DeviceName = (TCHAR*) malloc(size); + if (!DeviceName) { + printf("Insufficient memory.\n"); + this->Status = 1; + return FALSE; + } + + status = SetupDiGetDeviceRegistryProperty(hDevInfo, + &DeviceInfoData, + SPDRP_DEVICEDESC, + NULL, + (PBYTE)DeviceName, + size, + NULL); + if (!status) { + printf("SetupDiGetDeviceRegistryProperty failed, Error: %u", + GetLastError()); + free(DeviceName); + this->Status = 1; + return status; + } + + // + // Now retrieve the Device Location. + // + SetupDiGetDeviceRegistryProperty(hDevInfo, + &DeviceInfoData, + SPDRP_LOCATION_INFORMATION, + NULL, + (PBYTE)DeviceLocation, + 0, + &size); + + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + DeviceLocation = (TCHAR*) malloc(size); + + if (DeviceLocation != NULL) { + + status = SetupDiGetDeviceRegistryProperty(hDevInfo, + &DeviceInfoData, + SPDRP_LOCATION_INFORMATION, + NULL, + (PBYTE)DeviceLocation, + size, + NULL); + if (!status) { + free(DeviceLocation); + DeviceLocation = NULL; + } + } + + } else { + DeviceLocation = NULL; + } + + // + // If there is more than one device print description. + // + if (count > 1 && console) { + printf("%d- ", i); + } + + printf("%s\n", DeviceName); + + if (DeviceLocation) { + printf(" %s\n", DeviceLocation); + } + + free(DeviceName); + DeviceName = NULL; + + if (DeviceLocation) { + free(DeviceLocation); + DeviceLocation = NULL; + } + + i++; // Cycle through the available devices. + } + + // + // Select device. + // + index = 0; + if (count > 1) { + printf("\nSelect Device: "); + + if (scanf_s("%d", &index) == 0) { + return ERROR_INVALID_DATA; + } + } + + // + // Get information for specific device. + // + status = SetupDiEnumDeviceInterfaces(hDevInfo, + NULL, + (LPGUID)&GUID_PLX_INTERFACE, + index, + &DeviceInterfaceData); + + if (!status) { + printf("SetupDiEnumDeviceInterfaces failed, Error: %u", GetLastError()); + return status; + } + + // + // Determine the size required for the DeviceInterfaceData + // + SetupDiGetDeviceInterfaceDetail(hDevInfo, + &DeviceInterfaceData, + NULL, + 0, + &size, + NULL); + + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + printf("SetupDiGetDeviceInterfaceDetail failed, Error: %u", GetLastError()); + this->Status = 1; + return FALSE; + } + + pDeviceInterfaceDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA) malloc(size); + + if (!pDeviceInterfaceDetail) { + printf("Insufficient memory.\n"); + this->Status = 1; + return FALSE; + } + + // + // Initialize structure and retrieve data. + // + pDeviceInterfaceDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + status = SetupDiGetDeviceInterfaceDetail(hDevInfo, + &DeviceInterfaceData, + pDeviceInterfaceDetail, + size, + NULL, + &DeviceInfoData); + if (!status) { + printf("SetupDiGetDeviceInterfaceDetail failed, Error: %u", GetLastError()); + this->Status = 1; + return status; + } + + return status; +} + +BOOL +PLX::GetDeviceHandle() +{ + BOOL status = TRUE; + + if (pDeviceInterfaceDetail == NULL) { + status = GetDevicePath(); + } + if (pDeviceInterfaceDetail == NULL) { + status = FALSE; + } + + if (status) { + + // + // Get handle to device. + // + hDevice = CreateFile(pDeviceInterfaceDetail->DevicePath, + GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + 0, + NULL); + + if (hDevice == INVALID_HANDLE_VALUE) { + status = FALSE; + printf("CreateFile failed. Error:%u", GetLastError()); + this->Status = 1; + } + } + + return status; +} + +void +PLX::SetThreadLifeTime(ULONG time) +{ + ThreadTimer = time; +} + |
