MODULEN and MODULEC Functions

Part of this page is composed from a thread of the newsgroup comp.soft-sys.sas discussing several uses of the MODULEN (numeric) and MODULEC (text) functions which are available from both DATA-step and SCL code. The functions are able to access the Windows API, a set of basic functions which are used with the Windows operating system.

The following examples are discussed:


Message Boxes



Subject: More on Win32 API function calls

Date: Sat, 10 Feb 1996 01:16:45 +1100

From: Jerry Le Breton 

Newsgroups: comp.soft-sys.sas


Tim CHURCHES wrote, in response to Paul Oldenkamp's post:


> ....
>

>Now, does anyone have an equivalent call for WIN32 (ie
>Windows 95 and Windows NT platforms) rather than WIN32s
>(ie Windows 3.x)?
>
> ....

I was talking to a highly respected, inside source today, and he suggested that all you need to do is use:

module=KERNEL32

in the Routine statement of the attribute table, instead of the one in Paul's example, W32SCOMB, which only works when operating under win32s i.e. Windows 3.1x. If I understood it right, KERNEL32 will work for all 3 Windows platforms.

Also,

Paul was looking for more examples of using the MODULExy functions. My real favourite at the moment is using the Windows MessageBox to display messages in my AF application. They pop up much faster than a Frame and its a little bit less code that needs maintaining. (I'm busy rewriting my message handling now.)

The Attribute table (c:\sas\winapi.txt):


routine MessageBoxA
   module=USER32
   minarg=4
   maxarg=4
   stackpop=called
   returns=short
   ;

arg 1 input num format=pib4. byvalue;
arg 2 input char format=$cstr200.;
arg 3 input char format=$cstr200.;
arg 4 input num format=pib4. byvalue;



Style values Description
0OK button only
1OK and Cancel buttons
2Abort, Retry and Ignore buttons
3Yes, No, Cancel buttons
4Yes, No buttons
5Retry , cancel buttons
16Icon=Stop sign
32Icon=Question mark
48Icon=Exclamation mark
64 Icon=i (info) symbol
0Button 1 is
256Button 2 is default
512Button 3 is default
4096All Windows apps are inaccesible until user responds

In the SCL:


   ...

   rc=filename('sascbtbl','c:\sas\winapi.txt');

   ...

** Question dialog box with Yes/No buttons, user must answer before another Windows application is accessible **;

   * See right table for explanation ;
   style=4096+32+4;

   rc=modulen('*e','MessageBoxA',
                    0,
                    'Dialog Text',
                    'Dialog Title',
                    style);

   if rc=6 then put 'Oh, YES';
   else if rc=7 then put 'Oh, NO';
   else put rc=;

   ...

Return codes:Explanation
0Out of memory
1OK pressed
2Cancel pressed
3Abort pressed
4Retry pressed
5Ignore pressed
6Yes pressed
7No pressed
Jerry

<=><=><=><=><=><=><=><=><=><=><=><=>

Jerry Le Breton

Independent SAS Consultant, Rationale Pty Ltd, Australia

lebreton@spirit.com.au


Copying and moving DOS files


Subject: And more Win32 APIs for MODULE functions

Date: Sat, 17 Feb 1996 00:17:22 +1100

From: Jerry Le Breton 

Newsgroups: comp.soft-sys.sas


Hi folks,

Here is another couple of Win32 API calls for those that are interested. Thanks go to the same, reliable 'little birdie' who has supplied me with previous API tips.

Anyone else out there got any to offer?

Attribute Table(C:\SAS\WINAPI.TXT)

routine CopyFileA
   module KERNEL32
   minarg=3 maxarg=3
   stackpop=called
   returns=ushort;

arg 1 input char format=$cstr200.;   * From filename;
arg 2 input char format=$cstr200.;   * To filename;
arg 3 input num format=pib4. byvalue;  * 1=dont overwrite an existing file, 0=overwrite ;

routine MoveFileA
   module=KERNEL32
   minarg=2 maxarg=2
   stackpop=called
   returns=ushort;

arg 1 input char format=$cstr200.;   * From filename ;
arg 2 input char format=$cstr200.;   * To filename ;

SCL:

rc=filename('sascbtbl','c:\sas\winapi.txt');

...

* Copy c:\sample.sd2 to c:\sasdata\sample.sd2;

rc = modulen( '*e', 'CopyFileA', 'c:\sample.sd2', 'c:\sasdata\sample.sd2', 0);

...


* Move c:\sample.sd2 to c:\sasdata\sample.sd2;

rc = modulen( '*e', 'MoveFileA', 'c:\sample.sd2', 'c:\sasdata\sample.sd2');

 ...

<=><=><=><=><=><=><=><=><=><=><=><=>

Jerry Le Breton

Independent SAS Consultant, Rationale Pty Ltd, Australia

lebreton@spirit.com.au


Getting Disk information


Subject: SASTip: Yet more Win API calls

Date: Fri, 23 Feb 1996 17:18:16 +1000

From: Tim Churches 

Organization: Royal Prince Alfred Hospital

Newsgroups: comp.soft-sys.sas


Following Jerry Le Breton's examples, here is yet another Windows API call. This one will return the amount of free disc space on a target drive. I have tested it with Windows 32s (ie DOS/Win 3.1 plus Win 32s extensions as used by SAS), Windows 95 and WIndows NT. Note that it appears to over-estimate the free disc space by about up to 1% because it rounds up to the nearest full disc cluster. Nevertheless it is exceedingly useful since one of the most common causes of fialure in SAS applications is running out of disc space. This acll allows you to check how much space is free on the target drive before running a data setep or procedure which will need more space.

The details follow.

Tim Churches

The following lines should be placed in or added to a text file containing details of the Windows API calls. We use a file called c:\temp\winapi.txt. Alternatively they can be stored in a catalogue entry of type SOURCE, which is easier to keep track of and distribute in a SAS/AF application.

Attribute Table (c:\temp\winapi.txt)

routine GetDiskFreeSpaceA
 module=KERNEL32
 minarg=5
 maxarg=5
 stackpop=called
 returns=long
 ;

 arg 1 input char format=$cstr200. ;
 arg 2 output num format=pib4. ;
 arg 3 output num format=pib4. ;
 arg 4 output num format=pib4. ;
 arg 5 output num format=pib4. ;

DATA step code


filename sascbtbl "c:\temp\winapi.txt" ;

* Use the following if the API calls are stored in a catalogue. ;
* filename sascbtbl library "sasuser.profile.winapi.source" ;

data _null_ ;
 attrib rootpath length=$8 label="Path to drive root directory"
        sectors  length=8  label="Sectors per Cluster"
        bytes    length=8  label="Bytes per Sector"
        freeclus length=8  label="Free Clusters"
        totclus  length=8  label="Total Clusters"
        freespc  length=8  label="Free disc space (bytes)"
        totspc   length=8  label="Total disc space (bytes)";

 * Initialise parameters ;

 Rootpath = "c:\" ;
 sectors = . ;
 bytes = . ;
 freeclus = . ;
 totclus = . ;

 * Call the module ;

 rc=modulen('*e',
            'GetDiskFreeSpaceA',
            rootpath,
            sectors ,
            bytes   ,
            freeClus,
            totclus ) ;

* Calculate approximate free and total disc space in bytes. Note that this API call seems to slightly overestimate the free space available because it calculates to the nearest cluster, but it is pretty close. ;

  freespc = bytes * sectors * freeclus ;
  totspc  = bytes * sectors * totclus ;

  * Write results to log ;

  put rc= / rootpath= / sectors= / bytes= / freeclus= /
     totclus= / freespc= / totspc= ;
run ;


Creating and deleting directories

This one was sent to me by Wilbram Hazejager:
Deleting and creating subdirectories. Note that only one subdirectory may be created in a MODULEN call.

Attribute Table

routine RemoveDirectoryA                                              
     module=W32SCOMB                                                     
     minarg=1                                                            
     maxarg=1                                                            
     stackpop=called                                                     
     returns=short;  
                                                                 
     arg 1 input char format=$cstr200.;                                    
     routine CreateDirectoryA                                              
     module=W32SCOMB                                                     
     minarg=2                                                            
     maxarg=2                                                            
     stackpop=called                                                     
     returns=short;  
                                                                   
     arg 1 input char format=$cstr200.;                                    
     arg 2 input num format=ib4.;                                          

DATA step code:


     filename sascbtbl 'c:\temp\winapi.txt';                               

     data _null_;                                                          

       ** Create a subdirectory of C:\TEMP;                                  

       rc=modulen('CreateDirectoryA','c:\temp\newdir',0);                    

       ** Then create a subdirectory of that;                                

       rc=modulen('CreateDirectoryA','c:\temp\newdir\a',0);                  
    
       ** Create another subdirectory of C:\TEMP and then remove it;         

       rc=modulen('CreateDirectoryA','c:\temp\newdir2',0);                   
       rc=modulen('RemoveDirectoryA', 'c:\temp\newdir2');                    

     run;                                                                  


Porting SCL code with MODULEN|C to Unix

MODULEN and MODULEC functions do not compile right on Unix platforms. When a PC program containing thesee functions is ported to Unix, compile errors occur. Even when the hardware platform is detected and an IF-statement sends the flow of the SCL-program around this part of the code, the SCL compiler signals a syntax error (not enough parameters). The only solution is to place the MODULEN part in a separate SCL-entry:


IF SYMGET('sysscp')='HP 800' THEN CALL DISPLAY('ux_mkdir.scl',directory);

ELSE IF SYMGET('sysscp')='WIN' THEN CALL DISPLAY('pc_mkdir.scl,directory);


Netware functions

/**********************************************************************/


   /* This is the attribute table for NWGetDefaultConnectionID.   */

routine NWGetDefaultConnectionID
minarg=1
maxarg=1
arch=bit16
stackorder=l2r
stackpop=called
module=NWCALLS
returns=short;
arg 1 num update byaddr format=ib2.;


/**********************************************************************/


   /* This is the attribute table for NWGetConnectionNumber.      */

routine NWGetConnectionNumber
minarg=2
maxarg=2
arch=bit16
stackorder=l2r
stackpop=called
module=NWCALLS
returns=short;
arg 1 num input byvalue format=ib2.;
arg 2 num update byaddr format=ib2.;


/**********************************************************************/


   /* This is the attribute table for NWGetConnectionInformation. */

routine NWGetConnectionInformation
minarg=6
maxarg=6
arch=bit16
stackorder=l2r
stackpop=called
module=NWCALLS
returns=short;
arg 1 num input byvalue format=ib2.;
arg 2 num input byvalue format=ib2.;
arg 3 char update byaddr format=$cstr48.;
arg 4 num update byaddr format=ib2.;
arg 5 num update byaddr format=ib4.;
arg 6 char update byaddr format=$cstr7.;


/**********************************************************************/


   /* This DATA step will return the user's login id.             */

filename sascbtbl 'c:\sas\attr.txt';

data _null_;
   length Name $48 logtime $7;
   rc=modulen('NWGetDefaultConnectionID',ID);
   rc=modulen('NWGetConnectionNumber',ID,Handle);
   rc=modulen('NWGetConnectionInformation',ID,Handle,Name,Type,
               UserID,logtime);
   put Name=;
run;


/**********************************************************************/


   /* Something similar could be done in the INIT section of a    */
   /* FRAME entry.                                                */

INIT:
length Name $48 logtime $7;
Name=repeat('  ',24);
logtime='       ';
rc=filename('sascbtbl','c:\sas\attr.txt');
rc=modulen('NWGetDefaultConnectionID',ID);
rc=modulen('NWGetConnectionNumber',ID,HANDLE);
rc=modulen('NWGetConnectionInformation',ID,HANDLE,Name,Type,
            UserID,logtime);
return;


/**********************************************************************/
/**********************************************************************/

/******** Example for Windows 3.1 or Windows 3.11 with Novell *********/
/*
  Use the following code on a Windows 3.1 or Windows 3.11 client running
  on a NOVELL network to update the DOS environment variable, VQPNAME
  with the userid of the current user logged onto a particuliar NOVELL
  server. This code uses the modulen function to reference
  three DOS/WINDOWS/Novell API's.

*1*   
  Data step creates the text file novell.txt referenced by the fileref 
  SASCBTBL.  SASCBTBL will be read by the modulen function.

*2* 
   Data step to execute the NOVELL api external modules
   'NWGetDefaultConnectionID','NWGetConnectionNumber', and
   'NWGetConnectionInformation'.  The NWGetConnectionInformation api
   actually returns the name information. Connectionid and connection
   number are needed to call NWGetConnectionInformation.  The macro
   variable vqpname is updated with the value of the name returned.
   Note that NWGetConnectionInformation returns a other valuable
   informatio that might be used in other applications.  

*3* 
   Now set the environment variable vqpname with the value stored 
   in the macro variable vqpname.
*/
/****** Start of Code for NOVELL/DOS/WIN32S example******************/
  
/* *1* */  

data _null_;
   filename sascbtbl 'novell.txt';
   file sascbtbl;
   input  line $char80.;
   put line $char80.;
   cards4;
routine NWGetDefaultConnectionID
minarg=1
maxarg=1
arch=bit16
stackorder=l2r
stackpop=called
module=NWCALLS
returns=short;
arg 1 num update byaddr format=ib2.;

routine NWGetConnectionNumber
minarg=2
maxarg=2
arch=bit16
stackorder=l2r
stackpop=called
module=NWCALLS
returns=short;
arg 1 num input byvalue format=ib2.;
arg 2 num update byaddr format=ib2.;

routine NWGetConnectionInformation
minarg=6
maxarg=6
arch=bit16
stackorder=l2r
stackpop=called
module=NWCALLS
returns=short;
arg 1 num input byvalue format=ib2.;
arg 2 num input byvalue format=ib2.;
arg 3 char update byaddr format=$cstr48.;
arg 4 num update byaddr format=ib2.;
arg 5 num update byaddr format=ib4.;
arg 6 char update byaddr format=$cstr7.;
;;;;

/* *2* */

filename sascbtbl 'novell.txt';
data _null_;
  length Name $48 logtime $7;
  rc=modulen('NWGetDefaultConnectionID',ID);
  rc=modulen('NWGetConnectionNumber',ID,Handle);
  rc=modulen('NWGetConnectionInformation',ID,Handle,Name,Type,UserID,
              logtime);
  put ID= Handle= Name= Type= UserID= logtime=;
  call symput('vqpname',name);
run;

/* *3* */

options set=vqpname &vqpname;

/****** End of example for NOVELL/DOS/WINDOS **************************/
/**********************************************************************/

/******** Example for Windows 95 or Windows NT ************************/
/*
  Use the following code on a Windows 95 or Windows NT client to
  update the DOS environment variable, VQPNAME.  This code uses the
  external dll, GetUserName, a Win32 api. This api is called with the
  modulen function.

*1*
   Data step to create the attribute table for GetUserNameA.  Store
   attribute table in a text file to be referenced by the modulen
   function.  The example uses the file winapi.txt in the current
   directory.  The modulen function will use the fileref SASCBTBL.

*2* 
   Data step to execute the Win32 api external module 'GetUserNameA'.
   The module returns the current user's userid.  The macro variable 
   vqpname is updated with this value.

*3* 
   Now set the environment variable vqpname with the value stored 
   in the macro variable vqpname.
*/

/****** Start of Code for WINDOWS 95 and WINDOWS NT example ***********/

/* *1* */ 

filename sascbtbl 'winapi.txt';

data _null_;
   file sascbtbl;
   input  line $char80.;
   put line $char80.;
   cards4;
routine GetUserNameA
minarg=2
maxarg=2
stackpop=called
module=advapi32
returns=short;
arg 1 char update format=$cstr20;
arg 2 num  update format=pib4.;
;;;;
 run;

/* *2* */

filename sascbtbl 'winapi.txt';

data _null_;
  length Name $20.;
  name='';
  Size=20;
  rc=modulen('GetUserNameA',Name,Size);
  put rc= Name=;
  call symput('vqpname',name);
run;

/* *3* */

options set=vqpname &vqpname;

/****** End of example for Windows 95 or Windows NT *******************/

Frequently Asked Questions about SAS

The following Question & Answer List is based on SAS questions received by the author.


Using Win31 DLLs with SAS

Question Answer
How can I make a Windows-style message box, with OK and Cancel buttons, from SAS? First create a flat file, e.g. C:\WINAPI.TXT, to store the following lines of code:
routine MessageBox
  module=USER
  arch=BIT16
  returns=SHORT
  stackpop=CALLED
  minarg=4
  maxarg=4
  stackorder=L2R
;
arg 1 input format=pib2. byvalue;
arg 2 input format=$cstr200.;
arg 3 input format=$cstr200.;
arg 4 input format=pib2. byvalue;

This flat file needs to be given a fileref of SASCBTBL, and the message box is created using the MessageBox function:

filename sascbtbl 'c:\winapi.txt';

data _null_;
  length rc hWnd style 8 text caption $200;
  hWnd=0;
  text='Text';
  caption='Caption';
  style=(0*4096)+(0*256)+(2*16)+(1*1);
    * 0021 = Question mark icon + OK/Cancel *;
  rc=modulen('*ie','MessageBox',hWnd,text,caption,style);
  if (rc=0) then put 'ERROR: Cannot create message box';
  put rc=;
run;

The button style values are:
OK=0, OK/Cancel=1, Abort/Retry/Ignore=2, Yes/No/Cancel=3, Yes/No=4, Retry/Cancel=5
The icon style values are:
Stop sign=10, Question mark=20, Exclamation mark=30, Information sign=40
The possible return values are:
Error state=0, OK=1, Cancel=2, Abort=3, Retry=4, Ignore=5, Yes=6, No=7

How can I find out the versions of Windows and DOS being used from SAS? First create a flat file, e.g. C:\WINAPI.TXT, to store the following lines of code:
routine GetVersion
  module=KERNEL
  arch=BIT16
  returns=ULONG
  stackpop=CALLED
  minarg=0
  maxarg=0
  stackorder=L2R
;

This flat file needs to be given a fileref of SASCBTBL, and the message box is created using the GetVersion function:

filename sascbtbl 'c:\winapi.txt';

data _null_;
  length temp 8 dosver winver $5;
  rc=modulen('*e','GetVersion');
  temp=rc;
  dosver='  .  ';
  winver='  .  ';
  substr(dosver,1,2)=put(mod(temp,256),2.);
  temp=int(temp/256);
  substr(dosver,4,2)=put(mod(temp,256),z2.);
  temp=int(temp/256);
  substr(winver,1,2)=put(mod(temp,256),2.);
  temp=int(temp/256);
  substr(winver,4,2)=put(mod(temp,256),z2.);
  put winver= dosver=;
run;
How can I find out the screen size being used from SAS? First create a flat file, e.g. C:\WINAPI.TXT, to store the following lines of code:
routine GetSystemMetrics
  module=USER
  arch=BIT16
  returns=SHORT
  stackpop=CALLED
  minarg=1
  maxarg=1
  stackorder=L2R
;
arg 1 input format=pib2. byvalue;

This flat file needs to be given a fileref of SASCBTBL, and the message box is created using the GetSystemMetrics function:

filename sascbtbl 'c:\winapi.txt';

data _null_;
  length index 8;
  index=0; * Width of screen (pixels) *;
  width=modulen('*e','GetSystemMetrics',index);
  put 'Screen width: ' width;
  index=1; * Height of screen (pixels) *;
  height=modulen('*e','GetSystemMetrics',index);
  put 'Screen height: ' height;
run;


Back to Main FAQ Menu


Email: Phil Holland <phil.holland@bcs.org.uk>