tlbimp is only the beginning of the problem.
Every COM-visible .NET object implements IManagedObject. Every time you try to call an object through a COM interface, .NET does a QueryInterface for IManagedObject, and if it succeeds the object is unwrapped and accessed directly instead. Eliminating the interop call in this way is considered a performance optimization.
This is a serious issue in COM-based interop scenarios where multiple components may be implemented in .NET languages. If each .NET component implements its own tlbimp-generated version of the COM interface, they will be unable to communicate with each other using that interface. Even though each tlbimp'ed copy of the interface has the same COM GUID, .NET considers them separate interfaces. The "correct" solution to this problem is to define the COM interface in a primary interop assembly, and have every .NET-implemented component use that same copy of the interface, but if there's no coordination between component developers that's pretty unlikely to happen.
This problem was once highlighted by a Microsoft developer at http://blogs.msdn.com/b/jmstall/archive/2009/07/09/icustomqueryinterface-and-clr-v4.aspx along with a solution for .NET 4, but it was based on a pre-release version and doesn't work in the final. I'm not aware of anywhere else it's been acknowledged by Microsoft.
One solution is to forego the interface in the .NET-to-.NET scenario and use reflection instead. This may work in many cases but of course isn't very performant. Possibly the best solution is to use unmanaged code to aggregate each of your managed components and reject attempts to QueryInterface for IManagedObject. This is similar to what's described in the above blog entry, but doesn't rely on .NET features that no longer work as described in that blog.
The .NET interop behavior is, IMO, pretty terrible, and clearly violates COM rules (same IID == same interface, and shouldn't have to care what language the object is implemented in). But .NET has behaved this way from the beginning and no amount of feedback from developers bitten by this behavior has changed the minds of anyone on the .NET team.