Created by ic@stairways.com

IC Internals Documentation



1 Introduction

This document describes implementation details of the Internet Configuration System. It is targeted as those who are maintaining IC, and those with an interest in how it works. Programmers who rely on the internal details described here will find that their programs break under new versions of IC.

Note: This document is largely for internal use only and does not have the polish of the other IC documents. We apologise for the inconvenience.

2 Preferences File Format

An Internet Preferences file contains up to 33 profiles, each of which can hold an unbounded (actually, it's limited by the Resource Manager) number of preferences.

Preferences are stored as resources of type 'ICxy', where x ranges from 'R' to 'T' and y ranges from 'P' to 'Z'. Each distinct resource type is used to hold the preferences for a given profile. The profile ID that is exposed in the API is actually the resource type of the resources used to hold the profile. [If you rely on this, I can guarantee that you will break!] The range of valid characters for x and y gives the maximum number of profiles, ie 99.

Within a profile, preferences are access by key. The key is stored in the resource name. IC uses Get1NamedResource to find a preference given its key. The first four bytes of the resource data holds the preference's attributes, and the remaining resource data holds the preference data.

There is no attempt to keep the profiles compact. So, if you have 11 profiles (types 'ICRP'...'ICRZ') and then delete the middle 9 ('ICRQ'..'ICRY'), the profile IDs will be very sparse ('ICRP' and 'ICRZ').

Each profile has a special key, "49434170*ProfileName" ("*" is the bullet character, option-8), that holds the profile name as a PString. This preference is created when the profile is created. The ICDeletePref routine will refuse to delete this key, and hence you can't delete a profile by deleting all of its keys. [Applications should definitely not rely on this implementation detail. We may change our method for storing profile names in the future.]

Each profile also uses another key, "49434170*ProfileVersion" ("*" is the bullet character, option-8), to hold the version number of Internet Config that last updated the profile. The preference data is a 4 byte version number in NumVersion format. When IC first encounters a profile, it checks that version number to see whether it should update the profile, ie any any new default preferences that were not defined under previous versions of IC.

IC also maintains the version number of the preferences file itself, copying the component's 'vers' ID=1 resource to the preference file's 'vers' ID=1, so that the latest IC version that modified the file is user visible in the Finder's Get Info dialog.

A four byte resource of type 'CURP' ID=128 holds the profile ID of the current profile.

This file format design is an evolution the of the original file format used by IC 1.0. In that version, which did not support profiles, preferences were stored in resources of type 'ICRP'. In addition, no version number information was kept in the preferences file. IC 1.4 was the first version to update the file's 'vers' ID=1 resource. IC 2.0 introduced the concept of multiple profiles. Because the time to update all the profiles in a file in one hit would be huge, the per-profile version resource was added to track the version on a profile-by-profile basis.

When IC 2.0 first sees an IC 1.4 or earlier file, it first updates the 'vers' ID=1 resource. It also sets the 'CURP' resource to mark 'ICRP' as the current profile. It also adds a default profile name and updates the profile version.

3 Internet Config Application Requirements

This appendix describes some of the technical details for writing the Internet Configuration application. Most readers will not be interested in the details contained in this chapter.

Note: This chapter describes the application roughly as it stands with version 2.0. We hope to change the application to eliminate all uses of private API routines soon.

3.1 Basic Operations

The Internet Configuration application has the basic ability to create and edit Internet preferences files, which the application just views as documents. When it is launched without a specific document the application opens the Internet Preferences file in the Preferences folder. When it is launched with a document it edits that document. The documents can then be edited, closed, saved, etc as per the usual Mac interface.

The actual user interface used to edit preferences is beyond the scope of this discussion.

3.2 Component Installation

When it is launched the application first checks the version of the Internet Config Extension in the Extensions folder. If it is not present it should create it; if it out of date it should update it. This operation involves creating the file and filling out its resource and data forks with resources from the application's resource fork into the file. Courtesy demands that it ask the user for permission to do this beforehand.

Because it is in the Extensions folder the component will automatically be registered the next time the system starts up. However, in order to make the component available immediately the application registers the component. This is particularly tricky if an old version of the component is already registered.

From now on the Internet Configuration application accesses the Internet Preferences file using exactly the same API as every other program. Well, more or less. There are some API calls that are designed for use specifically by the Internet Configuration application and are not recommended for use by normal applications. Also the application has inside knowledge about the file used to store preferences, which it needs in order to implement safe saves.

3.3 Setting Up Default Values

The application can now start a session using ICStart. When it opens a preference file the application makes sure that every preference that has a meaningful default value is initialised to that value. Well that was the original specification. As of IC 1.4, the component itself is responsible for creating the default preferences.

The download folder preference is tricky one in that it is not a static value; the application must create the 'alias' to the desktop folder on the fly.

In future the application might set up the ArchiePreferred, InfoMacPreferred and UMichPreferred based on the machines reverse DNS name.

3.4 Editing Configuration Documents

The API provides one extra routine for the configuration application, namely ICSpecifyConfigFile. This allows you to open a config file in any folder directly, without having to mess around with the search path. Also the API extensions introduced with Internet Config 1.2 may allow you do perform all configuration in a totally implementation independent fashion.

4 Reserved for Future Expansion

5 Component API Reference

This section documents the component interface to the Internet Configuration System. This interface was originally designed so that IC 1.x clients could talk to the IC component directly, without having to link with the full IC glue file. As of IC 2.0, this API is obsolete; clients should use the standard IC API instead.

5.1 Component API

This section describes the routines available in the component API, as defined in "InternetConfig.[ph]". Two of the routines are special in that they have special glue, but the vast bulk of the routines just call directly through to the component.

Special Routines

The following routines have special glue to make them look more like their equivalent Internet Config API routines.


function ICCStart (var inst: ComponentInstance;
    creator: OSType): ICError;

If routine is glue that checks for the presence of the Component Manager and the Internet Configuration component. If it finds them it creates an instance, initialises it and returns it. If it can't find them or the initialisation fails then it returns badComponentInstance and inst is nil.


function ICCStop (inst: ComponentInstance): ICError;

This routine is glue that shuts down the instance and closes it.


Standard Routines

All of the other routines in "InternetConfig.[ph]" correspond exactly with their standard API equivalents.

6 Overriding Components

Important: The technology described in this section has not been updated since IC 1.3. Please proceed with caution.

Internet Config provides a powerful mechanism for programmers to override the default implementation of the preference code in a large number of applications. Anyone wielding this power should be careful that they only use it for the forces of niceness and good! Remember that any Internet Config component you write is dynamically linked into the application and the applications will expect you to obey certain rules. Some of these rules are given in this chapter but this chapter can never be exhaustive. Please use some common sense.

Caution: The override components that shipped with Internet Config 1.0 are broken in a variety of ways. Please make sure you are using the new, improved, low calorie, all-mod-cons override components provided in Internet Config 1.1 and later.

6.1 Generic Override Architecture

Writing override components is difficult, especially if you start from scratch. The Component Manager is a very cool but also very tricky to come to grips with. One thing that we discovered while building our two sample override components is that a lot of the code is common code that interfaces to the Component Manager, and that it could be abstracted out. The result of this is the Internet Config Component Generic Override Architecture [How's that for a collection of buzzwords!], which provides a framework for building override components.

Note: The Generic Override system documented here is currently only usable from Think Pascal. Building code resources varies quite dramatic between development environments and we have not had time to build the equivalent code for other systems. We are working on an Generic Override system for Metrowerks C and Pascal and hope to ship it Real Soon Now .

Component Override Concepts

There are a number of concepts with which you must be familiar before attempting an override component. The first is the files that are provided as part of the Generic Override Architecture. These include:

The idea is that you clone ICSpecificOverride.* and modify them to build your component. ICSpecificOverride.p is a unit that has a number of entry points that you can fill out to achieve specific goals. It also declares a number of data structures that you can extend.

There are two types of global variables associated with a component. The shared globals are shared between all instances of the component and the instance globals (or just globals for short) are created for each instance of the component. Normally you would use instance globals in preference to shared globals.

There are two other terms used in the following section that you must be aware of, namely delegate and target. The delegate is reference to the component that your override component is overriding. You can call the delegate either through the delegate field of the instance globals or by returning delegateThisCallErr from one of your override routines.

The target is the component that has overridden your override component, using a ComponentSetTarget call. If, in one of your override routines, you call a component routine that is not the one you are overriding, then you should call through the target. This allows the component that is overriding your component to see that call.

Override Component Cookbook

To create a new override component, following the simple steps documented here.

  1. Make a copy of the specific override files:
  2. Change the 'thng' resource in ICSpecificOverride.rsrc to indicate your manufacturer code. Also change the 'vers' resource to reflect your override component's version and add any resources your component needs. These are the only changes required in ICSpecificOverride.rsrc; the rest of the changes are now in ICSpecificOverride.p.
  3. Change kOurComponentManufacturer to your manufacturer code.
  4. Add any shared globals to the sharedGlobals record.
  5. If you have added shared globals then initialise them in ICSOInitShared.
  6. If your shared globals need cleaning up then clean them in ICSOCleanShared.
  7. Add any instance specific globals to globalsRecord.
  8. If you have added globals then initialise them in ICSOInitGlobals.
  9. If your globals need cleaning up then clean them ICSOCleanGlobals.
  10. If you want to add a completely new routine or remove support for one of the built in routines then modify ICSOCanDo accordingly.
  11. Modify ICSOWhatToOverride to return the correct ProcPtr for each routine that you override or add.
  12. Write each routine. If you want the component to continue calling through to the captured component for this routine then have your routine return delegateThisCallErr. Alternatively if you want to modify the results of the delegate then call the delegate directly (using the delegate field in the globals record) and then don't return delegateThisCallErr.
  13. Smirk at the wonders of Component Manager.
  14. Looking inside ICGenericOverride and frown at the wonders of Component Manager.

The ICSpecificOverrride API

This sub-section describes each of the structures in ICSpecificOverride that you are allowed to modify.

const
  kOurComponentManufacturer = 'ICso';

You must modify this constant declaration to denote your component manufacturer code. If you don't have an official manufacturer code then just make something up, although having a code clash would be very bad, so try to be imaginative. Also remember to set the manufacturer code in the 'thng' resource of ICSpecificOverride.rsrc.

const
  delegateThisCallErr = $81234568;

You should most probably not modify this; it's provided in ICSpecificOverride so that you can return it from your override routines in order to delegate a call.

type
  sharedGlobals = record
    delegate: Component;
    (* add your own shared globals here *)
  end;
  sharedGlobalsPtr = ^sharedGlobals;

You can extend this record to add your own shared globals. Do not delete or modify the first field; it contains a reference to the captured component and is needed by ICGenericOverride.p.

type
  globalsRecord = record
    self: ComponentInstance;
    target: ComponentInstance;
    delegate: ComponentInstance;
    shared: sharedGlobalsPtr;
    (* add your own component specific globals here*)
  end;
  globalsPtr = ^globalsRecord;
  globalsHandle = ^globalsPtr;

You can extend this record to add your own instance globals. Do not delete or modify the existing fields; they are required by ICGenericOverride.p. You can however read the fields and use their values.

Note: Except when otherwise noted, the globals handle which is provided to each of the following API routines is not nil and is locked.

function ICSOInitShared (globals: globalsHandle): ComponentResult;

This routine is called to initialise the shared globals. If you return an error then you should make sure your part of the shared globals are 'clean'.

function ICSOCleanShared (globals: globalsHandle): ComponentResult;

This routine is called to clean the shared globals. If your shared globals contain any memory references, you should modify this routine to dispose of them.

Caution: This routine will never be called if you are running under an old version of the Component Manager. The workaround is to ignore the problem if your specific globals only bleed a small amounts of memory. If your specific globals bleed a lot of memory, or some other resource (such as open files), then you should refuse to install with older Component Managers. I think it was fixed in version 2 of the manager but you should check yourself.

function ICSOInitGlobals (globals: globalsHandle): ComponentResult;

This routine is called initialise the override specific fields of the instance globals. If it returns an error then the component instance is not created and the instance globals must be 'clean'.

function ICSOCleanGlobals (globals: globalsHandle): ComponentResult;

This routine is called to clean up the component specific globals, disposing any pointers and otherwise releasing any allocated resources.

function ICSOCanDo (globals: globalsHandle;
    selector: integer): ComponentResult;

This routine is called in response to a component CanDo request. You should set component result to:

-1, if you definitely want to say that the component can't do this

 0, if you definitely want to say that the component can do this

 1, if you want to let the delegate decide

Caution: These constants are quite different from the constants used by a standard Component Manager CanDo request.

Caution: Do not say you can do something if you require support from your delegate and it can't do that thing!

function ICSOWhatToOverride (globals: globalsHandle;
    selector: integer): ProcPtr;

Return nil if you do not want to override this selector. Return a pointer to a function with the appropriate signature if you do. The function must have the same parameters as the default implementation for this routine.

Caution: globals will not necessarily be locked and may be nil!!!

Sample Code

The current distribution provides two sample component that capture and extend the behaviour of the default Internet Config component. Although these sample programs are final code, writing override components is a complicated business and we are still unsure as to exactly how well these work. If you are interested in writing overriding component then you should be aware of their limitations. Please talk to us before you use these as the basis for your new, Way Cool overriding component.

6.2 Override Component Issues

This section documents some of the important issues for override components.

Preference Coherency

It is critical that your component maintains the following invariant: if any preferences that are not marked as volatile change then the seed must increase. If you do not maintain this invariant then you will break applications that are using the Cache Watching approach to preference consistency.

Seeds and ICBegin/ICEnd

It is critical that your application does not clash with programs using the Cache Watching approach to maintain preference consistency. This is surprisingly difficult to do! The important things to remember are that the seed in not valid within ICBegin/End pairs and that applications sample the seed after calling ICEnd. Conceivably it would be sensible to cache preference modifications inside ICBegin/ICEnd pairs and only write those preferences on the call to ICEnd. This would obviously modify the seed, which is perfectly sensible and would not cause any problems because the application shouldn't have sampled the seed yet. There is a possible problem if you want to change the preferences at ICEnd time and you want the application to notice these changes. The solution is to make the next two seeds return different values. Note that you shouldn't always return different values otherwise applications will be continuously refetching their cached preferences.

Readers and Writers

The Internet Config system uses a single writer or multiple readers approach to preference consistency. This means that either one writer or multiple readers can be accessing the preferences at any given point in time. Your component should enforce this restriction. You can determine whether a program is a reader or writer by watching the ICBegins.

At the moment, we're not entirely sure whether the current implementation actually does enforce this restriction, especially if a single application creates two instances. This is entirely besides the point!

Override Problems

The current implementation of the component supports component targeting, but the whole issue of component targeting is a tricky one. For example, it's really an implementation detail as to whether ICGetIndMapEntry calls ICGetMapEntry. Overriding base level component routines is tricky and you should be careful.

Having said that, it's important to note that Internet Config 1.0's support for targeting is incomplete. For example it calls the target component for default file name but not for the implicit ICBegin/ICEnd calls around a ICGetPref or ICSetPref and not for the ICGetPerm call. Your overriding component will have to deal with this. This situation is further complicated by the new routines added in Internet Config 1.2. IC 1.4 introduced a slightly reworked architecture for the IC component, so it now calls through the target in a much more thorough and consistent manner. If you developer an override component under IC 1.4, you should either test it thoroughly or refuse to load under earlier versions.

Locking Preferences

If you override a preference in such a way that a standard user interface is no longer appropriate for changing the preference then you should make sure to mark that preference as locked. This will prevent applications that provide their own user interface for changing preferences from attempting to change the preference. A good example of this is the RandomSignature component which locks the signature so that programs do not allow the user to edit the signature using their own user interface. Obviously if you do this then you need to provide an alternative interface for editing the preference.

Configuration Reference Overrides

If you need to add information to the configuration reference data returned by your delegate you can do so by override both the GetConfigReference and SetConfigReference calls. On the former you should call through to get the delegate's configuration reference and then change the manufacturer code and insert your data (including the original manufacturer code) in the handle between the manufacturer code and the delegate's information. When you see a SetConfigReference you should check for the presence of your manufacturer code, remove your data, change the manufacturer code back to the original and then call through to your delegate.


This document is Public Domain (really, we mean it!). No Rights Reserved

Comments: ic@stairways.com