Copyright (c) Hyperion Entertainment and contributors.
AmiWest Lesson 1
Contents
- 1 AmiWest Lesson 1: Coding Basics
- 1.1 Compiler and Linker
- 1.2 Standard C Library
- 1.3 AmigaOS Threading
- 1.4 Threading in C and C++
- 1.5 Program Startup
- 1.5.1 How did your application start?
- 1.5.2 Starting from the Console: the ISO C way
- 1.5.3 Starting from the Console: the AmigaOS way
- 1.5.4 Starting from the Workbench: Getting File Names
- 1.5.5 WorkBench StartUp: Examining Icons & Tooltypes
- 1.5.6 Workbench StartUp: Getting Path & File Names
- 1.5.7 Example Program Source Code
AmiWest Lesson 1: Coding Basics
Compiler and Linker
The GNU GCC toolset is the officially supported standard compiler suite for Amiga operating system programming. An good alternative C compiler named vbcc is also supported and is popular with some programmers. We will focus on GCC which supports both C and C++.
Some Amiga-specific extensions to the baseline GNU GCC have been made to support the new Exec Interfaces, switching C standard libraries, shared objects and more. The source code for GNU GCC is readily available from the Amiga Development Tools web site and you are encouraged to participate if you are able.
There is a large wealth of detailed documentation and support for GNU GCC and programmers are encouraged to search around the internet. The Amiga-specific extensions are described on the AmigaOS Documentation Wiki.
Standard C Library
AmigaOS officially uses a variant of the Newlib C standard library implementation. For programmers, it is essential to know what your C standard library is and what its limits are. Functions may or may not be available. Behaviour may or may not be identical between C standard library implementations.
A popular alternative C standard library implementation is called clib2 and it is included as an option with the standard Amiga SDK. The clib2 library is particular well suited for porting software from the Unix world. The complete source code for clib2 is also available which makes is possible to debug complex problems involving the C library itself.
Switching between C standard library is easy thank to an Amiga-specific extension to GCC. The -mcrt option is used for both compiling and linking:
-mcrt=newlib (default, always thread-safe) -mcrt=clib2 (non-thread safe version) -mcrt=clib2-ts (thread-safe version)
AmigaOS Threading
AmigaOS uses lightweight processes and a shared memory model. As such, it has never been a high priority to provide an Amiga-specific threading implementation.
That said, the POSIX threading API (Pthreads) has been implemented and is available for programmers. This familiar API is quite popular so a lot of software has been written to use it.
AmigaOS supports a subset of the pthreads standard. It is implemented as a standard shared library with both static and dynamic link interfaces. The dynamic link interface is an Amiga shared object named libpthread.so. The static link interface is available in both newlib and clib2 flavours. These link libraries are just thin wrappers for the underlying pthreads.library which is currently private.
If your software is AmigaOS-specific then it is fine to launch and control processes using the existing API.
Threading in C and C++
It is very important to note that both C and C++ did not directly support threading until recently. This may come as a surprise to many programmers that have been developing threading applications in both C and C++ for many years.
Since there was no official standard for so long, threading has always been system dependent and several threading standards have come and gone over the years.
With C, the threading issue is relatively simple and GNU GCC supports a memory model which has always supported threading so there is little for an Amiga programmer to worry about. This is true for all platforms GCC supports.
The C++ programming language is a more complicated issue. The current GCC compiler implementation does not support threading on AmigaOS. This can be verified with the g++ -v command. The output of this command will specify what threading model is supported. On AmigaOS it currently states:
Thread model: single
This means threading is not supported. As a consequence, C++ exceptions and the RTTI feature will not function correctly in the presence of threads. Both features are optional in C++ but highly desirable. Amiga programmers may need to make some tough decisions in this case.
Program Startup
There are two primary environments in which a user can start applications in AmigaOS - by Shell console or by Workbench. Each method comes with its own way of sending the application its starting arguments, data or options.
With the Workbench, the user can either double-click the application's icon, shift click the app and some other icon(s) or double-click an icon that has this application as its "default tool".
In the Shell console, the user usually enters the name of the program and ends the command line with arguments, options and/or a filename.
In either case, the application typically uses and responds to the files, options and/or arguments that were involved in starting it. A file is opened and/or the program runs as configured.
How did your application start?
Following standard ISO C, AmigaOS applications always start using the standard argc and argv parameters:
int main(int argc, char **argv)
The argc variable indicates the number of arguments received by the application. The first argument (argv[0]) contains the program name. Any following command line arguments are stored in following argv array elements (argv[1], argv[2], ...).
AmigaOS provides special startup code that fills in the argc and argv parameters for you. The reason for the special startup code is twofold: to setup the standard C library and to support launching programs from Workbench or the Shell.
In AmigaOS, if argc' is equal to zero that means the application was started from the Workbench. If it is non-zero then it means the application was started from the Shell console.
While the ISO C method works for applications started from the Shell, it does not directly support an application started from the Workbench. AmigaOS provides ways for handling both Workbench start-up as well as a more sophisticated way of dealing with Shell start-up and arguments.
Starting from the Console: the ISO C way
For the sake of ISO C standard conformance, an AmigaOS application can use the standard argv array variables to read the arguments passed to the app.
After determining the argument count (argc) is greater than zero, then that number of arguments can be accessed and printed out as follows:
for (int argNo=0; argNo < argc; ++argNo) { // print CLI arguments printf(" argument #%d = >%s<\n", argNo, argv[argNo]); }
This method then leaves the parsing and testing of the arguments to the application and its developer. Many 3rd party libraries are available to parse the arguments such as Getopt. Since we are focusing on AmigaOS-specific programming they won't be discussed any further.
More information about this topic can be found in Running a Program under the CLI.
Starting from the Console: the AmigaOS way
AmigaOS provides an alternate way for applications to parse arguments from a Shell command line using the ReadArgs function. In its most common use, this function will parse command line arguments according to a "template" defined by the application. If the user follows the command name with a question mark, this function will print the application's commands template and then allow the user to type in their arguments. If the user fails to provide a valid command line, then ReadArgs lets the application know and the application can then exit gracefully.
More information about this topic can be found in Standard Command Line Parsing.
Configuring ReadArgs
The ReadArgs function is called with the following syntax:
struct RDArgs *result = IDOS->ReadArgs(CONST_STRPTR template, int32 *array, struct RDArgs *rdargs);
The first "template" parameter is a pointer to a string that defines the application's keywords, settings and "toggles" along with the following "modifiers":
- /S - Switch - indicates keyword string is a boolean setting
- /K - Keyword - indicates keyword string must appear before a setting
- /N - Number - indicates a setting must be an integer
- /T - Toggle - indicates keyword must be followed with a boolean setting (ON, OFF, etc)
- /A - Required - indicates keyword must be followed with a value
- /F - Rest of line - indicates the rest of the line isn't parsed and taken as one setting
- /M - Multiple strings - indicates following strings except for keywords are settings
Examples of such templates can be found by typing most any AmigaOS command line command with a "?". Here is an code example of how such a "template" definition could look like:
CONST_STRPTR argTemplate = "TEST/K,NUMBER/K/N,FILE";
[Note: Make sure there are no spaces in your template!]
This template allows the user to enter the following optional settings for an application:
- the TEST keyword followed with a value.
- the NUMBER keyword follwed with a numeric value.
- the FILE keyword and a filename.
The second parameter in the ReadArgs function is an array of int32 pointers. If the ReadArgs function finds valid arguments, the respective array elements will point to any valid user input for those parameters (in template order). To start with, those pointers can be defined and set to NULL values like this:
int32 argArray[] = { 0, 0, 0 };
The final parameter of ReadArgs function is typically NULL unless the application wants to set some default values (see the DOS/ReadArgs autodocs for more information on this).
Calling ReadArgs
Once run, the ReadArgs function will return either a pointer to a RDArgs structure if it succesfully parsed valid arguments or NULL if inadequate input was provided by the user. If the ReadArgs function was successful, then each entry of the second parameter (the argArray defined above) will point to any valid input for that slot in the template.
It is the developer's choice how best to deal with inadequate input from the user (ReadArgs returns a NULL value). At the very lest the application should gracefully let the user know the command line arguments were not accepted.
Using ReadArgs Results
For example, with the above code, the first entry (argArray[0]) would point to any valid input that followed the "TEST" keyword, if it was in the command line. This value could be printed as follows:
if ((int32 *)argArray[0] != NULL) IDOS->Printf(" TEST = %s\n", (STRPTR)argArray[0]);
If the second "NUMBER" keyword was used with a numeric value, its value could be obtained from the second arguments array entry as follows:
int32 intArg; if ((int32 *)argArray[1] != NULL) { intArg = *(int32 *)argArray[1]; IDOS->Printf(" NUMBER = %ld\n", intArg); }
Cleaning Up After ReadArgs
If ReadArgs successfuly parsed input from the command line, the FreeArgs function must be called using the resulting pointer from ReadArgs (rdargs in the above example). This will release any memory used by ReadArgs to store the user's input, as follows:
IDOS->FreeArgs(rdargs);
Starting from the Workbench: Getting File Names
As discussed above, if the user starts an application from the Workbench, AmigaOS indicates this by setting the argc variable to ZERO. AmigaOS also uses the argv variable to send a pointer to a WBStartup structure that contains more information on the user's actions.
There are three ways the user could have started an application that will be reflected by the contents of the WBStartup structure;
- The user double-clicked on the application's icon - In which case the WBStartup structure will indicate the program's name and "lock" its directory location on disk.
- The user double-clicked on the "project" file's icon that had this application set as its "tool" - In addition to the app's information, the WBStartup structure will indicate the project file's name and "lock" the project file's directory.
- The user selected one (or more) "project" files and shift-double-clicked on the application's icon - The WBStartup structure will have a file name and directory "lock" for each of the project files in addition to the application's information.
In all these cases, when the application starts, it must goes though a few steps to determine if any files were selected with it. If the application also wants to read any settings attached to those files - as "Tool Types" within their icons - the application has to go through a few more steps. These steps start with reading the WBStartup structure passed to the app.
More information about this topic can be found in Workbench Environment and Workbench and Startup Code.
Accessing the WBStartup structure
When started by the Workbench, an application is passed a pointer to the WBStartUp' structure in the standard argv variable. After checking the argc variable is equal to zero (to see that it was started by the Workbench), the WBStartUp structure can be obtained like this:
if (argc == 0) { struct WBStartup *wbs = (struct WBStartup *)argv; }
The WBStartup structure is based on the AmigaOS Message structure and it has the following format:
struct WBStartup { struct Message sm_Message; /* a standard message structure */ struct MsgPort * sm_Process; /* the process descriptor for you */ BPTR sm_Segment; /* a descriptor for your code */ int32 sm_NumArgs; /* the number of elements in ArgList */ char * sm_ToolWindow; /* reserved for future use */ struct WBArg * sm_ArgList; /* the arguments themselves */ };
Under typical circumstances the sm_NumArgs and sm_ArgList are the only elements of the structure that need to be accessed for program start up. The number of arguments will always be at least one, with the first entry representing the application itself. Any number of arguments above one reflects the number of "project" files or icons the user selected when the application was started.
The sm_ArgList will be an array of WBArg structures where the number of elements corresponds to the number of application & data files selected by the user (reflected by the sm_NumArgs variable above). The WBArg structure containing the arguments has the following format:
struct WBArg { BPTR wa_Lock; /* a lock descriptor */ STRPTR * wa_Name; /* a string relative to that lock */ };
The wa_Lock contains a pointer to an AmigaOS "lock" on the directory containing that argument's application or data file. For each of the files (the application or project files) passed to the application from the Workbench, a "lock" is applied to that directory. This prevents the directory of the application or any of the selected data files ("projects") from being disturbed or deleted while they are in use.
The wa_Name contains a string pointer to the name of the application or data file. This is a pointer to the name of the file itself and doesn't include the pathname for the directory is stored in.
The names of the application and any selected files could be printed out with the following code excerpt:
uint8 NoArgs, argNo; NoArgs = wbs->sm_NumArgs; IDOS->Printf(" number of args received = %ld\n", NoArgs); for (argNo = 0; argNo < NoArgs; ++argNo) IDOS->Printf(" arg %ld name = >%s<\n", argNo, wbs->sm_ArgList[argNo].wa_Name);
Below we will see how to obtain more information from the selected files and how to get full path names for each. When the application finishes running, AmigaOS will release the "locks" on the directories referred to by the WBStartUp entries.
WorkBench StartUp: Examining Icons & Tooltypes
Whereas options might be given to a program in the Shell console in the form of argument in command line, Workbench programs frequently keep such settings within the application's icon, which is stored in a companion .info file. Specifically, each icon has the ability to store "Tool Types" text strings within the icon's .info file. While such settings can be almost any form of text, the standard format is to have a SETTING, equals sign and a value, like these:
TEST=verbose NUMBER=23
These "Tool Type" settings are usually stored within the application's icon, but such settings can be also stored in most any icon files in AmigaOS. As such, an application can also look for such information within each project's icon file.
For an application to access any information that might be stored in its icon's .info file or in those of the "Projects" passed to the application, the application needs to go through a few more steps after collecting the related file names (mentioned above).
More information about this topic can be found in Tool Types Array.
Getting to Each Icon's Directory
The easiest way to access any of the files sent by the Workbench is to use the sm_ArgList entry provided for each. This is done by first confirming a valid directory was found and "locked" for us by the Workbench. Then we call the CurrentDir function to switch the application to that directory, like this:
if (wbs->sm_ArgList[argNo].wa_Lock != ZERO) { BPTR oldDir = IDOS->CurrentDir(wbs->sm_ArgList[argNo].wa_Lock); }
As you can see, this function returns a pointer to the old directory when we switch to the new directory location. This is very important, since the Workbench also maintains a directory lock based on the application's current directory. Before an application finishes, it needs to call the CurrentDir function again and switch back to the application's original location so it can be "unlocked" when the app quits - like this:
IDOS->CurrentDir(oldDir);
Getting Icon Information
Once the application has "moved" to the directory of the icon file to be examined, we can use the GetDiskObjectNew function to get information from the icon. This function is simply called using the same wa_Name element from the sm_ArgList structure used above, as in this code excerpt:
struct DiskObject *dobj; dobj = IIcon->GetDiskObjectNew(wbs->sm_ArgList[argNo].wa_Name); if(dobj != NULL) { IDOS->Printf(" Opened disk object.\n"); }
The GetDiskObjectNew function automatically appends the ".info" necessary to open the selected file's icon. If there is no .info file for the selected file, this function will automatically use the information from the "default icon" for the selected file type. For example, if the file "test.jpg" has no "test.jpg.info" icon file, the "def_jpeg.info" will be used.
If an application needs to explore icon information for a file not in the current directory of the app, the GetDiskObjectNew function will also accept the name of a file including a full path. But as we will see below, this does involve the application going through the overhead of allocating a string sufficient to store the full file & path name.
If successful, the GetDiskObjectNew function returns a pointer to a DiskObject structure that provides the following information for that .info file and icon:
struct DiskObject { UWORD do_Magic; /* magic number at start of file */ UWORD do_Version; /* so we can change structure */ struct Gadget do_Gadget; /* a copy of in core gadget */ UBYTE do_Type; char *do_DefaultTool; char **do_ToolTypes; LONG do_CurrentX; LONG do_CurrentY; struct DrawerData *do_DrawerData; char *do_ToolWindow; /* only applies to tools */ LONG do_StackSize; /* only applies to tools */ };
As you can see, the DiskObject structure presents most of the information conveyed by an AmigaOS icon. The do_DefaultTool is the setting that is used to designate whichever application will be used when a "Project" icon is double-clicked. The do_ToolTypes element points to an array of strings containing the icon's "Tool Types".
Checking for Our Tooltypes
AmigaOS provides another function to simplify checking the Tool Types for values an application is interested in - the FindToolType function. It is simply called with the pointer to the do_ToolTypes array and the desired setting string. This function returns either a pointer to the string with that ToolType setting or NULL if the ToolType wasn't found. The following code would check for our "TEST" ToolType and print out any results found.
STRPTR TTarg = NULL; TTarg = IIcon->FindToolType(dobj->do_ToolTypes, "TEST"); if (TTarg) IDOS->Printf(" TEST found set to = >%s<\n", TTarg);
With all the above code, an application can check each of the elements of the sm_ArgList sent by the Workbench to check for Tool Types in the app's icon as well as any of the icons of any Project files sent to the app.
Workbench StartUp: Getting Path & File Names
So far the above code has relied on using the WBStartUp structure and sm_ArgList sub-structure to obtain the necessary information on the application and any Project files selected. This has avoided directly dealing with the full file and path names of those files. If the full file & path name is required, the application can get the full path name by examining the directory "lock" of each sm_ArgList entry. This is done by calling the DOS NameFromLock function with another multi-step process.
First we must allocate a string in which to put the full name, then call NameFromLock to get the path name from the "Lock" and finally we must free the allocated memory after we're done with it. The following code excerpt would obtain the path to the application (always the first entry in sm_ArgList):
int32 StringLen = 2048; // Alloc memory for pathname string filename = (STRPTR) IExec->AllocVecTags(StringLen, TAG_END); if (filename) { if (wbs->sm_ArgList[0].wa_Lock != ZERO) if (IDOS->NameFromLock(wbs->sm_ArgList[0].wa_Lock,filename, StringLen) != 0) IDOS->Printf(" app path name = >%s<\n", filename); // deallocate pathname string memory IExec->FreeVec(filename); }
As you can see, we allocate a large string size for the path name - 2048 characters - to provide room for potentially long and/or heavily nested path names. If that string is not long enough, the NameFromLock function will fail and return a NULL value. The developer must use their judgment in deciding how likely this error might be and how much effort to put into handling it - whether to have the application politely handle and notify the user of the error or whether to wrap this function in some loop that keeps allocating larger string sizes until NameFromLock succeeds.
Obviously, there are few levels of error trapping in this code: to make sure we were able to allocate our string (filename), to make sure there is a valid directory lock to use and finally to make sure the path name could be obtained from the lock. Regardless of how unlikely failure might be, within any application these tests should occur and be "wrapped" in proper and graceful error handling and feedback.
Getting The Full Name
Once we have the path name, we can combine that with the name of the file (from the wa_Name element of the sm_ArgList structure). AmigaOS provides the AddPart function which also resolves AmigaOS separator characters ("/" or ":") to create a valid and complete file pathname, like this:
if (IDOS->AddPart(filename,wbs->sm_ArgList[argNo].wa_Name,StringLen) != 0) IDOS->Printf(" full path = >%s<\n", filename);
The resulting combined path and file name should be suitable for use by the application - to open or act upon the file selected by the user.
Bear in mind, we are still working within the fixed size of the "filename" string that must contain both the path name as well as the file name. As before, we use error trapping to check that the combined file and path names fit into the string length we've defined. Again, should there be a failure, provide feedback and gracefully manage the situation.
Example Program Source Code
The following is an example program to demonstrate the above concepts in starting an application from the Shell and the Workbench.
From the Shell, the user can enter arguments or enter the "?" to see the template prompt before entering arguments. Commented out within the code is a working example of using the simple ANSI method of reading command line arguments.
From the Workbench, this example will print out all the above information found on all the arguments passed by the Workbench. Besides the application's information, the printed information will reflected selected icons or Projects calling this app as a default Tool.
An archive for use with CodeBench is available from here.
/* ************************************************************ ** ** Created by: CodeBench 0.26 (04.01.2012) ** ** Project: PJS-OS4ex-StartUp ** ** Version: 4 ** ** File: This an example program of the various AmigaOS ** ways of starting an application - from CLI or ** Workbench - and dealing with the options. ** ** Date: 25-08-2012 18:21:44 ** ************************************************************ */ #include <exec/execbase.h> #include <workbench/startup.h> #include <proto/exec.h> #include <proto/dos.h> #include <proto/icon.h> #include <string.h> #include <stdio.h> // declare & initialize empty LONG words array to be filled with pointers to CLI arguments received int32 argArray[] = { 0, 0, 0 }; // declare the number of possible arguments (the size of the argArray above) uint8 noArgs = 3; // declare CLI options template string with these modifiers: // /S - Switch - indicates keyword string is a boolean setting // /K - Keyword - indicates keyword string must appear before a setting // /N - Number - indicates a setting must be an integer // /T - Toggle - indicates keyword must be followed with a boolean setting (ON, OFF, etc) // /A - Required - indicates keyword must be followed with a value // /F - Rest of line - indicates the rest of the line isn't parsed and taken as one setting // /M - Multiple strings - indicates following strings except for keywords are settings // CONST_STRPTR argTemplate = "TEST/K,NUMBER/K/N,FILE"; uint8 returnCode = RETURN_OK; // Starting program int main(int argc,char **argv) { uint8 argNo; IDOS->Printf("AmigaOS StartUp Example\n"); // Check if program was started from CLI or Workbench based on value of "argc" if (argc > 0) { IDOS->Printf("Started from CLI.\n"); // declare struct pointer to receive CLI options struct RDArgs *rdargs; // read CLI args (using AmigaOS method) rdargs=IDOS->ReadArgs(argTemplate,argArray,NULL); // were CLI arguments sucessfully read? if (rdargs) { int32 intArg; // loop through possible options & settings and print them for (argNo=0; argNo<noArgs; ++argNo) { // print readarg arguments IDOS->Printf(" arg %ld = %s\n", argNo, argArray[argNo]); if ((int32 *)argArray[argNo] != NULL) { intArg = *(int32 *)argArray[argNo]; IDOS->Printf(" # = >%ld<\n", intArg); } } // free memory used by ReadArgs and data IDOS->FreeArgs(rdargs); } else IDOS->Printf(" ERROR: Incorrect Values received by ReadArgs\n"); //read args using ISO C method /* for (argNo=0; argNo<argc; ++argNo) { // print CLI arguments IDOS->Printf(" argument #%ld = >%s<\n", argNo, argv[argNo]); } */ } else { IDOS->Printf("Started from Workbench.\n"); // set pointer to WBStartUp structure using argv contents struct WBStartup *wbs = (struct WBStartup *)argv; // set a length for path names (to be added to file name length) int32 MaxPathLen = 1792; // set a length for file names (to be added to path name length) int32 MaxFileLen = 256; // working variable uint8 NoArgs; STRPTR filename = NULL; STRPTR TTarg = NULL; BPTR oldDir = ZERO; // print out number of arguments (first one is this program) NoArgs = wbs->sm_NumArgs; IDOS->Printf(" number of args received = %ld\n", NoArgs); // Alloc memory for pathname string filename = (STRPTR) IExec->AllocVecTags(MaxPathLen+MaxFileLen, TAG_END); // Alloc disk object pointer for icon information struct DiskObject *dobj; if (filename) { // loop through WB arguments getting path names for (argNo=0; argNo<NoArgs; ++argNo) { IDOS->Printf(" arg %ld name = >%s<\n", argNo, wbs->sm_ArgList[argNo].wa_Name); if (wbs->sm_ArgList[argNo].wa_Lock != ZERO) { IDOS->Printf(" file locked.\n"); // CD to project file's dir oldDir = IDOS->CurrentDir(wbs->sm_ArgList[argNo].wa_Lock); // get disk object on argument's file dobj = IIcon->GetDiskObjectNew(wbs->sm_ArgList[argNo].wa_Name); if(dobj) { IDOS->Printf(" Opened disk object.\n"); // check for "TEST" keyword TTarg = IIcon->FindToolType(dobj->do_ToolTypes, "TEST"); if (TTarg) IDOS->Printf(" TEST found set to = >%s<\n",TTarg); else IDOS->Printf(" TEST tooltype not found.\n"); // check for "NUMBER" keyword TTarg = IIcon->FindToolType(dobj->do_ToolTypes, "NUMBER"); if (TTarg) IDOS->Printf(" NUMBER found set to = >%s<\n",TTarg); else IDOS->Printf(" NUMBER tooltype not found.\n"); // free disk object structure & memory IIcon->FreeDiskObject(dobj); } else { // print why we couldn't get icon information int32 MYerror, MYerrLen; MYerror = IDOS->IoErr(); IDOS->Printf(" ERROR: Unable to open disk object.\n"); IDOS->Printf(" number = >%ld<\n",MYerror); MYerrLen = IDOS->Fault(MYerror,NULL,filename,80); IDOS->Printf(" text = >%s<\n",filename); returnCode = RETURN_ERROR; } // CD back to previous dir IDOS->CurrentDir(oldDir); // Indicate what type of file the WBArg is refering to if (argNo == 0) IDOS->Printf(" entry = application.\n"); else { // check item type if (strlen(wbs->sm_ArgList[argNo].wa_Name) > 0) IDOS->Printf(" entry = file.\n"); else IDOS->Printf(" entry = device or directory.\n"); } // Get the path and full path/filename of the file if (IDOS->NameFromLock(wbs->sm_ArgList[argNo].wa_Lock,filename,MaxPathLen) != 0) { IDOS->Printf(" path name = >%s<\n",filename); if (IDOS->AddPart(filename,wbs->sm_ArgList[argNo].wa_Name,MaxPathLen+MaxFileLen) != 0) IDOS->Printf(" full path = >%s<\n",filename); else { IDOS->Printf(" ERROR: full name is larger than %ld\n",(MaxPathLen+MaxFileLen)); returnCode = RETURN_WARN; } } else { IDOS->Printf(" ERROR: path name is larger than %ld\n",MaxPathLen); returnCode = RETURN_WARN; } } else { IDOS->Printf(" ERROR: file handle could not be locked.\n"); returnCode = RETURN_ERROR; } } // deallocate pathname string memory IExec->FreeVec(filename); } else { IDOS->Printf(" ERROR: unable to allocate memory for string!\n"); returnCode = RETURN_ERROR; } } IDOS->Printf("GOODBYE!\n"); return (returnCode); }