$IF 0 Absender : dave@powerbasic.com (Dave Navarro) Betreff : Environment Variables, Part 1 Datum : Mo 10.11.97, 17:18 (erhalten: 11.11.97) Groesse : 5378 Bytes ---------------------------------------------------------------------- First, please excuse my cross-posting this into so many newsgroups. I see this question asked a lot and I'd like this code to help as many people as possible. This article does not really do this subject justice (I'm the first to admit it) but I hope it will help clear up some issues. Question: How do I set an environment variable from my application which can be accessed by other applications? Every time I use the ENVIRON statement in Basic, the variables I set or change are gone when my program ends. Answer: There are many different answers to this question and I'm sure most of you have read about "master environments", "global environments", "parent environments" and "local environments". Most programming languages which have statements or functions for accessing environment variables (such as ENVIRON and ENVIRON$ in Basic). These access what is called the "local" environment. That is, the environment which is actually owned by your application. Any applications which are executed by your program (the SHELL statement in Basic) will inherit your environment. So if you change or add a variable in your local environment, any applications which are executed by yours will have access to these changes. The biggest problem with this method is that when your application is executed by COMMAND.COM (or another application), DOS shrinks the size of the environment before it assigns it to your program. This was very important back in the early days of DOS because memory was sparse and every byte that could be saved was important. DOS measures memory in paragraphs (16-byte blocks), so it shrinks the block of memory used by the new environment down to as few paragraphs as it can. The result is that the local environment will have 15 bytes of extra space available at the most for any new variables or changes. I'm sure you've been frustrated many times when you've tried to change the PROMPT variable from your application only to get an "Out of Memory" error. The method for enlarging the local environment is a different topic entirely and if enough people are interested, I'll post another article on that subject. What most of us really want to do, however, is set a variable that will still be in the environment when our program ends. This is where the different types of environments becomes important. The following are my own definitions. The "local environment" is owned by your application. The "parent environment" is owned by the application which executed your applications (most often this is COMMAND.COM). The "master environment" is owned by COMMAND.COM (usually the same as the parent environment). The "global environment" is owned by the very first copy of COMMAND.COM loaded when your computer booted up (usually this is both the master and parent). Today, most of us run multi-tasking operating systems which allow us to run more than a single program at the same time. It's in these operating systems where "master" and "global" environments distinguish themselves. The global environment is the copy of COMMAND.COM which started your multi-tasker. While the master environment is the copy of COMMAND.COM which starts your "DOS box" in DesqView, Windows, Win95, etc. There's a lot of code around for accessing the "master" environment, which in my definition is really the "global" environment. The Int 2E backdoor (to see the code for this method, you may want to visit dejanews) gets you to the global environment. On a computer running DOS without a multi-tasker this is the same as the "master" environment (my definition) and most often is the same as the "parent" environment. Using the Int 2E method will work most of the time in this situation for what most people want to do with environment variables. However, if your purpose is to set and modify environment variables which run in the same context as your program (applications which are spawned by the same copy of COMMAND.COM that ran your program), this method is prone to failure. Particularly in a multi-tasker and when a secondary copy of COMMAND.COM has been executed which has in-turn executed your program. Example: An application executes a batch file by first launching a copy of COMMAND.COM. That batch file runs your program which sets a variable in the "global" environment. The next program in the batch file executes and attempts to access your environment variable which it expects to find in the environment of the COMMAND.COM executing the batch file. But its not there because you stored it in the global copy of COMMAND.COM, not the current copy of COMMAND.COM. The system breaks down and global war consumes the planet. However, if the purpose is for your programs which are "global environment aware" to communicate with each other, this technique will work just fine. Example: A DOS box is launched in Windows or DesqView which executes your program. Your program wants to see if any other copies of itself are running on the same computer in another DOS box, so it looks for a variable in the "global" environment. If found, it increments the count, otherwise it creates the variable. There is peace in the universe. Several years back I used to write utilities for BBS's (you still remember those, don't you? ). In my utilities I needed to access and modify environment variables so that other programs (usually other utilities that I had written) which ran in the same context (DOS box) as my own could access them. Since they were typically executed by the BBS in a batch file, I wrote my code to access the "parent" environment which was always the copy of COMMAND.COM that executed my program. This way, when my program finished and another was started in the batch file, my variables were still sitting there, waiting for them. My language of choice is PowerBASIC because it lets me mix Basic and assembler code together, although the following code should be readable enough that you can convert it to your favorite compiler. First, in order to find the parent environment, we need to find the PSP (Program Segment Prefix) of our own application. Fortunately, DOS gives us an interrupt to accomplish this: DIM psp AS WORD ! mov AX, &H6200 ; get current PSP segment ! int &H21 ; call DOS ! mov psp, BX ; it's returned in BX, so put it in our variable There are severs values stored in the PSP. But all we're interested in is one, at offset 22 is a word which is the segment for the PSP of the program which executed ours (the parent): DIM parent AS WORD ! mov ES, psp ; set ES to the segment of our PSP ! mov AX, ES:[22] ; get the segment for our parent's PSP ! mov parent, AX ; put it in our variable At offset 44 in the PSP is the segment where the environment is located: DIM environment AS WORD ! mov ES, parent ; set ES to the segment of our parent's PSP ! mov AX, ES:[44] ; get the segment for its environment ! mov environment, AX ; put it in our variable The environment is a serious of null terminated strings, one after the other. The end of the list is a null all by itself. Personally, I've found it easier to "meddle" with environment variables by first copying them into a string array: DIM x AS WORD PTR 'we need a pointer to find the end of 'the environment variables x = environment * 65536 'convert the environment segment into 'a memory address ' using a word pointer allows us to access two bytes of memory at the ' same time. WHILE @x 'if both bytes are not zero... INCR x '...add one to the memory address... WEND '...and keep looping Okay, our variable "environment" now points to the memory address which is the end of the environment variable. First, let's calculate the length and get all of the variables into a single dynamic string where we can easily parse them: DIM l AS INTEGER DIM buffer AS STRING l = x AND &HFF 'isolate the lower half of the memory address... '...which will contain the offset to the end of the '...variable block DEF SEG = environment buffer = PEEK$(0, l) 'put all of the variables into a single string DEF SEG Now we can parse them into a dynamic string array where they will be easier to manipulate: DIM env(1 to 100) AS STRING DIM count AS INTEGER DIM null AS INTEGER DO null = INSTR(buffer, CHR$(0)) 'find the next null INCR count IF null THEN env(count) = EXTRACT$(buffer, CHR$(0)) 'extract everything up... '...to the null buffer = MID$(buffer, null + 1) 'and remove from the buffer ELSE env(count) = buffer buffer = "" END IF LOOP WHILE LEN(buffer) Now "count" has the total number of environment variables and env() contains all of them. Each string is formatted with the name of the variable first, followed by an equal sign, followed by the value for the variable. From here you can change them, remove them or add to them as you see fit. Okay, you've done all the modifications that you wanted to do, so now you need to put them back into memory where other programs can access them. First, we need to build our dynamic string buffer with all of the variables: DIM i AS INTEGER buffer = "" 'clear out the old buffer FOR i = 1 TO count buffer = buffer + env(i) + CHR$(0) 'don't forget the null terminator NEXT i buffer = buffer + CHR$(0) 'and we need to indicate that there are... '...no more variables Now we can simply put the buffer back into memory: DEF SEG = environment POKE$ 0, buffer END DEF The following PowerBASIC program will sort all of the variables in the environment by name: $ENDIF '------------------------------------------------------------------------ '* Environment test code '* by Dave Navarro, Jr. (dave@powerbasic.com) '* donated to the public domain $DIM ALL 'require all variables to be declared first '* First create all of our variables DIM buffer AS STRING DIM env(1 to 100) AS STRING DIM i AS INTEGER DIM l AS INTEGER DIM count AS INTEGER DIM null AS INTEGER DIM psp AS WORD DIM parent AS WORD DIM environment AS WORD DIM x AS WORD PTR '* Get the PSP for our program ! mov AX, &H6200 ! int &H21 ! mov psp, BX '* Get the PSP for our parent ! mov ES, psp ! mov AX, ES:[22] ! mov parent, AX '* Get the environment segment for our parent ! mov ES, parent ! mov AX, ES:[44] ! mov environment, AX '* Get the length of all the variables in the environment x = environment * 65536?? WHILE @x INCR x WEND l = CINT(x AND &HFFFF??) '* Put all of the environment variables into a '* single dynamic string DEF SEG = environment buffer = PEEK$(0, l) DEF SEG '* Now parse them into our array DO null = INSTR(buffer, CHR$(0)) INCR count IF null THEN env(count) = LEFT$(buffer, null - 1) buffer = MID$(buffer, null + 1) ELSE env(count) = buffer buffer = "" END IF LOOP WHILE LEN(buffer) '* Sort the array ARRAY SORT env() FOR count '* Build the buffer buffer = "" FOR i = 1 TO count buffer = buffer + env(i) + CHR$(0) NEXT i buffer = buffer + CHR$(0) '* And put the variables back into memory DEF SEG = environment POKE$ 0, buffer DEF SEG SHELL "SET"