/// <summary>
 /// Sends the command to the driver and gets the response.
 /// </summary>
 /// <typeparam name="TResponse">Type of the response.</typeparam>
 /// <param name="command">Command to be sent to the driver.</param>
 /// <returns>Response received from the driver.</returns>
 /// <exception cref="ArgumentNullException"><paramref name="command"/> is <see langword="null"/>.</exception>
 /// <exception cref="ArgumentException"><typeparamref name="TResponse"/> type is not a structure type.</exception>
 /// <exception cref="InvalidOperationException">
 /// Memory for the command could not be allocated.
 ///     <para>-or-</para>
 /// Memory for the response could not be allocated.
 ///     <para>-or-</para>
 /// Message was not sent to the driver.
 /// </exception>
 /// <remarks>
 /// This method should be called from a synchronized context.
 /// </remarks>
 public TResponse ExecuteCommand <TResponse>(IDriverCommand command)
     where TResponse : struct
 {
     return((TResponse)this.ExecuteCommand(command, typeof(TResponse)));
 }
        /// <summary>
        /// Sends the command to the driver.
        /// </summary>
        /// <param name="command">Command to be sent to the driver.</param>
        /// <param name="responseType">Type of the response. This parameter may be <see langword="null"/>.</param>
        /// <returns>Response received from the driver, or <see langword="null"/>, if the <paramref name="responseType"/> is <see langword="null"/>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="command"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentException"><paramref name="responseType"/> is not a structure type.</exception>
        /// <exception cref="InvalidOperationException">
        /// Client is not connected.
        ///     <para>-or-</para>
        /// Memory for the command or response was not allocated.
        ///     <para>-or-</para>
        /// Message was not sent to the driver.
        /// </exception>
        private object ExecuteCommand(IDriverCommand command, Type responseType)
        {
            if (command == null)
            {
                throw new ArgumentNullException(nameof(command));
            }

            if (responseType != null && (!responseType.IsValueType || responseType.IsPrimitive))
            {
                throw new ArgumentException("Response type is not a structure type.", nameof(responseType));
            }

            lock (this.syncRoot)
            {
                if (this.state != ConnectionState.Connected)
                {
                    throw new InvalidOperationException("Client is not connected.");
                }

                // We want to have two separate buffers, because the driver can update the response buffer
                // while parsing the command.
                IntPtr commandBuffer  = IntPtr.Zero;
                IntPtr responseBuffer = IntPtr.Zero;

                try
                {
                    // Allocate buffer for the command.
                    // Command header contains 'Type' and 'DataLength' (Int32) values.
                    // See the 'DRIVER_COMMAND' structure in the 'CommunicationData.h' for details.
                    int commandHeaderSize = Marshal.SizeOf(command.Type) + Marshal.SizeOf(typeof(int));
                    int commandDataSize   = command.Data?.Length ?? 0;
                    int commandSize       = commandHeaderSize + commandDataSize;

                    try
                    {
                        commandBuffer = Marshal.AllocHGlobal(commandSize);
                    }
                    catch (OutOfMemoryException oom)
                    {
                        string message = "Unable to allocate memory for the command.";

                        DriverClientBase.Logger.Error(message, oom);
                        throw new InvalidOperationException(message, oom);
                    }

                    // Marshal command to the buffer allocated, if the command.Data is NULL, it'll be ignored.
                    MarshalingHelper.MarshalObjectsToPointer(commandBuffer, commandSize, command.Type, commandDataSize, command.Data);

                    //
                    // Allocate the response buffer, if needed.
                    //

                    int responseSize = 0;

                    if (responseType != null)
                    {
                        responseSize = Marshal.SizeOf(responseType);

                        try
                        {
                            responseBuffer = Marshal.AllocHGlobal(responseSize);
                        }
                        catch (OutOfMemoryException oom)
                        {
                            string message = "Unable to allocate memory for the response.";

                            DriverClientBase.Logger.Error(message, oom);
                            throw new InvalidOperationException(message, oom);
                        }
                    }

                    //
                    // Send command to the driver.
                    //

                    uint bytesReceived;
                    uint hr = NativeMethods.FilterSendMessage(this.filterPortHandle, commandBuffer, (uint)commandSize, responseBuffer, (uint)responseSize, out bytesReceived);
                    if (hr != NativeMethods.Ok)
                    {
                        string    message        = string.Format(CultureInfo.InvariantCulture, "Unable to send message to the driver: 0x{0:X8}", hr);
                        Exception innerException = Marshal.GetExceptionForHR((int)hr);

                        DriverClientBase.Logger.Error(innerException, message);
                        throw new InvalidOperationException(message, innerException);
                    }

                    // Return NULL, if response type is not specified.
                    return(responseType == null ? null : Marshal.PtrToStructure(responseBuffer, responseType));
                }
                catch
                {
                    this.Disconnect();
                    this.state = ConnectionState.Faulted;

                    throw;
                }
                finally
                {
                    if (commandBuffer != IntPtr.Zero)
                    {
                        Marshal.FreeHGlobal(commandBuffer);
                    }

                    if (responseBuffer != IntPtr.Zero)
                    {
                        Marshal.FreeHGlobal(responseBuffer);
                    }
                }
            }
        }
 /// <summary>
 /// Sends the command to the driver.
 /// </summary>
 /// <param name="command">Command to be sent to the driver.</param>
 /// <exception cref="ArgumentNullException"><paramref name="command"/> is <see langword="null"/>.</exception>
 /// <exception cref="InvalidOperationException">
 /// Memory for the command could not be allocated.
 ///     <para>-or-</para>
 /// Memory for the response could not be allocated.
 ///     <para>-or-</para>
 /// Message was not sent to the driver.
 /// </exception>
 /// <remarks>
 /// This method should be called from a synchronized context.
 /// </remarks>
 public void ExecuteCommand(IDriverCommand command)
 {
     this.ExecuteCommand(command, null);
 }