Calling fclose() here after dup()ing its file descriptor blocks until the child process has ended (presumably because the stream has ended).
The FILE* returned by popen() should be closed by pclose(), not by fclose(). Then, the documentation for pclose() specifies:
The pclose() function waits for the associated process to terminate and returns the exit status of the command as returned by wait4().
So the waiting is one of the two things pclose() does, besides closing the file descriptor. close() does just one thing: it closes the descriptor.
In response to your second question, I think you can use the descriptor returned by fileno(). There's no need to dup() it. After you're done with it, pclose() the original.