In the case of a dynamically linked library, creating all those links happens at program startup — and that’s why typically the operating system is partially involved in this, because it role is (amongst other things) to start up programs. If something goes wrong during this linking process, the program won’t start.
In the plugin case, creating those links tends to happen when the plugin is used. Depending on the language/environment your program runs in, the operating system may or may not be included in this operation. If something goes wrong during this linking process, it falls to the program to decide what to do about it.
In the service case, creating links happens when the service is used. It’s really very similar to the plugin case, with a difference that is substantial, but irrelevant to this discussion: a service runs in a separate process, and therefore must keep context for each program that uses it. A plugin does not have that requirement.
Now I’ve been using the term “linking” above, because I’ve started off from a C/C++ example, where there’s a static linker (used in the first category), and a dynamic linker (used in the second category), and, well, linking is the term you’d use.
When you’re talking about services, though, people tend to use the term “binding”. And the above two categories then can be described as examples of early versus late binding — early binding describing binding at compile-time, and late binding describing binding at run-time.
Thinking about this process in terms of early vs. late — whether linking or binding — makes something apparent that I think can be obscured by the terms “static” and “dynamic” as used in C/C++. In the C/C++ world, linking to functions in dynamically linked libraries is a run-time process, and therefore firmly falls into the “late” category of things.
But taking a step back and looking at this from a different angle, it can easily be argued that it’s quite obviously an “early” binding process. Why? Because the program won’t even be started if binding fails.
It’s a different approach to defining early vs. late binding, but it’s a valid one: you can lump everything that makes sure a program will not start if binding fails under the term “early” binding. And conversely that makes everything where each program must handle binding failures itself examples of “late” binding.
I’m not trying to argue that one definition is better or worse than the other, really. What I am trying to point out is that dynamically linked libraries are slightly harder to classify in terms of “early” vs. “late” than might appear obvious at first.
And that’s a hugely important point. I’ll get back to that later, though.
First, let’s ignore dynamically linked libraries for a bit and focus on the benefits and downsides of using binding that’s either clearly in the “early” camp or clearly in the “late” camp.
I’ve mentioned to the huge benefits of late binding earlier on in this post: it saves disk/memory space, and it’s a powerful mechanism for spreading bug fixes in a library: you just install a newer version of your library with the fixes, and every program that uses benefits from that.
The latter point is not as clear-cut as it may appear, however. There are plenty of issues with changing the behaviour of a library. In the most difficult-to-handle instance, programs may have included work-arounds for the bugs in a library which, once the library is fixed, will cause the program to malfunction.
This class of problems mandates that in order for libraries to be re-usable safely, one has to somehow ensure that a program is only bound to a version of a library it’ll work with. The most commonly used mechanism for dealing with that is to version the interface the library exports, and then specify “safe” interface versions when binding a program to an interface.
The problem with specifying what version of an interface you want to bind to is that you need to anticipate what sort of changes each future version of the interface will bring. If you don’t, you either risk binding against an interface that still breaks your code — or on the other end of the spectrum, you risk not binding against interface versions your code would work with. To solve this, many versioning schemes distinguish between major/incompatible version increments, and minor/compatible version increments.
Handling compatible and incompatible versions of an interface properly is a topic worthy of it’s own lengthy blog post. I don’t really want to go into details here, but hopefully have made clear that late binding is not without it’s own set of problems, that — ultimately — can determine whether or not the user’s experience with your software is good or not.

