/// <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);
                    }
                }
            }
        }
        private void ProcessNotification(ResizableBuffer resizableBuffer)
        {
            IntPtr bufferPointer = resizableBuffer.DangerousGetPointer();

            // Marshal the notification received.
            DriverNotificationHeader header       = (DriverNotificationHeader)Marshal.PtrToStructure(bufferPointer, typeof(DriverNotificationHeader));
            DriverNotification       notification = new DriverNotification {
                Type = header.Type, DataLength = header.DataLength, Data = bufferPointer + this.notificationHeaderSize
            };

            // Get the reply object from the user-defined handler.
            object reply         = null;
            int    handlerResult = (int)NativeMethods.Ok;

            try
            {
                reply = this.handler(notification);
            }
            catch (Exception e)
            {
                handlerResult = Marshal.GetHRForException(e);
                NotificationsMonitor.Logger.Error(e, "Notification handler threw an exception.");
            }

            // Driver is not expecting any reply.
            if (header.ReplyLength == 0)
            {
                if (reply != null)
                {
                    NotificationsMonitor.Logger.Warn(CultureInfo.InvariantCulture, "Driver is not expecting any reply, but reply object is returned from handler: {0}", reply.GetType());
                }

                return;
            }

            int replySize = this.replyHeaderSize + MarshalingHelper.GetObjectSize(reply);

            if (replySize > header.ReplyLength)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Reply ({0} bytes) is bigger than the one expected by the driver ({1} bytes).", replySize, header.ReplyLength));
            }

            DriverReplyHeader replyHeader = new DriverReplyHeader
            {
                MessageId = header.MessageId,

                // Notify driver about the exception thrown, if any.
                Status = handlerResult
            };

            // Adjust the buffer to fit the reply.
            resizableBuffer.Resize(replySize);
            bufferPointer = resizableBuffer.DangerousGetPointer();

            // Marshal reply to the output buffer.
            MarshalingHelper.MarshalObjectsToPointer(bufferPointer, replySize, replyHeader, reply);

            // And send it to the driver.
            uint hr = NativeMethods.FilterReplyMessage(this.filterPortHandle, bufferPointer, (uint)replySize);

            if (hr != NativeMethods.Ok)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Unable to send reply: 0x{0:X8}", hr), Marshal.GetExceptionForHR(unchecked ((int)hr)));
            }
        }