Pretty much any software that "wants" to be extended over time, by multiple contributors who are not closely linked or coordinating, can benefit from a plug-in architecture of some sort. Common examples are operating systems, CAD and GIS tools, drawing and image manipulation tools, text editors and word processors, IDEs, web browsers, web content management systems, and programming language and frameworks. Plugins are the mainstay of extensible systems.
Plug-in architectures typically use duck typing. The architect defines a common set of methods (e.g.
seek, etc.), which each plugin then implements (either entirely or in part). Some methods are mandatory, while others may be optional, or useful only in specific cases.
When the main program initially runs, it checks one or more "plugin areas" (such as known
./plugins directories) for the existence of plugins. Those found are loaded into the program.
Often plugins must exist at the time the main program runs. The Unix kernel and the Apache web server typically operate this way; they must be restarted to "see" and use new plugins. Plugins may be more dynamic however; here the main program periodically re-checks for newly-added or changed plugins (for example by comparing a stored
plugins-last-loaded timestamp with the "last modified" timestamp for a plugins directory). The main program would then (re-)load plugins--either all of them, in the simple/naive case, or just the new/changed ones, if it's more sophisticated.
There is often a "registration" requirement, with each plugin not just being code, but also including some metadata that communicates how the plugin integrates into the whole. A music player plugin, for example, might be required to state what kind(s) of files it can play, what processor architecture(s) it can run on, what resources it needs (e.g. how much memory it needs to be allocated), and other attributes required for the main program to decide which plugin to use to play which file.
The mechanisms for plugin registration, loading, and interaction with the main program is quite language- and framework-specific. Because there's a lot of "orchestration" going on, with some functions handled by the main program and some by its plugins (of which there might be quite a few), setting up a program for extensibility requires care and consideration, and an architectural view of the program as "a system" rather than "a single piece of code."
Most large-scale projects will have already chosen a plugin framework, or designed their own. There are also a number of generic plugin frameworks designed to simplify making your program into an extensible system.
(Answer to Question 1) While plug-ins can use each other's functionality, they typically would do so through the pre-defined methods/APIs the architect laid out. The use of such "duck typing" helps avoid super-interdependency, and means that it's not necessarily clear whether a given feature is provided by "core" code or a plugin. Indeed, having adopted a plug-in strategy, many developers implement even "core" features as plugins--just ones that are shipped with the main program. While having spaghetti tangles of plugins isn't ideal, it's not uncommon to see some plug-ins requiring the existence of other plug-ins.
(Answer to Question 2) As an architect, the main thing you offer plugins is an architecture--e.g. a set of methods via which they are setup, registered, and invoked, and a design and set of requirements in which the plugins will operate. The main program, while running, usually exposes many if not all of its internal data structures and methods to plugins. This is obviously a security exposure. A number of sandboxing techniques can be (and
increasingly are being) used, but most often, plugins are "trusted" code, operating as
if they are part of the main program.
For further reading: