Some good answers so far, but there seems to be some confusion in the use of the word dynamic and its relationship to a virtual interface.
First there are dynamic-ally sized types like arrays, strings, and queues. These are generic datatypes that are usually not synthesizable. but they could be if vendors were motivated enough. These types may be declared inside a module, interface, or class, but are not really relevant to the discussion of interfaces and virtual interfaces.
Then there are dynamically constructed class objects. Class objects are constructed by executing procedural code at run time, instead of statically elaborating them at compile time. Static elaboration also takes care of the port connections between modules by flattening out the hierarchy and creating direct reference to the signals connected by the ports. You can't do that with classes, so you need another mechanism besides port connections to communicate outside of the class object.
A simple solution is to use hierarchical references - just reach into the DUT with the hierarchical path you want to read or write. However this solution quickly becomes unworkable for a number of reasons. It is bad for re-usability. Whenever the hierarchy of your DUT changes, or you try to move from block level to SOC verification, you have to update the path in your testbench class. Many people try to manage this by using text macros like
`define PATH top.design.block1
and then the write `PATH.sig1 <= 1; in their testbench. This approach does not work when there are multiple instances of testbench objects that need to communicate multiple paths of the DUT. Another problem is that most classes are written inside SystemVerilog packages, and packages are not supposed to have any outside dependencies other than other package imports.
So SystemVerilog, which already has dynamic references to class objects, added a dynamic reference to an interface instance and called it a virtual interface. It works very similar to an abstract class reference without inheritance. A virtual interface variable can dynamically accept a handle to an actual interface instance. Then you can then dynamically reference members of that interface instance.