pp
project, unlike the others, simply did not
work well as a library. The interface could be clean, but at
the cost of having the thread of control pass to the plotting
function, never to return. We could throw out the library,
and launch pp
as a program using named pipes to
send it data, which would allow the thread of control to
continue in the original application, but at the cost of a
fair bit of programming overhead ... which you saw in the
solution I gave in the last class. So, what to do about it?
One way to deal with a situation like this is to make a library that has the same interface as the original, something like
wplot(vector<double> & X, vector<double> & Y, string type);in which the implementation goes through all the trouble of spawning the new
pp
process, setting up the named
pipe, sending the data down the named pipe, and removing the
named pipe. This was the approach I told you take in class,
and this is what you spent most of class working on.
pp
in our new library. If pp
moves, of course, our library breaks --- and it can't be fixed
without recompiling the library and, if it's a static library,
relinking all the applications that use it. Worse, different
users on different systems will doubtless have pp
located elsewhere, and for them our library is useless!
The typical was to deal with this is to not hard-code
pp
's location in our library. Instead, we let
the user provide pp
's location via an
environment variable. Every user has environment
variables --- like the LD_LIBRARY_PATH
variable
we've already seen --- which control many aspects of their
interaction with Unix. If you type
setenvyou'll get a listing of all your environment variables. They are often defined in your .cshrc or .login files. If we assume that there's an environment variable
PP_HOME
that gives us the location of pp
(we could define
such a beast with a line lik
setenv PP_HOME /home/m050000which could be entered in the shell or stuck in a .cshrc file) then our program could grab the path with the
getenv
function. Check out genenv
with man
getenv
. Here's a silly little program demoing it:
#include <cstdlib> #include <iostream> using namespace std; int main() { // Get variable name from user string s; cout << "enter variable name: "; cin >> s; // Print out var's value or error if var doesn't exist char *p = getenv(s.c_str()); if (!p) cout << "Variable not found!" << endl; else cout << p << endl; return 0; }
Now, in many cases we may not want the user to be burdened
with adding the environment variable --- for instance we may
expect the sysadmin to install the program we're writing.
Assuming we're still talking about the cth
program from the previous lecture, we might make "the
program" a script that sets the environment variable and
launches cth
.
#!/bin/tcsh setenv PP_HOME /home/m050000 cthWith this setup, the system admin could edit the path in PP_HOME, and the actual users would never have to worry about it. When you launch firefox, that's what happens. The executable
firefox
is just the
script:
#!/bin/csh -x # # FIREFOX 1.0 wrapper in /usr/local/bin #Set home and paths setenv FIREFOXHOME /usr/local/firefox #execute firefox $FIREFOXHOME/firefox-bin $*
cth
program at the same time
on the same machine ... then one person's
named pipe "/tmp/cthPipe
" will fail to get
created because that named pipe will already exist from the
other person. The second user is out of luck!
The solution to this problem is to make sure that each pipe
you generate gets its own unique name. There's a standard
function mktemp
that does this for you. Do a
man
for more info or ... look at my example:
#include <cstdlib> #include <iostream> #include <fstream> using namespace std; int main() { // Create string, since mktemp modifies its argument, // we can't use a string literal. char s[30]; strcpy(s,"/tmp/dummyXXXXXX"); // Call mktemp and convert the result to a C++ string string fn(mktemp(s)); if (fn != "") { ofstream fout(fn.c_str()); fout << "hello" << endl << "world" << endl; } return 0; }