Software architectures promote development focused on modular functional building blocks (components), their interconnections (configurations), and their interactions (connectors). Since architecture-level components often contain complex functionality, it is reasonable to expect that their interactions will be complex as well. Middleware technologies such as CORBA, COM, and RMI provide a set of predefined services for enabling component composition and interaction. However, the potential role of such services in the implementations of software architectures is not well understood. In practice, middleware can resolve various types of component heterogeneity - across platform and language boundaries, for instance - but also can induce unwanted architectural constraints on application development. We present an approach in which components communicate through architecture-level software connectors that are implemented using middleware. This approach preserves the properties of the architecture-level connectors while leveraging the beneficial capabilities of the underlying middleware. We have implemented this approach in the context of a component- and message-based architectural style called C2 and demonstrated its utility in the context of several diverse applications. We argue that our approach provides a systematic and reasonable way to bridge the gap between architecture-level connectors and implementation-level middleware packages.