Visual Basic Binary Compatibility
What is binary compatibility and why is it important?
In order to answer these questions,
you first need to understand a little about how Visual Basic maps to the Microsoft Component
Object Model (COM). It's important to understand how VB utilises COM technologies if you are
to maximise the capabilities of VB to deliver component-based solutions and also if you are
to avoid the infamous "Error 429 - ActiveX component can't create object".
VB, ActiveX Components And COM
Visual Basic ActiveX (DLL, EXE, control and document) projects create COM components.
All COM components contain one or more component classes and COM requires
each component class to implement one or more interfaces. COM interfaces provide a standard
mechanism by which a client can invoke the services of a server component.
COM makes heavy use of the Windows registry and requires all COM components to register
information about themselves within the registry before clients can access their services. To
ensure different components do not clash, COM uses globally unique identifiers (known as GUIDs)
which are computed using a special algorithm.
VB public classes correspond to COM component classes and VB automatically creates a
default interface for each class. Each component class has a unique GUID (known as a CLSID)
as does each interface implemented by a class (known as an IID). Again, VB automatically creates
the unique identifiers on behalf of the programmer at compilation time based on the
compatibility setting selected for the project.
A COM component class can also be accessed via its programmatic identifier (referred to
as its ProgID). This is a name constructed from the type library name (VB project 'Name' attribute)
and VB class 'Name' attribute separated by a period e.g. 'MyApp.MyClass'. (The
VB 'CreateObject' command takes a ProgID as one of its parameters to create an instance of an object.)
Type Libraries
Information about a COM component's classes, their interfaces, methods,
properties, enums etc. are typically stored in a type library and referenced by VB projects.
This information is used by the VB IDE to display intelli-sense information and component
details within the Object Browser window.
Type library information can be defined using Interface Definition Language (IDL) and compiled into
a type library using the MIDL compiler. Fortunately, VB programmers are spared this exercise
as VB does this work behind the scenes and automatically binds the type
library into the compiled component binary. VB also registers the component, including a
reference to the type library GUID (referred to as the LIBID).
Note: If you wish to view the IDL for your VB components, you can do so using the
OleView utility which ships with Visual Studio Enterprise Edition. Run the utility and select
the 'View TypeLib...' option from the 'File' menu. This can be particularly
useful when trouble-shooting unexpected binary compatibility issues.
Windows Registry
When an ActiveX component is registered on a machine, a number of registry entries are
created. For example, consider an ActiveX DLL component containing a public class called
'AXTest' with a public method name 'TestMethod1' and the VB project name 'VBAXTest'.
When the compiled component (VBAXTest.DLL) is deployed to
'C:\Program Files\Test' and registered, a number of entries are written under
HKEY_CLASSES_ROOT as follows (not all entries are shown):
| CLSID |
| |
{E2366527-7621-4CB3-BC1B-7527C167BD23} |
| |
|
InprocServer32 |
| |
|
|
(Default) |
REG_SZ |
C:\Program Files\Test\VBAXTest.dll |
| |
|
|
ThreadingModel |
REG_SZ |
Apartment |
| |
|
ProgID |
| |
|
|
(Default) |
REG_SZ |
VBAXTest.AXTest |
| |
|
TypeLib |
| |
|
|
(Default) |
REG_SZ |
{62166784-093E-498F-BDE0-C8DAF8CB40A6} |
| Interface |
| |
{B36EE049-7504-4047-BA93-684A2380A820} |
| |
|
ProxyStubClsid |
| |
|
|
(Default) |
REG_SZ |
{00020420-0000-0000-C000-000000000046} |
| |
|
ProxyStubClsid32 |
| |
|
|
(Default) |
REG_SZ |
{00020420-0000-0000-C000-000000000046} |
| |
|
TypeLib |
| |
|
|
(Default) |
REG_SZ |
{62166784-093E-498F-BDE0-C8DAF8CB40A6} |
| |
|
|
Version |
REG_SZ |
1.0 |
| TypeLib |
| |
{62166784-093E-498F-BDE0-C8DAF8CB40A6} |
| |
|
1.0 |
| |
|
|
(Default) |
REG_SZ |
VB ActiveX Test Component |
| |
|
|
0 |
| |
|
|
|
win32 |
| |
|
|
|
|
(Default) |
REG_SZ |
C:\Program Files\Test\VBAXTest.dll |
| VBAXTest.AXTest |
| |
CLSID |
| |
|
(Default) |
REG_SZ |
{E2366527-7621-4CB3-BC1B-7527C167BD23} |
Visual Basic References
When you display the Project References dialog within the VB IDE, VB scans the 'HKCR\TypeLib'
section of the registry and lists the friendly name from the version key
(i.e. in our example 'VB ActiveX Test Component'). If a reference is selected then VB uses the
value under the 'win32' key to locate the type library in the file system, which it then
loads so that it can populate the object browser and the intelli-sense lists in
the IDE. The LIBID of the selected type library is added to the project source file as a 'Reference=' line.
BEWARE
VB defaults to referencing the latest version of a type library. If you
compile an application against a later type library version and release the updated
application to existing users whilst failing to release the updated component, users
will encounter error 429 with the updated application!
ActiveX Component Clients
If we consider our example further by creating a standard Exe
project that references our test ActiveX server and contains the following code
in the Form_Load event of the application’s main form.
Private Sub Form_Load
Dim objTest As AXTest
Set objTest = New AXTest
objTest.TestMethod1
End Sub
When the client project is compiled, the component Class ID (CLSID) and Interface ID (IID)
of AXTest is compiled into the executable (the CLSID is used by COM to lookup
in the registry the physical location in the file system of the file containing
the class). This means that if either
ID changes in the ActiveX component then the dreaded Error 429 ActiveX Cannot
Create Component will be displayed and without error handling, the executable
will simply terminate.
When an ActiveX component is compiled, there are three
possible project compatibility settings:
- No Compatibility
- Project Compatibility
- Binary Compatibility
With this setting, new class ID’s, new interface ID’s and a new type
library ID will be generated by VB each time the ActiveX component project is compiled.
This will cause any compiled client components to fail (with error 429!)
and report a missing reference to the 'VB ActiveX Test Component' when a client project
is loaded in the VB IDE.
Tip
Use this setting to compile the initial release of a component to other developers.
With this setting, VB will generate new interface ID’s for classes whose interfaces
have changed, but will not change the class ID’s or the type library ID.
This will still cause any compiled client components to fail (with error 429!)
but will not report a missing reference to the 'VB ActiveX Test Component' when a client project
is loaded in the VB IDE. Recompilation of client components will restore them
to working order again.
Tip
Use this setting during the initial development and testing of a component within
the IDE and before the component is released to other developers.
VB makes it possible to extend an existing class or interface by adding new methods and properties
etc. and yet still retain binary compatibility. It can do this, because it silently creates a new
interface ID for the extended interface and adds registration code to register the original
interface ID but with a new Forward key containing the value of this new interface ID. COM
will then substitute calls having the old ID with the new ID and hence applications
built against the old interface will continue to work (assuming the inner workings of the component
remain backward compatible!).
With this setting, VB will not change any of the existing
class, interface or type library ID’s, however in order that it can do so, VB
requires the project to specify an existing compiled version that it can compare against
to ensure that existing interfaces have not been broken.
Tip
Use this setting following the release of a component to other developers.
Breaking Compatibility
When the need to break compatibility arises, be sure to tackle it head on and make a
clean break. To do so, create a new version of the project with compatibility set to
'No Compatibility'. Next, change the project 'Name' attribute and compile to a different
component name. Taking this approach ensures that existing applications can continue to
work with the old component while new versions can use the new component.
Working With Binary Compatibility And Avoiding Error 429
The first time an ActiveX component is built for release then the 'No Compatibility'
setting should be selected thereby causing new class, interface and type library ID’s
to be generated. (Only use 'Project Compatibility' during the initial development of a
component within the IDE and before releasing the component to other developers.)
A copy of the released component should then be placed into a sub-folder (preferably
named 'Compat') beneath the project folder and the compatibility setting changed to
'Binary Compatible' with the compatible image being set to the component in the 'Compat'
sub-folder.
The following rules ensure binary compatibility is maintained:
- The name of the project cannot change.
- The names of any public classes cannot change.
- The names of any public methods or properties cannot change.
- The number of parameters to any public method cannot change.
- The data types of the parameters cannot change.
- The calling convention (ByRef, ByVal) of the parameters cannot change.
- The names of public enums cannot change.
- The order/values of elements in public enums cannot change.
- The names of public user-defined types cannot change.
- The names and data types of elements in public user-defined types cannot change.
- If a public class implements user-defined interfaces then the 'Implements'
statement cannot change.
Whenever the component is amended, always update the component's version information
to allow different versions to be correctly distinguished. You should also update the
compatible image in the 'Compat' folder with the latest compiled component binary
once the component has been successfully tested and released to other developers. It is
a good habit to do this even when interfaces remain unchanged.
How VB Law Can Help
VB Law can greatly assist in maintaining binary compatibility by
providing the following checks prior to commencement of the build process:
- Ensure that binary compatibility settings are set correctly for ActiveX projects.
- Ensure that component version information is completed correctly.
- Ensure that projects only reference authorised components (including
specific versions of authorised components if necessary).
- Ensure that projects only reference authorised references (including
specific versions of authorised references if necessary).
The VB Law sample VB Coding Standards rulebase (supplied free of charge) includes rules to
support binary compatibility for ActiveX components. Visit our downloads
page and trial VB Law for free today.
Recommended Book ...
|
|
Programming Distributed Applications With COM & Visual Basic 6.0, Ted Pattison
|
Our opinion: Must read! [Easy to read, easy to re-visit.]
If you want to learn about COM, DCOM, MTS and how to write
better components with Visual Basic 6 then you won't be disappointed with this book.
The book covers a lot of ground including interfaces, interface design, component versioning,
marshalling, threading,
MTS, deployment of MTS components, configuration of client-side and server-side components, MSMQ,
transaction programming and distributed transactions.
|
|