/// <summary>
        /// Closes the shared memory queue currently opened.
        /// </summary>
        public void Close()
        {
            if (mMemoryMappedViewAccessor != null)
            {
                if (mQueueHeader != null)
                {
                    mMemoryMappedViewAccessor.SafeMemoryMappedViewHandle.ReleasePointer();
                    mQueueHeader = null;
                }

                mMemoryMappedViewAccessor.Dispose();
                mMemoryMappedViewAccessor = null;
            }

            if (mMemoryMappedFile != null)
            {
                mMemoryMappedFile.Dispose();
                mMemoryMappedFile = null;
            }

            mQueueHeader         = null;
            mFirstBlockInMemory  = null;
            mFirstBlockUnderRead = null;
            mNumberOfBlocks      = 0;
            mBufferSize          = 0;
            mBlockSize           = 0;
            mInitialized         = false;
        }
        /// <summary>
        /// Creates a new shared memory queue (only supported on the full .NET framework).
        /// </summary>
        /// <param name="name">Name of the shared memory region to create the queue in.</param>
        /// <param name="bufferSize">Size of a buffer in the shared memory queue (in bytes).</param>
        /// <param name="numberOfBlocks">Number of blocks the queue should keep.</param>
        /// <exception cref="NotSupportedException">This method is not supported on the current platform/framework.</exception>
        /// <remarks>
        /// This method creates a new queue in a shared memory region specified by the given name and the given size.
        /// </remarks>
        public void Create(string name, int bufferSize, int numberOfBlocks)
        {
#if NETFRAMEWORK
            if (name == null)
            {
                throw new ArgumentNullException(nameof(name));
            }
            if (bufferSize < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(bufferSize), "The block size must be positive.");
            }
            if (numberOfBlocks < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(numberOfBlocks), "The number of blocks must be positive.");
            }

            // close currently opened queue, if necessary
            Close();

            mNumberOfBlocks = numberOfBlocks;
            mBufferSize     = bufferSize;
            // ensure that the block sizes are a multiple of the cache line size to avoid false sharing
            mBlockSize       = (sizeof(QueueBlock) + mBufferSize + CacheLineSize - 1) & ~(CacheLineSize - 1);
            mQueueHeaderSize = (sizeof(QueueHeader) + CacheLineSize - 1) & ~(CacheLineSize - 1);
            long dataSize        = (long)mNumberOfBlocks * mBlockSize;
            long totalBufferSize = mQueueHeaderSize + dataSize;

            // create the shared memory region
            string queueName = $"{name} - Shared Memory";
            mMemoryMappedFile = MemoryMappedFile.CreateNew(
                queueName,
                totalBufferSize,
                MemoryMappedFileAccess.ReadWrite,
                MemoryMappedFileOptions.None,
                sMemoryMappedFileSecurity,
                HandleInheritability.None);
            mMemoryMappedViewAccessor = mMemoryMappedFile.CreateViewAccessor();

            // get pointer to the buffer
            byte *ptr = null;
            mMemoryMappedViewAccessor.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
            mQueueHeader        = (QueueHeader *)ptr;
            mFirstBlockInMemory = (QueueBlock *)((byte *)mQueueHeader + mQueueHeaderSize);

            // init the queue
            InitQueue();

            // the queue is initialized now
            mFirstBlockUnderRead = null;
            mInitialized         = true;
#else
            throw new NotSupportedException("The Created() method is only supported on the full .NET framework.");
