Skip to content

Instantly share code, notes, and snippets.

@wqweto
Created June 5, 2026 13:03
Show Gist options
  • Select an option

  • Save wqweto/1c2c8c464e78c3cba78404d108e01f78 to your computer and use it in GitHub Desktop.

Select an option

Save wqweto/1c2c8c464e78c3cba78404d108e01f78 to your computer and use it in GitHub Desktop.
VB6-TypeLib-Projection-Guide.md

Emulating VB6: References vs Components, and the Ctl suffix

A guide for language/IDE developers who want to reproduce how VB6 surfaces COM type libraries in its two pickers (References and Components) and decides the control namespace name (and when to append the Ctl suffix).

Everything below was verified against a real working OCX (UniCCtl.ocx, library FLAGS=2, no suffix) and a from-scratch one (FLAGS=0, suffix appears).


1. The two pickers serve two different worlds

Picker Purpose Underlying registry source
References (Project ▸ References) Nonvisual type libraries: classes, automation servers, enums, structs HKCR\TypeLib
Components (Project ▸ Components, Ctrl+T) Visual ActiveX controls that go in the Toolbox HKCR\CLSID\{…} with the Control marker

A single file (an .ocx) usually registers both a type library and one or more control coclasses, so it can be a candidate for both lists at once. That overlap is what creates the naming question.


2. Building the References list

Enumerate HKCR\TypeLib\{libid}\{version} and, for each, read the win32 (or win64) sub-key path and the FLAGS value (= ITypeLib::GetLibAttr().wLibFlags). Include the library unless any of:

wLibFlags & LIBFLAG_FRESTRICTED  (0x1)   -> never show
wLibFlags & LIBFLAG_FHIDDEN      (0x4)   -> hide from users
wLibFlags & LIBFLAG_FCONTROL     (0x2)   -> "describes controls; do not show
                                            in browsers meant for NONVISUAL
                                            objects" -> References hides it

So References shows a library iff wLibFlags has none of FRESTRICTED / FHIDDEN / FCONTROL. (FCONTROL is the one that matters for controls — it is the switch that moves a library out of References and into "Components only".)

Pseudo:

for each registered typelib T:
    if T.wLibFlags & (FRESTRICTED | FHIDDEN | FCONTROL): continue
    referencesList.add(T)               # displayed as T.libraryName

3. Building the Components list

Components is not driven by the type library; it is driven by the component-category / control registration under each CLSID. A CLSID is a control candidate when it has the legacy Control subkey or lists CATID_Control ({40FC6ED4-2438-11CF-A3DB-080036F12502}) under Implemented Categories:

HKCR\CLSID\{clsid}\Control                                   (empty key), or
HKCR\CLSID\{clsid}\Implemented Categories\{CATID_Control}
HKCR\CLSID\{clsid}\InprocServer32  = path, ThreadingModel = Apartment

Enumerate those CLSIDs (preferably via ICatInformation::EnumClassesOfCategories with CATID_Control). For each, resolve its owning type library (via HKCR\CLSID\{clsid}\TypeLib + \Version) so you know which library/coclass pair the control belongs to.

Pseudo:

for each clsid C implementing CATID_Control (or with the Control key):
    lib  = typelib of C
    componentsList.add( controlEntry(lib, coclassNameOf(C)) )

A control entry is later instantiated on a form as <projectedLibName>.<coclass>.


4. The namespace projection rule (when to use Ctl)

When code/designers reference a type, VB6 projects each type library into a VB namespace named after the library (ITypeLib::GetDocumentation(-1) name, e.g. TextBoxExLib). The conflict: the same library can be visible in both lists, and the form designer must name a control instance unambiguously (Begin <ns>.<coclass>).

VB6 resolves it like this:

isInReferences = (lib visible in References)          # see section 2
isInComponents = (lib owns >=1 CATID_Control coclass) # see section 3

if isInComponents and isInReferences:
    # library lives in BOTH worlds -> disambiguate the control projection
    controlNamespace = lib.name + "Ctl"        # e.g. TextBoxExLibCtl
    referenceNamespace = lib.name              # e.g. TextBoxExLib
elif isInComponents:        # controls only (FCONTROL set -> not in References)
    controlNamespace = lib.name                # e.g. TextBoxExLib  (NO suffix)
else:                       # references only
    referenceNamespace = lib.name

Rule of thumb for the Ctl suffix: append Ctl to the control namespace iff the same library is simultaneously offered in References and in Components. Set LIBFLAG_FCONTROL on the library and it drops out of References, the dual visibility disappears, and the suffix is no longer added.

Notes:

  • The suffix is purely a disambiguation device, not a semantic marker. Nothing in COM mandates it; it is VB6 IDE policy.
  • The coclass-level TYPEFLAG_FCONTROL (TYPEATTR.wTypeFlags & 0x20) marks an individual coclass as a control (placeable / toolbox-able). The library-level LIBFLAG_FCONTROL (wLibFlags & 0x2) is the separate switch that hides the whole library from the nonvisual (References) browser. They are independent.

5. How a control author controls the outcome

Desired behavior Library wLibFlags Result in VB6
Show in both lists, Ctl suffix on control 0 LibName (References) + LibNameCtl (form)
Controls only, no suffix (typical OCX) FCONTROL = 2 only Components, LibName.Coclass
Fully hidden from users but usable FCONTROL|FHIDDEN = 6 only Components; host builds wrapper .oca

The flag is baked into the .tlb from IDL (control attribute on the library statement) and copied to HKCR\TypeLib\{libid}\{ver}\FLAGS by RegisterTypeLib. It is not a DllRegisterServer category decision.


6. Minimal algorithm to emulate

# Discovery
references = [ T for T in registeredTypeLibs
                if not (T.wLibFlags & (FRESTRICTED|FHIDDEN|FCONTROL)) ]
controls   = enumerateClasses(CATID_Control)        # -> (clsid, lib, coclass)

# Naming for a control coclass on a form
def controlNamespace(lib):
    inRefs = lib in references
    inComps = any(c.lib == lib for c in controls)
    return lib.name + ("Ctl" if (inRefs and inComps) else "")

That reproduces VB6's observable behavior: which entries appear in each picker, and exactly when the Ctl suffix is appended.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment