Click here to Skip to main content
15,892,746 members
Articles / Database Development / SQL Server

Accessing the SQL Server Virtual Device Interface via .NET (C#)

Rate me:
Please Sign up or sign in to vote.
4.76/5 (15 votes)
3 Jul 2007CPOL10 min read 102.2K   1.1K   35  
This article is an introduction to the SQL Server Virtual Device Interface and how to access it via any .NET language
//*****************************************************************************
// Copyright � 2007, Steve Abraham
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer. 
//
// Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution. 
//
// Neither the name of the ORGANIZATION nor the names of its contributors may
// be used to endorse or promote products derived from this software without
// specific prior written permission. 
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//*****************************************************************************

#pragma once
#define _WIN32_DCOM
#include "vdi.h"
#include "vdierror.h"
#include "vdiguid.h"

using namespace System;
using namespace System::Data;
using namespace System::Data::Odbc;
using namespace System::Globalization;
using namespace System::IO;
using namespace System::Runtime::InteropServices;
using namespace System::Threading;

namespace VdiDotNet {

	public ref class InfoMessageEventArgs : EventArgs
	{
	private: String^ _Message;
	public: InfoMessageEventArgs(String^ message)
			 {
				 if (String::IsNullOrEmpty(message))
				 {
					 message = String::Empty;
				 }
				 else
				 {
					 if (message->StartsWith("[Microsoft][ODBC SQL Server Driver][SQL Server]"))
					 {
						 message = message->Replace("[Microsoft][ODBC SQL Server Driver][SQL Server]", String::Empty);
					 }
				 }

				 this->_Message = message;
			 }
	private: ~InfoMessageEventArgs()
			 {
				 delete (this->_Message);
			 }
	public: property String^ Message
			{
				String^ get() { return this->_Message; }
			}
	};
	public ref class CommandIssuedEventArgs : EventArgs
	{
	private: String^ _Command;
	public: CommandIssuedEventArgs(String^ message)
			 {
				 this->_Command = message;
			 }
	private: ~CommandIssuedEventArgs()
			 {
				 delete (this->_Command);
			 }
	public: property String^ Command
			{
				String^ get() { return this->_Command; }
			}
	};

	
	public ref class VdiEngine
	{
		//Delegates & Events
		public: event EventHandler<InfoMessageEventArgs^>^ InfoMessageReceived;
		public: event EventHandler<CommandIssuedEventArgs^>^ CommandIssued;
		
		private: Void SqlServerConnection_InfoMessage(Object^ sender, OdbcInfoMessageEventArgs^ e)
				{
					VdiDotNet::InfoMessageEventArgs^  i = gcnew VdiDotNet::InfoMessageEventArgs(e->Message);
					InfoMessageReceived(this, i);
				}
		private: Void ThreadFunc(Object^ data)
		{
			//Create and configure an ODBC connection to the local SQL Server
			OdbcConnection^ SqlServerConnection = gcnew OdbcConnection("Driver={SQL Server};Server=(local);Trusted_Connection=Yes;");
			SqlServerConnection->InfoMessage += gcnew OdbcInfoMessageEventHandler(this, &VdiDotNet::VdiEngine::SqlServerConnection_InfoMessage);

			//Create and configure the command to be issued to SQL Server
			OdbcCommand^ SqlServerCommand = gcnew OdbcCommand(data->ToString(), SqlServerConnection);
			SqlServerCommand->CommandType = CommandType::Text;
			SqlServerCommand->CommandTimeout = 0;

			//Notify the user of the command issued
			CommandIssued(this, gcnew CommandIssuedEventArgs(data->ToString()));

			//Open the connection
			SqlServerConnection->Open();

			//Execute the command
			SqlServerCommand->ExecuteNonQuery();
		}


		private: static Void ExecuteDataTransfer (IClientVirtualDevice* vd, Stream^ s)
		{
			//Declare Local Variables
			VDC_Command *   cmd;
			DWORD           completionCode;
			DWORD           bytesTransferred;
			HRESULT         hr;

			while (SUCCEEDED(hr = vd->GetCommand(INFINITE, &cmd)))
			{
				array<System::Byte>^ arr = gcnew array<System::Byte>(cmd->size);
				bytesTransferred = 0;
				switch (cmd->commandCode)
				{
					case VDC_Read:
						//Read the specified number of bytes from the stream
						bytesTransferred = s->Read(arr, bytesTransferred, cmd->size - bytesTransferred);

						//Copy the stream bytes to the cmd object
						Marshal::Copy(arr, 0, (IntPtr)cmd->buffer, cmd->size);

						//Set the completion code
						if (bytesTransferred == cmd->size)
						{
							completionCode = ERROR_SUCCESS;
						}
						else
						{
							completionCode = ERROR_READ_FAULT;
						}
						break;

					case VDC_Write:
						//Copy the data from the cmd object to a CLR array
						Marshal::Copy((IntPtr)cmd->buffer, arr, 0, cmd->size);

						//Write the data to the stream
						s->Write(arr, 0, cmd->size);
				
						//Set the number of bytes transferred
						bytesTransferred = cmd->size;

						//Set the completion code
						completionCode = ERROR_SUCCESS;
						break;

					case VDC_Flush:
						//Flush the stream
						s->Flush();

						//Set the completion code
						completionCode = ERROR_SUCCESS;
						break;
		    
					case VDC_ClearError:
						//Set the completion code
						completionCode = ERROR_SUCCESS;
						break;

					default:
						//Set the completion code
						completionCode = ERROR_NOT_SUPPORTED;
						break;
				}

				//Complete the command
				hr = vd->CompleteCommand(cmd, completionCode, bytesTransferred, 0);
				if (FAILED(hr))
				{
					throw gcnew ApplicationException(String::Format(gcnew CultureInfo("en-US"), "CompleteCommand Failed. HRESULT: {0}", hr));
				}
			}

			//If the command is not closed gracefully, throw an exception
			if (hr != VD_E_CLOSE)
			{
				switch (hr)
				{
					case VD_E_TIMEOUT:
						throw gcnew ApplicationException("GetCommand timed out.");
						break;
					case VD_E_ABORT:
						throw gcnew ApplicationException("GetCommand was aborted.");
						break;
					default:
						throw gcnew ApplicationException(String::Format(gcnew CultureInfo("en-US"), "GetCommand Failed. HRESULT: {0}", hr));
						break;
				};
			}
		}

		public: Void ExecuteCommand(System::String^ command, Stream^ commandStream)
		{
			//Initialize COM
			HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
			if (FAILED(hr))
			{
				throw gcnew ApplicationException(String::Format(gcnew CultureInfo("en-US"), "CoInitializeEx Failed HRESULT: {0}", hr));
			}

			//Get an interface to the virtual device set
			IClientVirtualDeviceSet2* vds = NULL;
			hr = CoCreateInstance(CLSID_MSSQL_ClientVirtualDeviceSet, NULL, CLSCTX_INPROC_SERVER, IID_IClientVirtualDeviceSet2, (void**)&vds);
			if (FAILED(hr))
			{
				throw gcnew ApplicationException(String::Format(gcnew CultureInfo("en-US"), "Unable to get an interface to the virtual device set.  Please check to make sure sqlvdi.dll is registered. HRESULT: {0}", hr));
			}

			//Configure the device configuration
			VDConfig config = {0};
			config.deviceCount = 1;	//The number of virtual devices to create

			//Create a name for the device using a GUID
			String^ DeviceName = System::Guid::NewGuid().ToString()->ToUpper(gcnew CultureInfo("en-US"));
			WCHAR wVdsName [37] = {0};
			Marshal::Copy(DeviceName->ToCharArray(), 0, (IntPtr)wVdsName, DeviceName->Length);

			//Create the virtual device set
			hr = vds->CreateEx (NULL, wVdsName, &config);
			if (FAILED(hr))
			{
				throw gcnew ApplicationException(String::Format(gcnew CultureInfo("en-US"), "Unable to create and configure the virtual device set. HRESULT: {0}", hr));
			}

			//Format the command
			command = String::Format(gcnew CultureInfo("en-US"), command, DeviceName);

			//Create and execute a new thread to execute the command
			Thread^ OdbcThread = gcnew Thread(gcnew ParameterizedThreadStart(this, &VdiDotNet::VdiEngine::ThreadFunc));
			OdbcThread->Start(command);

			//Configure the virtual device set
			hr = vds->GetConfiguration(INFINITE, &config);
			if (FAILED(hr))
			{
				switch (hr)
				{
					case VD_E_ABORT:
						throw gcnew ApplicationException("GetConfiguration was aborted.");
						break;
					case VD_E_TIMEOUT:
						throw gcnew ApplicationException("GetConfiguration timed out.");
						break;
					default:
						throw gcnew ApplicationException(String::Format(gcnew CultureInfo("en-US"), "Un unknown exception was thrown during GetConfiguration.  HRESULT: {0}", hr));
						break;
				};
			}

			//Open the one device on the device set
			IClientVirtualDevice* vd = NULL;
			hr = vds->OpenDevice(wVdsName, &vd);
			if (FAILED(hr))
			{
				throw gcnew ApplicationException(String::Format(gcnew CultureInfo("en-US"), "OpenDevice Failed.  HRESULT: {0}", hr));
			}

			//Execute the data transfer
			ExecuteDataTransfer(vd, commandStream);

			//Wait for the thread that issued the backup / restore command to SQL Server to complete.
			OdbcThread->Join();
		}


		public: VdiEngine()
		{
		}
	};
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder Mini Cities, Inc.
United States United States
SQL Server Expert, Steve Abraham, http://SQLSteve.com holds 8 Microsoft certifications with his claim to fame being he passed all but one of the certifications within the span of 19 days – passing all tests on the first attempt.

The SQL Server 2008 exam was also passed on the first attempt but at a later date.

SQL Steve has been developing SQL Server based software for 13 years. Steve specializes in SQL Server and .Net Framework architecture, high availability, capacity planning, development, and performance tuning.

Steve has lead teams for some of the biggest and best known companies many of them dealing with Terabytes of data AND in crisis situations - including the U. S. State Department, T-Mobile, USA, Eddie Bauer, 1-800-Flowers and Spiegel. In 2006, Steve co-founded MiniCities, a hyper-local search company, based on the HYPERLOCAL ENGINE (TM), offering the first hyper local web franchises.

Steve Abraham is available for consulting on short term and possibly long term projects.
Contact: Steve Abraham
Email: steve@minicities.com
Phone: 813.300.0165

Comments and Discussions