<> <> <> <> DIRECTORY Ascii USING [CR], BcdDefs USING [ Base, BcdBase, Link, CTHandle, CTIndex, CTNull, EVNull, FPHandle, FPIndex, ModuleIndex, MTHandle, MTIndex, NameString, VarLimit, VersionID], BcdOps USING [ProcessConfigs, ProcessFramePacks, ProcessModules], ConvertUnsafe USING [AppendRope], IO USING [card, PutChar, PutF, PutFR, PutRope, rope, STREAM], MB USING [ Abort, AllocateFrames, BHandle, BIndex, EnumerateControlList, EnumerateFramePacks, EnumerateGlobalFrames, Error, Handle, ModuleInfo, ModuleInfoSequence, MT], MBLoaderOps USING [ Bind, ConfigIndex, EnterModule, FileNotFound, FindFiles, FindCode, GetModule, GetNextGFI, GetVirtualLinks, InputLoadState, LinkFragLength, ModuleInfo, ProcessUnboundImports, RealLinks, RealLinksTable, SetGFTEntry, UpdateLoadState, VersionMismatch, VirtualLinks, WriteLoadState, WriteLinks], MBVM USING [AllocFile, Base, CopyWrite, FileSeg, HyperSpace, Read, Write], PrincOps USING [ AV, ControlLink, ControlModule, FrameCodeBase, GFTIndex, GlobalFrame, GlobalFrameHandle, NullControl, NullLink, UnboundLink], PrincOpsUtils USING [BITAND], RuntimeInternal USING [MakeFsi], Segments USING [ BaseFromSegment, DeleteSegment, FHandle, FileFromSegment, HardUp, MoveSegment, NewFile, NewSegment, PagesFromSegment, Read, SegmentAddress, SHandle, SwapIn, Unlock]; MBLoaderCore: PROGRAM IMPORTS BcdOps, ConvertUnsafe, IO, MB, MBLoaderOps, MBVM, PrincOpsUtils, RuntimeInternal, Segments EXPORTS MB, MBLoaderOps = BEGIN OPEN MB; CMTable: TYPE = RECORD [SEQUENCE length: NAT OF CMEntry]; CMEntry: TYPE = RECORD [bh: MB.BHandle, cm: PrincOps.ControlModule]; data: MB.Handle _ NIL; controlModules: REF CMTable _ NIL; InitLoaderCore: PUBLIC PROC [h: MB.Handle] = {data _ h}; FinishLoaderCore: PUBLIC PROC = { FOR i: MB.BIndex IN [0..data.inputBCDs.nBcds) DO loadee: MB.BHandle = data.inputBCDs.bcds[i]; IF loadee.bcdSegment ~= NIL THEN { Segments.Unlock[loadee.bcdSegment]; Segments.DeleteSegment[loadee.bcdSegment]; }; ENDLOOP; data _ NIL; }; Load: PUBLIC PROC = { typescript: IO.STREAM = data.typescript; loadmap: IO.STREAM = data.loadmap; versionError: BOOL _ FALSE; controlModules _ NEW[CMTable[data.inputBCDs.nBcds]]; data.nModules _ 0; FOR i: MB.BIndex IN [0..data.inputBCDs.nBcds) DO loadee: MB.BHandle = data.inputBCDs.bcds[i]; name: STRING _ [40]; missingCodeFile: BOOL _ FALSE; config: MBLoaderOps.ConfigIndex; ConvertUnsafe.AppendRope[to: name, from: loadee.name]; typescript.PutF["Loading %g...", IO.rope[loadee.name]]; IF (loadee.bcdSegment _ LoadBcd[Segments.NewFile[name]]) = NIL THEN { typescript.PutF["\N! Invalid file %g", IO.rope[loadee.name]]; ERROR MB.Abort }; loadmap.PutF["Global Frames for Modules in %g:\N", IO.rope[loadee.name]]; loadee.bcd _ Segments.SegmentAddress[loadee.bcdSegment]; IF ~data.germ THEN { loadee.bcdSeg _ MBVM.AllocFile[ file: Segments.FileFromSegment[loadee.bcdSegment], fileBase: Segments.BaseFromSegment[loadee.bcdSegment], base: MBVM.HyperSpace, pages: Segments.PagesFromSegment[loadee.bcdSegment]]; loadee.bcdSeg.segment _ loadee.bcdSegment; loadee.bcdSeg.bIndex _ i; }; MBLoaderOps.FindFiles[loadee ! MBLoaderOps.FileNotFound => { typescript.PutF["\N! Can't find file %g", IO.rope[name]]; missingCodeFile _ TRUE; RESUME } ]; IF missingCodeFile THEN { typescript.PutChar[Ascii.CR]; MB.Error["One or more missing code files"]; }; config _ LoadModules[loadee]; typescript.PutRope["looking up code..."]; MBLoaderOps.FindCode[loadee]; typescript.PutRope["binding modules..."]; controlModules[i] _ [bh: loadee, cm: AssignControlModules[loadee]]; IF config = FIRST[MBLoaderOps.ConfigIndex] THEN JustRelocateLinks[loadee] ELSE MBLoaderOps.Bind[loadee, config ! MBLoaderOps.VersionMismatch => { typescript.PutF["\N! Version mismatch: %g, [%g, %g]", IO.rope[interface], IO.rope[ref1], IO.rope[ref2]]; versionError _ TRUE; RESUME } ]; loadmap.PutChar[Ascii.CR]; typescript.PutRope["done\N"]; ENDLOOP; data.header.controlList _ BuildTopLevelControlList[]; MBLoaderOps.WriteLoadState[]; data.nGFIs _ MBLoaderOps.GetNextGFI[reserve: 0]; loadmap.PutF["\NTotal of %d modules, %d GFIs\N", IO.card[data.nModules], IO.card[data.nGFIs]]; IF versionError THEN MB.Error["One or more version mismatches"]; MBLoaderOps.ProcessUnboundImports[]; typescript.PutRope["Finished loading.\N"]; }; LoadBcd: PROC [bcdfile: Segments.FHandle] RETURNS [bcdseg: Segments.SHandle] = { b: BcdDefs.BcdBase; pages: CARDINAL; bcdseg _ Segments.NewSegment[file: bcdfile, base: 1, pages: 1, access: Segments.Read]; Segments.SwapIn[seg: bcdseg, info: Segments.HardUp]; b _ Segments.SegmentAddress[bcdseg]; IF b.versionIdent ~= BcdDefs.VersionID OR b.definitions --OR ~b.spare1-- THEN { Segments.Unlock[bcdseg]; Segments.DeleteSegment[bcdseg]; RETURN[NIL] }; IF (pages _ b.nPages) > 1 THEN { Segments.Unlock[bcdseg]; Segments.MoveSegment[bcdseg, 1, pages]; Segments.SwapIn[seg: bcdseg, info: Segments.HardUp]; }; }; SetupModuleTable: PROC [loadee: MB.BHandle] RETURNS [ngfi: CARDINAL _ 0] = { <> CountEntries: PROC [mth: BcdDefs.MTHandle, mti: BcdDefs.MTIndex] RETURNS [stop: BOOL _ FALSE] = { ngfi _ ngfi + mth.ngfi; }; SetEntriesForOneModule: PROC [mth: BcdDefs.MTHandle, mti: BcdDefs.MTIndex] RETURNS [stop: BOOL _ FALSE] = { FOR i: CARDINAL IN [mth.gfi..mth.gfi+mth.ngfi) DO loadee.mt[i] _ MB.ModuleInfo[mth: mth, frame: NIL, code: NIL]; ENDLOOP; }; [] _ BcdOps.ProcessModules[loadee.bcd, CountEntries]; loadee.mt _ NEW[MB.ModuleInfoSequence[ngfi+1]]; -- +1 because index 0 isn't used loadee.mt[0] _ MB.ModuleInfo[mth: NIL, frame: NIL, code: NIL]; [] _ BcdOps.ProcessModules[loadee.bcd, SetEntriesForOneModule]; }; LoadModules: PROC [loadee: MB.BHandle] RETURNS [config: MBLoaderOps.ConfigIndex] = BEGIN bcd: BcdDefs.BcdBase = loadee.bcd; mtb: BcdDefs.Base = LOOPHOLE[bcd + bcd.mtOffset]; mt: MT; ngfi: CARDINAL; gfiOffset: CARDINAL; framePtr: POINTER; frameSpace: CARDINAL _ 0; useFrameHeap: BOOL = (bcd.nModules = 1); inFrameHeap: BOOL; framesResident: BOOL _ FALSE; ProcessOneFramePack: PROC [fph: BcdDefs.FPHandle, fpi: BcdDefs.FPIndex] RETURNS [BOOL] = { frameSpace _ 0; framesResident _ FALSE; FOR i: CARDINAL IN [0..fph.length) DO GetFrameSize[@mtb[fph.modules[i]]]; ENDLOOP; [framePtr, inFrameHeap] _ MB.AllocateFrames[ size: NextMultipleOfFour[frameSpace], single: useFrameHeap, resident: framesResident OR ResidentFramePack[fph] -- specified in bootmesa file--]; FOR i: CARDINAL IN [0..fph.length) DO FrameInit[fph.modules[i]]; ENDLOOP; RETURN[FALSE] }; GetFrameSize: PROC [mth: BcdDefs.MTHandle] = { IF ~(mth.linkLoc = code AND mth.code.linkspace) THEN -- frame links frameSpace _ frameSpace + MBLoaderOps.LinkFragLength[loadee, mth]; frameSpace _ NextMultipleOfFour[frameSpace] + mth.framesize; framesResident _ framesResident OR mth.residentFrame; }; FrameInit: PROC [mti: BcdDefs.MTIndex] = { mth: BcdDefs.MTHandle = @mtb[mti]; frame: PrincOps.GlobalFrameHandle; gf: PrincOps.GlobalFrame; gfi: PrincOps.GFTIndex = gfiOffset + mth.gfi; -- module's biased gfi framelinks: BOOL = ~(mth.linkLoc = code AND mth.code.linkspace); nLinks: CARDINAL = MBLoaderOps.LinkFragLength[loadee, mth]; data.nModules _ data.nModules + 1; <> IF framelinks THEN framePtr _ (framePtr + nLinks); frame _ NextMultipleOfFour[framePtr]; framePtr _ (frame + mth.framesize); <> FOR i: CARDINAL IN [0..mth.ngfi) DO mt[mth.gfi + i].frame _ frame; ENDLOOP; <> MBLoaderOps.SetGFTEntry[frame: frame, gfi: gfi, ngfi: mth.ngfi]; FOR i: CARDINAL IN [0..mth.ngfi) DO MBLoaderOps.EnterModule[ rgfi: (gfi + i), module: MBLoaderOps.ModuleInfo[ config: config, module: mth.gfi + i, resolved: nLinks = 0], mti: mti ]; ENDLOOP; <> gf _ PrincOps.GlobalFrame[ gfi: gfi, copied: FALSE, alloced: inFrameHeap, shared: FALSE, started: FALSE, trapxfers: FALSE, codelinks: ~framelinks, global: , code: PrincOps.FrameCodeBase[offset[offset: mth.code.offset, highHalf: 0]] ]; gf.code.out _ TRUE; MBVM.CopyWrite[from: @gf, to: frame, nwords: SIZE[PrincOps.GlobalFrame]]; MBVM.Write[@frame.global[0], PrincOps.NullControl]; -- no control module PrintLoadmapEntry[bcd: bcd, mth: mth, frame: frame, gfiOffset: gfiOffset]; }; OtherFrameSizes: PROC [mth: BcdDefs.MTHandle, mti: BcdDefs.MTIndex] RETURNS [BOOL] = { IF mt[mth.gfi].frame = NIL THEN GetFrameSize[mth]; RETURN[FALSE] }; OtherFrameInit: PROC [mth: BcdDefs.MTHandle, mti: BcdDefs.MTIndex] RETURNS [BOOL] = { IF mt[mth.gfi].frame = NIL THEN FrameInit[mti]; RETURN[FALSE] }; ngfi _ SetupModuleTable[loadee]; mt _ loadee.mt; config _ MBLoaderOps.InputLoadState[]; <> loadee.gfiOffset _ gfiOffset _ MBLoaderOps.GetNextGFI[reserve: ngfi] - 1; [] _ BcdOps.ProcessFramePacks[bcd, ProcessOneFramePack]; frameSpace _ 0; framesResident _ FALSE; [] _ BcdOps.ProcessModules[bcd, OtherFrameSizes]; IF frameSpace ~= 0 THEN { -- there are frames not in frame packs [framePtr, inFrameHeap] _ MB.AllocateFrames[ size: NextMultipleOfFour[frameSpace], single: useFrameHeap, resident: framesResident OR AnyResidentGlobalFrames[loadee]]; [] _ BcdOps.ProcessModules[bcd, OtherFrameInit]}; END; ResidentFramePack: PROC [f: BcdDefs.FPHandle] RETURNS [resident: BOOL] = { CheckOne: PROC [bh: MB.BHandle, fph: BcdDefs.FPHandle] RETURNS [BOOL] = { RETURN[resident _ (f = fph)]}; MB.EnumerateFramePacks[resident, CheckOne]; }; AnyResidentGlobalFrames: PROC [loadee: MB.BHandle] RETURNS [resident: BOOL] = { CheckOne: PROC [bh: MB.BHandle, mth: BcdDefs.MTHandle] RETURNS [BOOL] = { RETURN[resident _ (bh = loadee)]}; MB.EnumerateGlobalFrames[resident, CheckOne]; }; NextMultipleOfFour: PROC [x: UNSPECIFIED] RETURNS [UNSPECIFIED] = INLINE { RETURN[x + PrincOpsUtils.BITAND[-LOOPHOLE[x, INTEGER], 3B]]}; PrintLoadmapEntry: PROC [ bcd: BcdDefs.BcdBase, mth: BcdDefs.MTHandle, frame: POINTER, gfiOffset: CARDINAL] = { ssb: BcdDefs.NameString _ LOOPHOLE[bcd + bcd.ssOffset]; data.loadmap.PutF[" New: g = %06n ", IO.card[LOOPHOLE[frame, CARDINAL].LONG]]; FOR i: CARDINAL IN [mth.name .. mth.name+ssb.size[mth.name]) DO data.loadmap.PutChar[ssb.string.text[i]]; ENDLOOP; data.loadmap.PutF[" [%n]\N", IO.card[LOOPHOLE[PrincOps.ControlLink[procedure[gfi: gfiOffset + mth.gfi, ep: 0, tag: FALSE]]].LONG]]; }; <> CMMapItem: TYPE = RECORD [ cti: BcdDefs.CTIndex, cm: PrincOps.ControlModule, depth: CARDINAL ]; AssignControlModules: PROC [loadee: MB.BHandle] RETURNS [cm: PrincOps.ControlModule] = { bcd: BcdDefs.BcdBase = loadee.bcd; mt: MB.MT = loadee.mt; ctb: BcdDefs.Base _ LOOPHOLE[bcd + bcd.ctOffset]; mtb: BcdDefs.Base _ LOOPHOLE[bcd + bcd.mtOffset]; CMMap: TYPE = RECORD [ nConfigs: CARDINAL, configs: SEQUENCE length: CARDINAL --subconfig#-- OF CMMapItem]; cmMap: REF CMMap; maxDepth: CARDINAL _ 0; cti: BcdDefs.CTIndex; EnterControlInfoForConfig: PROC [ cth: BcdDefs.CTHandle, cti: BcdDefs.CTIndex] RETURNS [stop: BOOL] = { cm: PrincOps.ControlModule; depth: CARDINAL _ 0; -- depth of config in Bcd's config structure IF cth.nControls = 0 THEN cm _ PrincOps.NullControl ELSE { <> cm _ AllocateControlList[cth.nControls]; MBVM.Write[@cm.list.nModules, (cth.nControls + 1)]; FOR i: CARDINAL IN [0..cth.nControls) DO WITH cItem: cth.controls[i] SELECT FROM module => MBVM.Write[@cm.list.frames[i+1], mt[mtb[cItem.mti].gfi].frame]; config => MB.Error["Configurations in control lists aren't supported yet."]; ENDCASE; ENDLOOP; cm.multiple _ TRUE; FOR c: BcdDefs.CTIndex _ ctb[cti].config, ctb[c].config UNTIL c = BcdDefs.CTNull DO depth _ depth + 1; ENDLOOP; }; maxDepth _ MAX[maxDepth, depth]; cmMap.configs[cmMap.nConfigs] _ CMMapItem[cti: cti, cm: cm, depth: depth]; cmMap.nConfigs _ cmMap.nConfigs + 1; RETURN[FALSE] }; IF bcd.nModules = 1 THEN { frame: PrincOps.GlobalFrameHandle _ mt[1].frame; MBVM.Write[@frame.global[0], PrincOps.NullControl]; RETURN [[frame[frame]]] }; < (subconfig & control module info) map>> cmMap _ NEW[CMMap[bcd.nConfigs]]; cmMap.nConfigs _ 0; [] _ BcdOps.ProcessConfigs[bcd, EnterControlInfoForConfig]; FOR depth: CARDINAL DECREASING IN [0..maxDepth] DO FOR index: CARDINAL IN [0..cmMap.nConfigs) DO list, listHead, oldCm: PrincOps.ControlModule; SetModulesControl: PROC [ mth: BcdDefs.MTHandle, mti: BcdDefs.MTIndex] RETURNS [stop: BOOL] = { frame: PrincOps.GlobalFrameHandle _ mt[mth.gfi].frame; IF mth.config ~= cti THEN RETURN[FALSE]; IF MBVM.Read[@frame.global[0]] = PrincOps.NullControl THEN MBVM.Write[@frame.global[0], GetLink[cm]]; RETURN[FALSE] }; IF cmMap.configs[index].depth ~= depth OR (cm _ cmMap.configs[index].cm) = PrincOps.NullControl THEN LOOP; list _ cm; list.multiple _ FALSE; cti _ cmMap.configs[index].cti; <> listHead _ SetLink[cm: cm, frame: MBVM.Read[@list.list.frames[1]]]; MBVM.Write[@list.list.frames[1], listHead]; -- restore old value FOR i: CARDINAL IN [2..ctb[cti].nControls+1) DO oldCm _ SetLink[cm: GetLink[listHead], frame: MBVM.Read[@list.list.frames[i]]]; MBVM.Write[@list.list.frames[i], oldCm]; ENDLOOP; <> [] _ BcdOps.ProcessModules[bcd, SetModulesControl]; ENDLOOP; ENDLOOP; <> FOR index: CARDINAL IN [0..cmMap.nConfigs) DO parent: CARDINAL; list: PrincOps.ControlModule _ cmMap.configs[index].cm; IF list = PrincOps.NullControl THEN LOOP; list.multiple _ FALSE; IF (cti _ ctb[cmMap.configs[index].cti].config) = BcdDefs.CTNull THEN cm _ PrincOps.NullControl ELSE { FOR parent IN [0..cmMap.nConfigs) DO IF cmMap.configs[parent].cti = cti THEN EXIT; ENDLOOP; cm _ GetLink[cmMap.configs[parent].cm]; }; MBVM.Write[@list.list.frames[0], cm]; ENDLOOP; <> FOR i: CARDINAL IN [0..cmMap.nConfigs) DO IF ctb[cmMap.configs[i].cti].config = BcdDefs.CTNull THEN { cm _ GetLink[cmMap.configs[i].cm]; EXIT }; ENDLOOP; }; AllocFault: PUBLIC ERROR = CODE; Alloc: PUBLIC PROC [fsi: CARDINAL] RETURNS [p: POINTER] = { DO p _ MBVM.Read[PrincOps.AV + fsi]; SELECT LOOPHOLE[p, CARDINAL] MOD 4 FROM 0 => EXIT; -- a free frame 1, 3 => ERROR AllocFault; 2 => fsi _ LOOPHOLE[p, CARDINAL]/4; -- use an fsi for larger frames ENDCASE; ENDLOOP; MBVM.Write[PrincOps.AV + fsi, MBVM.Read[p]]; RETURN[p] }; AllocateControlList: PUBLIC PROC [nControls: CARDINAL] RETURNS [cm: PrincOps.ControlModule] = { fsi: CARDINAL = RuntimeInternal.MakeFsi[words: (nControls + 1) + 1]; cm.list _ Alloc[fsi ! AllocFault => MB.Error[IO.PutFR["Larger frame heap needed (fsi=%d) for control list", IO.card[fsi]]] ]; }; GetLink: PROC [ cm: PrincOps.ControlModule] RETURNS [--frame--PrincOps.ControlModule] = { list: PrincOps.ControlModule; DO -- search up backward pointers for the actual frame to start IF ~cm.multiple THEN RETURN[cm]; list _ cm; list.multiple _ FALSE; cm _ MBVM.Read[@list.list.frames[1]]; ENDLOOP; }; SetLink: PROC [cm: PrincOps.ControlModule, frame: PrincOps.GlobalFrameHandle] RETURNS [PrincOps.ControlModule] = { old: PrincOps.ControlModule = MBVM.Read[@frame.global[0]]; MBVM.Write[@frame.global[0], cm]; RETURN[IF old ~= PrincOps.NullControl THEN old ELSE [frame[frame]]] }; JustRelocateLinks: PROC [loadee: MB.BHandle] = { bcd: BcdDefs.BcdBase = loadee.bcd; mt: MB.MT = loadee.mt; realLinks: MBLoaderOps.RealLinks = NEW[MBLoaderOps.RealLinksTable]; RelocateLinksForModule: PROC [mth: BcdDefs.MTHandle, mti: BcdDefs.MTIndex] RETURNS [BOOL] = { mi: BcdDefs.ModuleIndex = mth.gfi; frame: PrincOps.GlobalFrameHandle _ mt[mi].frame; resolved: BOOL _ TRUE; virtualLinks: MBLoaderOps.VirtualLinks _ MBLoaderOps.GetVirtualLinks[loadee, mth]; IF LENGTH[virtualLinks] ~= 0 THEN { -- relocate external links FOR i: CARDINAL IN [0..LENGTH[virtualLinks]) DO link: BcdDefs.Link _ virtualLinks[i]; SELECT link.vtag FROM proc1, proc0 => IF link.gfi >= bcd.firstdummy THEN {resolved _ FALSE; realLinks[i] _ PrincOps.UnboundLink} ELSE realLinks[i] _ ConvertLink[link]; var => IF link.gfi >= bcd.firstdummy THEN {resolved _ FALSE; realLinks[i] _ PrincOps.NullLink} ELSE realLinks[i] _ ConvertVariableLink[link]; ENDCASE => {resolved _ FALSE; realLinks[i] _ PrincOps.NullLink}; ENDLOOP; MBLoaderOps.WriteLinks[loadee: loadee, mth: mth, links: realLinks]; }; FOR i: CARDINAL IN [mi..mi+mth.ngfi) DO info: MBLoaderOps.ModuleInfo _ MBLoaderOps.GetModule[i]; info.resolved _ resolved; MBLoaderOps.EnterModule[i, info]; ENDLOOP; RETURN[FALSE] }; ConvertVariableLink: PROC [link: BcdDefs.Link] RETURNS [PrincOps.ControlLink] = { mth: BcdDefs.MTHandle; frame: PrincOps.GlobalFrameHandle; mi: BcdDefs.ModuleIndex = link.gfi; evb: BcdDefs.Base = LOOPHOLE[bcd + bcd.evOffset]; vp: CARDINAL; [mth: mth, frame: frame] _ mt[mi]; IF mi >= bcd.firstdummy THEN RETURN[PrincOps.NullLink]; vp _ BcdDefs.VarLimit*(mi - mth.gfi) + link.var; IF vp = 0 THEN RETURN[LOOPHOLE[frame]]; IF mth.variables = BcdDefs.EVNull THEN RETURN[PrincOps.NullLink] ELSE RETURN[LOOPHOLE[frame + evb[mth.variables].offsets[vp]]] }; MBLoaderOps.UpdateLoadState[FIRST[MBLoaderOps.ConfigIndex], loadee]; [] _ BcdOps.ProcessModules[bcd, RelocateLinksForModule]; }; ConvertLink: PROC [bl: BcdDefs.Link] RETURNS [PrincOps.ControlLink] = INLINE { RETURN[LOOPHOLE[bl]]}; BuildTopLevelControlList: PROC RETURNS [cm: PrincOps.ControlModule] = { nControls: CARDINAL _ 0; index: CARDINAL _ 1; CountControls: PROC [bh: MB.BHandle, cth: BcdDefs.CTHandle] RETURNS [BOOL _ FALSE] = { IF cth.nControls ~= 0 THEN nControls _ nControls + 1; }; FillInControls: PROC [bh: MB.BHandle, cth: BcdDefs.CTHandle] RETURNS [BOOL _ FALSE] = { IF cth.nControls ~= 0 THEN { FOR i: NAT IN [0..controlModules.length) DO IF controlModules[i].bh = bh THEN { MBVM.Write[@cm.list.frames[index], controlModules[i].cm]; index _ index + 1; EXIT }; ENDLOOP; }; }; [] _ MB.EnumerateControlList[CountControls]; IF nControls = 0 THEN RETURN[PrincOps.NullControl]; cm _ AllocateControlList[nControls]; MBVM.Write[@cm.list.nModules, nControls + 1]; MBVM.Write[@cm.list.frames[0], PrincOps.NullControl]; [] _ MB.EnumerateControlList[FillInControls]; cm.multiple _ TRUE; }; END.