#endif
        }
        /// <summary>
        /// Pushes a sequence of blocks onto the top of the 'used block stack'.
        /// </summary>
        /// <param name="firstBlock">First block to push onto the 'used block stack'.</param>
        /// <param name="lastBlock">Last block to push onto the 'used block stack'.</param>
        /// <param name="overflowCount">Number of lost single blocks or block sequences since the last successful transferred block.</param>
        private void PushUsedBlocks(QueueBlock *firstBlock, QueueBlock *lastBlock, int overflowCount)
        {
            lastBlock->OverflowCount = overflowCount;             // last block is the first block of the pushed sequence

            int firstBlockIndex = (int)(((byte *)firstBlock - (byte *)mFirstBlockInMemory) / mBlockSize);

            while (true)
            {
                int firstIndex = lastBlock->NextIndex = mQueueHeader->UsedStackHeaderIndex;
                if (Interlocked.CompareExchange(ref mQueueHeader->UsedStackHeaderIndex, firstBlockIndex, firstIndex) == firstIndex)
                {
                    break;
                }
            }
        }
        /// <summary>
        /// Opens an existing queue in the shared memory region with the specified name.
        /// </summary>
        /// <param name="name">Name of the shared memory region to open.</param>
        public void Open(string name)
        {
            // close currently opened queue, if necessary
            Close();

            try
            {
                // open the shared memory region the queue resides in
                string queueName = $"{name} - Shared Memory";
                mMemoryMappedFile         = MemoryMappedFile.OpenExisting(queueName, MemoryMappedFileRights.ReadWrite);
                mMemoryMappedViewAccessor = mMemoryMappedFile.CreateViewAccessor();

                // get pointer to the buffer in shared memory
                byte *ptr = null;
                mMemoryMappedViewAccessor.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
                mQueueHeader = (QueueHeader *)ptr;

                // check the queue's signature
                // (for an explanation why 'ALVA' see InitQueue()
                if (mQueueHeader->Signature[0] != 'A' ||
                    mQueueHeader->Signature[1] != 'L' ||
                    mQueueHeader->Signature[2] != 'V' ||
                    mQueueHeader->Signature[3] != 'A')
                {
                    throw new InvalidDataException("Shared region does not start with the magic word 'ALVA'.");
                }

                // read administrative information from the queue's header
                mNumberOfBlocks     = mQueueHeader->NumberOfBlocks;
                mBufferSize         = mQueueHeader->BufferSize;
                mBlockSize          = mQueueHeader->BlockSize;
                mQueueHeaderSize    = (sizeof(QueueHeader) + CacheLineSize - 1) & ~(CacheLineSize - 1);
                mFirstBlockInMemory = (QueueBlock *)((byte *)mQueueHeader + mQueueHeaderSize);

                // the queue is initialized now!
                mFirstBlockUnderRead = null;
                mInitialized         = true;
            }
            catch (Exception)
            {
                Close();
                throw;
            }
        }
        /// <summary>
        /// Pushes a block onto the top of the 'used block stack'.
        /// </summary>
        /// <param name="block">Block to push onto the 'used block stack'.</param>
        /// <param name="overflowCount">Number of lost single blocks or block sequences since the last successful transferred block.</param>
        private void PushUsedBlock(QueueBlock *block, int overflowCount)
        {
            Debug.Assert(block->MagicNumber == QueueBlock.MagicNumberValue);
            Debug.Assert(block->NextIndex == -1);

            block->OverflowCount = overflowCount;

            Debug.Assert(((byte *)block - (byte *)mFirstBlockInMemory) % mBlockSize == 0);
            int blockIndex = (int)(((byte *)block - (byte *)mFirstBlockInMemory) / mBlockSize);

            Debug.Assert(blockIndex < mNumberOfBlocks);

            while (true)
            {
                int firstIndex = block->NextIndex = mQueueHeader->UsedStackHeaderIndex;
                if (Interlocked.CompareExchange(ref mQueueHeader->UsedStackHeaderIndex, blockIndex, firstIndex) == firstIndex)
                {
                    break;
                }
            }
        }
        /// <summary>
        /// Begins reading a new block.
        /// </summary>
        /// <param name="validSize">Receives the number of valid bytes in the block.</param>
        /// <param name="overflowCount">Receives the number of lost single blocks or block sequences since the last reading attempt.</param>
        /// <param name="blocksFollowingInSequence">
        /// Receives <c>true</c>, if there are more blocks in the queue that belong to the block;
        /// otherwise false.
        /// </param>
        /// <returns>
        /// Pointer to the data buffer within the retrieved block;
        /// null if no block is available for reading.
        /// </returns>
        /// <exception cref="InvalidOperationException">The queue was not initialized using <see cref="Create"/> or <see cref="Open"/>.</exception>
        public void *BeginReading(out int validSize, out int overflowCount, out bool blocksFollowingInSequence)
        {
            // abort, if the queue is not initialized
            if (!mInitialized)
            {
                throw new InvalidOperationException("Queue is not initialized, call Create() or Open() to initialize it.");
            }

            if (mFirstBlockUnderRead != null)
            {
                // there are some blocks to read left from the last flush
                // => take one of these blocks

                // prepare stuff to return
                validSize     = mFirstBlockUnderRead->DataSize;
                overflowCount = mFirstBlockUnderRead->OverflowCount;
                var block  = mFirstBlockUnderRead;
                var buffer = (void *)((byte)block + sizeof(QueueBlock));

                // proceed with the next block
                if (mFirstBlockUnderRead->NextIndex >= 0)
                {
                    Debug.Assert(mFirstBlockUnderRead->NextIndex < mNumberOfBlocks);
                    mFirstBlockUnderRead = (QueueBlock *)((byte *)mFirstBlockInMemory + (long)mFirstBlockUnderRead->NextIndex * mBlockSize);
                    Debug.Assert(mFirstBlockUnderRead->MagicNumber == QueueBlock.MagicNumberValue);
                }
                else
                {
                    mFirstBlockUnderRead = null;
                }

                blocksFollowingInSequence = mFirstBlockUnderRead != null;

                Debug.Assert(((long)buffer & 15) == 0);
                block->NextIndex = -1;
                return(buffer);
            }

            // there are no blocks to read left from the last flush
            // => try to flush the queue, re-initialize the buffer and proceed...
            while (true)
            {
                int blockIndex = mQueueHeader->UsedStackHeaderIndex;
                if (blockIndex < 0)
                {
                    // no block on the 'used block stack'
                    validSize                 = 0;
                    overflowCount             = 0;
                    blocksFollowingInSequence = false;
                    return(null);
                }

                // flush the 'used block stack'
                Debug.Assert(blockIndex < mNumberOfBlocks);
                if (Interlocked.CompareExchange(ref mQueueHeader->UsedStackHeaderIndex, -1, blockIndex) != blockIndex)
                {
                    continue;
                }

                // reverse the sequence of blocks to restore the original order
                var block             = (QueueBlock *)((byte *)mFirstBlockInMemory + (long)blockIndex * mBlockSize);
                int currentBlockIndex = blockIndex;
                int previousIndex     = -1;
                while (true)
                {
                    Debug.Assert(block->MagicNumber == QueueBlock.MagicNumberValue);
                    Debug.Assert(block->NextIndex < 0 || block->NextIndex < mNumberOfBlocks);
                    int nextIndex = block->NextIndex;
                    block->NextIndex  = previousIndex;
                    previousIndex     = currentBlockIndex;
                    currentBlockIndex = nextIndex;
                    if (currentBlockIndex < 0)
                    {
                        break;
                    }
                    block = (QueueBlock *)((byte *)mFirstBlockInMemory + (long)currentBlockIndex * mBlockSize);
                }

                Debug.Assert(previousIndex < mNumberOfBlocks);
                mFirstBlockUnderRead = (QueueBlock *)((byte *)mFirstBlockInMemory + (long)previousIndex * mBlockSize);
                validSize            = mFirstBlockUnderRead->DataSize;
                overflowCount        = mFirstBlockUnderRead->OverflowCount;
                void *buffer = (byte *)mFirstBlockUnderRead + sizeof(QueueBlock);

                // proceed with the next block
                if (mFirstBlockUnderRead->NextIndex >= 0)
                {
                    Debug.Assert(mFirstBlockUnderRead->NextIndex < mNumberOfBlocks);
                    mFirstBlockUnderRead = (QueueBlock *)((byte *)mFirstBlockInMemory + (long)mFirstBlockUnderRead->NextIndex * mBlockSize);
                }
                else
                {
                    mFirstBlockUnderRead = null;
                }

                blocksFollowingInSequence = mFirstBlockUnderRead != null;

                Debug.Assert(((long)buffer & 15) == 0);
                block->NextIndex = -1;
                return(buffer);
            }
        }