Subject: Re: Dynamic Loadable Segments?
From: [email-masked] (John S. Heaney)
Date: 21 Mar 1994 14:29:46 -0800
Newsgroups: comp.sys.apple2.programmer

In article <[email-masked]>, Joe Busnengo  <[email-masked]> wrote:
>Right.  Maybe I should have been a little more clear in my original message.
>I'm talking about loading segments from _separate load files_ that may or may
>not be present when the program is running.  What I'm saying is:  how would I
>use routines in those load files.  I assume that I'd use LoadSegName, but after
>that I'm stuck...  I know that it can be done, because PRIZM can debug a
>program of yours while it is still running.  I would like to know how!  :)

The key routines are InitialLoad or InitialLoad2, UserShutDown, GetNewID
and DeleteID. I wrote a program using videodisc drivers. Each player had
its own driver, which the user selected from a list. After the driver was
selected I had to unload any old driver and load the new one. Subsequent
calls to the driver were routed through a single vector that was returned
when the driver was loaded. Here are some code snippets. Note that this
was written using the MPW IIGS Assembler.

A new ID can be returned from InitialLoad without using GetNewID. And
UserShutDown can free up the new UserID without calling Delete ID. I do
not remember why I did that separately. It would probably be better not
to. 

;   unload any existing player driver and load a new one specified in
;    the player name handle.  Set prefix 0 before calling.
;
loadNewDriver   PROC        EXPORT

    EXPORT  unLoadOldDriver

;global variables
;
    IMPORT  segName
    IMPORT  playerDispatch

;external procedures
;

    jsl     unLoadOldDriver ;if there is one

    newDP   v1              ;set's up a direct page referenced by d1.

    lda     #pInitStatus    ;driver starts in an initial state
    sta     d1.driverStatus

    ldax    d1.playerName   ;lock the driver pathname handle
    jsl     deref           ; and get the pointer
    stax    d1.driverListPtr        

;------------------------------------------------------------
;
;  get a new user ID for the driver
;
    wordResult
    pea     $1000   ;application type ID
    _GetNewID
    pla
    sta     d1.driverID

    wordResult      ;for new user ID if not using GetNewID
    longResult      ;for start address of loaded program segment
    wordResult      ;for Direct Page/stack address
    wordResult      ;for Direct Page/stack buffer size
    lda     d1.driverID ;new user ID just for driver
    pha
    pushlong    d1.driverListPtr    ;pathname address ptr
    pea     0       ;flag - use any memory
    _InitialLoad
    _ToolErrors dispErrFlag ;my error reporting
    pla             ;already have user ID stored
    pulllong    d1.driverAddr   ;address of loaded segment
    pla             ;don't need
    pla             ;don't need

    php
        jsl     quickUnLock ;unlock the filename handle
    plp
    pld
    rtl

unLoadOldDriver
    newDP   v1              ;set's up a direct page referenced by d1.

    lda     d1.driverAddr+1         ;If there is a loaded driver
    beq     noLoadDriver
        lda     d1.driverStatus     ;   If the driver is initialized
        brTrue  @notInit
            _KillPlayer             ;       reset the player
@notInit                            ;   endif
        wordResult                  ;   echos user ID
        pei     d1.driverID         ;   ID for the driver
        pea     0                   ;   purge with extreme prejudice
        _UserShutDown
        _ToolErrors dispErrFlag
        pla

        pei     d1.driverID
        _DeleteID
        stz     d1.driverAddr       ;   no driver loaded; clear address
        stz     d1.driverAddr+2
noLoadDriver                        ;endif
    pld
    rtl

    ENDP

As for actually calling the driver, I chose to make it easy on myself. I
just use the same direct page for the caller and the driver. There is one
entry point to the driver and I put a command number in the x register,
which is used to fetch the routine pointer from a call table. I also use
the a and y registers for passing parameters. Obviously, you could pass a
parameter block pointer instead. 

So to call the driver I did something like this:

playerDispatch  PROC    EXPORT

    newDP   v1              ;set's up a direct page referenced by d1.

    lda     d1.driverAddr+1         ;has a driver been installed?
    bne     @gotDriver              ;yes

        ;Try installing a default player
        jsl     getDefPlayer
        bcc     @gotDriver

            ;For whatever reason there is none.  Alert the user.
            ldx     #noDriverAlert
            jsr     driverAlert
            bra     exitDriver

@gotDriver  ;A driver is installed.  See what the driver status is.

There some other stuff in here, but the important thing is the call itself,
which looks like the following. Note that I set my program bank to 0. The
reference to v1.driverAddr is an absolute addressing reference to 
d1.driverAddr above, which is a direct page reference to the same location.

        phk                     ;fake a subroutine call
        pea     firstReturn-1
        jmp     [v1.driverAddr]
firstReturn
        bcc     @goodInit
            jsr     failedInit
            bcc     @goodInit   ;possible if the error was just a warning
                bra     exitDriver
@goodInit

The driver itself receives the call and jumps to the correct routine based
on the x register.

;-----------------------------------------------
; Top level for driver.  Sets up for calling the individual routines.
; Parameters can be passed both ways in a/y.
;
mainEntry
    phb                     ;save the data bank
    phk                     ;set data bank=code bank
    plb

    pha                     ;save a and reserve an RTS slot
        tsc                     ;save the stack pointer
        sta     restoreStack
    pla                     ;get register A back

    cpx     #callTableEnd-callTable-1
    bge     @invalidCall
        jsr     (callTable,x)
        ;lda restoreStack/tcs/rts will bring us here
    bra     @endInvalid
@invalidCall
        sec
        lda     #pErrUnsupported
@endInvalid
    LONG                    ;just in case
    
    bcs     @playerError    ;If there is no error
        stz     d1.driverStatus ;let everyone know
@playerError
    plb
    rtl

;These are all of the calls that need to be handled by the driver.  Calls 
that are
;not supported by the driver should return pErrUnsupported with the carry 
set.
;all calls return with an rts.
callTable
            dc.w    initCode
            dc.w    startDisc
            dc.w    chapterSearch
            dc.w    frameSearch
            dc.w    playDisc
            dc.w    version
            dc.w    model
            etc....you don't need to see them all.
callTableEnd

;**************************************************************************
;   Initialize communications for this player
;   Enter with  a = playerPort
;               y = initCode, which is a player option that has previously
;                   been used to successfully init the player.  0 means that
;                   the player has never been initialized.
;   Returns the successful player option in a.
;
initCode
    etc....

Sorry if it looks like a mess. I threw it together as fast as I could. It 
should get you thinking in the right direction. Hope it helps.

-- 
John Heaney              The Devil is in the details.
[email-masked]