/*********************************************************************** Copyright (c) Microsoft Corporation All Rights Reserved. ***********************************************************************/ // This source code is an intended supplement to the Microsoft SQL // Server online references and related electronic documentation. // // This sample is for instructional purposes only. // Code contained herein is not intended to be used "as is" in real applications. // // mthread.cpp : // // Test & demonstrate the use of multiple streams from a single process. // // This is a sample program used to demonstrate the Virtual Device Interface // feature of Microsoft SQL Server. // // The program will backup or restore the 'pubs' sample database. // // The program requires two command line parameters. // 1) // One of: // b perform a backup // r perform a restore // // 2) // The number of streams to use, 1-32. // #define _WIN32_DCOM #include // for 'CoInitialize()' #include // for file operations #include // for toupper () #include // for C library-safe _beginthreadex,_endthreadex #include #include "vdi.h" // interface declaration #include "vdierror.h" // error constants #include "vdiguid.h" // define the GUIDs void LogError ( LPSTR location, // must always be provided LPSTR description, // NULL is acceptable DWORD errCode); // windows status code int performTransfer ( IClientVirtualDevice* vd, int backup, int streamId); HANDLE execSQL (int doBackup, int nStreams); int startSecondaries( IClientVirtualDeviceSet *vds, HANDLE hSQLProcess, // handle to process dealing with the SQL int nStreams); // number of i/o streams unsigned __stdcall runSecondary (void *parms); // Using a GUID for the VDS Name is a good way to assure uniqueness. // WCHAR wVdsName [50]; // // main function // int main(int argc, char *argv[]) { HRESULT hr; IClientVirtualDeviceSet* vds = NULL ; VDConfig config; int badParm=TRUE; int doBackup; HANDLE hProcess; int termCode = -1; int nStreams=1; // Check the input parm // if (argc == 3) { sscanf (argv[2], "%d", &nStreams); switch (toupper(argv[1][0])) { case 'B': doBackup = TRUE; badParm = FALSE; break; case 'R': doBackup = FALSE; badParm = FALSE; break; } } if (badParm) { printf ("useage: mprocess {B|R} \n" "Demonstrate a multistream Backup or Restore using the Virtual Device Interface\n"); exit (1); } // 1..32 streams. // if (nStreams < 1) nStreams = 1; else if (nStreams > 32) nStreams = 32; printf ("Performing a %s using %d virtual device(s).\n", (doBackup) ? "BACKUP" : "RESTORE", nStreams); // Initialize COM Library // Note: _WIN32_DCOM must be defined during the compile. // hr = CoInitializeEx (NULL, COINIT_MULTITHREADED); if (!SUCCEEDED (hr)) { printf ("Coinit fails: x%X\n", hr); exit (1); } // Get an interface to the device set. // Notice how we use a single IID for both the class and interface // identifiers. // hr = CoCreateInstance ( IID_IClientVirtualDeviceSet, NULL, CLSCTX_INPROC_SERVER, IID_IClientVirtualDeviceSet, (void**)&vds); if (!SUCCEEDED (hr)) { // This failure might happen if the DLL was not registered. // printf ("Could not create component: x%X\n", hr); printf ("Check registration of SQLVDI.DLL and value of IID\n"); goto exit; } // Create a GUID to use for a unique virtual device name // GUID vdsId; CoCreateGuid (&vdsId); StringFromGUID2 (vdsId, wVdsName, 49); // Setup the VDI configuration we want to use. // This program doesn't use any fancy features, so the // only field to setup is the deviceCount. // // The server will treat the virtual device just like a pipe: // I/O will be strictly sequential with only the basic commands. // memset (&config, 0, sizeof(config)); config.deviceCount = nStreams; // Create the virtual device set // hr = vds->Create (wVdsName, &config); if (!SUCCEEDED (hr)) { printf ("VDS::Create fails: x%X", hr); goto exit; } // Send the SQL command, via isql in a subprocess. // printf("\nSending the SQL...\n"); hProcess = execSQL (doBackup, nStreams); if (hProcess == NULL) { printf ("execSQL failed.\n"); goto shutdown; } // Wait for the server to connect, completing the configuration. // Notice that we wait a maximum of 15 seconds. // printf("\nWaiting for SQL to complete configuration...\n"); hr = vds->GetConfiguration (15000, &config); if (!SUCCEEDED (hr)) { printf ("VDS::Getconfig fails: x%X\n", hr); goto shutdown; } // Handle the virtual devices in secondary processes. // printf ("\nSpawning secondary threads..\n"); termCode = startSecondaries (vds, hProcess, nStreams); shutdown: // Close the set // vds->Close (); // COM reference counting: Release the interface. // vds->Release () ; exit: // Uninitialize COM Library // CoUninitialize () ; return termCode; } // // Execute a basic backup/restore, by starting 'isql' in a subprocess. // // Returns: // NULL : failed to start isql // else : process handle // HANDLE execSQL (int doBackup, int nStreams) { char cmd[5000]; char extend[100]; PROCESS_INFORMATION pi; STARTUPINFO si; int ix; sprintf (cmd, "osql -E -b -Q\"%s DATABASE PUBS %s VIRTUAL_DEVICE='%ls'", (doBackup) ? "BACKUP" : "RESTORE", (doBackup) ? "TO" : "FROM", wVdsName); for (ix=1; ix= WAIT_OBJECT_0+nActive) { LogError ("startSecondary", "WaitForMultiple", GetLastError ()); printf("Unexpected wait code: %d\n", waitStatus); goto errorExit; } // All of the children have completed. // Get the completion code from 'isql' to check for sucess. // if (!GetExitCodeProcess (hSQLProcess, &exitCode)) { LogError ("startSecondary", "GetExitCode", GetLastError ()); goto errorExit; } if (exitCode != 0) { printf ("The SQL operation failed with code %d\n", exitCode); goto errorExit; } printf ("The SQL operation was sucessful.\n"); // Be sure to close handles when finished with them. // // Notice that in our trivial error handling here we // don't bother closing them, since handles are // automatically closed as part of process termination. // for (ix = 0; ix < nActive; ix++) { CloseHandle (children[ix]); } return 0; errorExit: // Handle all problems in a trivial fashion: // SignalAbort() will cause all processes using the virtual device set // to terminate processing. // vds->SignalAbort (); // However, since the threads are using the virtual device set allocated // by the main thread, we can't let the main thread close the set before // the threads are finished with it. There are two options: // 1) exit the process immediately. // 2) wait for the threads to terminate. // // Option 2 is "cleaner" but requires more code, so we just exit here: // ExitProcess ((unsigned)-1); return -1; // ExitProcess doesn't return; this avoids a compiler error. } //------------------------------------------------------------------ // Perform secondary client processing from within a thread. // Exits with 0 if no errors detected, else nonzero. // The caller must setup a THREAD_PARMS parameter block when // spawning the thread. // unsigned __stdcall runSecondary (void *parms) { HRESULT hr; WCHAR devName[100]; IClientVirtualDevice* vd; VDConfig config; int termCode; // Fetch the input parms // int streamId = ((THREAD_PARMS*)parms)->streamId; IClientVirtualDeviceSet * vds = ((THREAD_PARMS*)parms)->vds; // Build the name of the device assigned to this thread. // if (streamId == 0) { // The first device has the same name as the set. // wcscpy (devName, wVdsName); } else { // For this example, we've simply appended a number // for additional devices. You are free to name them // as you wish. // swprintf (devName, L"%s%d", wVdsName, streamId); } // Open the device assigned to this thread. // hr = vds->OpenDevice (devName, &vd); if (!SUCCEEDED (hr)) { printf ("OpenDevice fails on %ls: x%X", devName, hr); termCode = -1; goto errExit; } // Grab the config to figure out data direction // hr = vds->GetConfiguration (INFINITE, &config); if (!SUCCEEDED (hr)) { printf ("VDS::Getconfig fails: x%X\n", hr); termCode = -1; goto errExit; } printf ("\nPerforming data transfer...\n"); termCode = performTransfer (vd, (config.features&VDF_WriteMedia), streamId); errExit: // If errors were detected, force an abort. // if (termCode != 0) { vds->SignalAbort (); } return ((unsigned)termCode); } // This routine reads commands from the server until a 'Close' status is received. // It simply synchronously reads or writes a file on the root of the current drive. // // Returns 0, if no errors are detected, else non-zero. // int performTransfer ( IClientVirtualDevice* vd, int backup, int streamId) { FILE * fh; char fname[80]; VDC_Command * cmd; DWORD completionCode; DWORD bytesTransferred; HRESULT hr; int termCode = -1; sprintf (fname, "multi.%d.dmp", streamId); fh = fopen (fname, (backup)? "wb" : "rb"); if (fh == NULL ) { printf ("Failed to open: %s\n", fname); return -1; } while (SUCCEEDED (hr=vd->GetCommand (INFINITE, &cmd))) { bytesTransferred = 0; switch (cmd->commandCode) { case VDC_Read: bytesTransferred = fread (cmd->buffer, 1, cmd->size, fh); if (bytesTransferred == cmd->size) completionCode = ERROR_SUCCESS; else // assume failure is eof completionCode = ERROR_HANDLE_EOF; break; case VDC_Write: bytesTransferred = fwrite (cmd->buffer, 1, cmd->size, fh); if (bytesTransferred == cmd->size ) { completionCode = ERROR_SUCCESS; } else // assume failure is disk full completionCode = ERROR_DISK_FULL; break; case VDC_Flush: fflush (fh); completionCode = ERROR_SUCCESS; break; case VDC_ClearError: completionCode = ERROR_SUCCESS; break; default: // If command is unknown... completionCode = ERROR_NOT_SUPPORTED; } hr = vd->CompleteCommand (cmd, completionCode, bytesTransferred, 0); if (!SUCCEEDED (hr)) { printf ("Completion Failed: x%X\n", hr); break; } } if (hr != VD_E_CLOSE) { printf ("Unexpected termination: x%X\n", hr); } else { // As far as the data transfer is concerned, no // errors occurred. The code which issues the SQL // must determine if the backup/restore was // really successful. // printf ("Successfully completed data transfer.\n"); termCode = 0; } fclose (fh); return termCode; } //-------------------------------------------------------------------- // // A simple error logger. // void LogError ( LPSTR location, // must always be provided LPSTR description, // NULL is acceptable DWORD errCode) // windows status code { LPVOID lpMsgBuf; printf ( "Error at %s: %s StatusCode: %X\n", location, (description==NULL)?"":description, errCode); // Attempt to explain the code // if (errCode != 0 && FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR) &lpMsgBuf, 0, NULL ) )// Process any inserts in lpMsgBuf. { printf ("Explanation: %s\n", lpMsgBuf); LocalFree( lpMsgBuf ); } }