Examples

Updating a Character String Argument

This example uses the Win32 routine GetTempPathA. This routine expects as an argument a pointer to a buffer, along with the length of the buffer. GetTempPathA fills the buffer with a null-terminated string representing the temporary path. Here is the C prototype for the GetTempPathA routine:
DWORD WINAPI GetTempPathA
    (DWORD nBufferLength, LPSTR lpBuffer);
Here is the attribute table:
routine GetTempPathA
  minarg=2
  maxarg=2
  stackpop=called
  returns=long;
arg 1 input  byvalue format=pib4.;
arg 2 update         format=$cstr200.;
Note that the STACKPOP=CALLED option is used; all Win32 service routines require this attribute. The first argument is passed by value because it is an input argument only. The second argument is an update argument because the contents of the buffer are to be updated. The $CSTR200. format allows for a 200-byte character string that is null-terminated.
Here is the SAS code to invoke the function. In this example, the DLL name (KERNEL32) is explicitly given in the call (because the MODULE attribute was not used in the attribute file):
filename sascbtbl "sascbtbl.dat";
data _null_;
  length path $200;
  n = modulen( '*i',
      "KERNEL32,GetTempPathA", 199, path );
  put n= path=;
run;
Note: KERNEL32.DLL is an internal DLL provided by Windows. Its routines are described in the Microsoft Win32 SDK.
The code produces these log messages:
NOTE: Variable PATH is uninitialized.
N=7 PATH=C:\TEMP
The example uses 199 as the buffer length because PATH can hold up to 200 characters with one character reserved for the null terminator. The $CSTR200. informat ensures that the null-terminator and all subsequent characters are replaced by trailing blanks when control returns to the DATA step.

Passing Arguments by Value

This example calls the Beep routine, part of the Win32 API in the KERNEL32 DLL. Here is the C prototype for Beep:
BOOL Beep(DWORD dwFreq, DWORD dwDuration)
Here is the attribute table to use:
routine Beep
  minarg=2
  maxarg=2
  stackpop=called
  callseq=byvalue
  module=kernel32;
arg 1 num format=pib4.;
arg 2 num format=pib4.;
Because both arguments are passed by value, the example includes the CALLSEQ=BYVALUE attribute in the ROUTINE statement. It is not necessary to specify the BYVALUE option in each ARG statement.
Here is the sample SAS code used to call the Beep function:
filename sascbtbl 'sascbtbl.dat';
data _null_;
  rc = modulen("*e","Beep",1380,1000);
run;
The computer speaker beeps.

Using PEEKCLONG to Access a Returned Pointer

The following example uses the lstrcat routine, part of the Win32 API in KERNEL32.DLL. lstrcat accepts two strings as arguments, concatenates them, and returns a pointer to the concatenated string. The C prototype is
LPTSTR lstrcat (LPTSTR lpszString1, 
                LPCTSTR lpszString2);
The following is the proper attribute table:
routine lstrcat
  minarg=2
  maxarg=2
  stackpop=called
  module=KERNEL32
  returns=ptr;
  arg 1 char format=$cstr200.;
  arg 2 char format=$cstr200.;
To use lstrcat, you need to use the SAS PEEKCLONG function to access the data referenced by the returned pointer. Here is the sample SAS program that accesses lstrcat:
filename sascbtbl 'sascbtbl.dat';
data _null_;
  length string1 string2 conctstr $200;
  length charptr $20;
  string1 = 'This is';
  string2 = ' a test!';
  charptr=modulec('lstrcat',string1,string2);
  concatstr = peekclong(charptr,200);
  put concatstr=;
run;
The following output appears in the log:
conctstr=This is a test!
Upon return from MODULEN, the pointer value is stored in RC. The example uses the PEEKCLONG function to return the 200 bytes at that location, using the $CSTR200. format to produce a blank-padded string that replaces the null termination.
For more information about the PEEKLONG functions, see PEEKCLONG Function in SAS Functions and CALL Routines: Reference and PEEKLONG Function in SAS Functions and CALL Routines: Reference.

Using Structures

Grouping SAS Variables as Structure Arguments describes how to use the FDSTART attribute to pass several arguments as one structure argument to a DLL routine. Refer to that section for an example of the GetClientRect attribute table and C language equivalent. This example shows how to invoke the GetClientRect function after defining the attribute table.
The most straightforward method works, but generates a warning message about the variables not being initialized:
filename sascbtbl 'sascbtbl.dat';
data _null_;
     hwnd=modulen('GetForegroundWindow');
     call module('GetClientRect',hwnd,
       left,top,right,bottom);
     put _all_;
     run;
To remove the warning, you can use the RETAIN statement to initialize the variables to 0. Also, you can use shorthand to specify the variable list in the MODULEN statement:
data _null_;
     retain left top right bottom 0;
     hwnd=modulen('GetForegroundWindow');
     call module('GetClientRect',hwnd,
           of left--bottom);
     put _all_;
     run;
Note that the OF keyword indicates that what follows is a list of variables, in this case delimited by the double-dash. The output in the log varies depending on the active window and looks something like the following:
HWND=3536768 LEFT=2 TOP=2 RIGHT=400
BOTTOM=587

Invoking a DLL Routine from PROC IML

This example shows how to pass a matrix as an argument within PROC IML. The example creates a 4x5 matrix. Each cell is set to 10x+y+3, where x is the row number and y is the column number. For example, the cell at row 1 column 2 is set to (10*1)+2+3, or 15.
The example invokes several routines from the theoretical TRYMOD DLL. It uses the changd routine to add 100x+10y to each element, where x is the C row number (0 through 3) and y is the C column number (0 through 4). The first argument to changd indicates what extra amount to sum. The changdx routine works just like changd, except that it expects a transposed matrix. The changi routine works like changd except that it expects a matrix of integers. The changix routine works like changdx except that integers are expected.
Note: A maximum of three arguments can be sent when invoking a DLL routine from PROC IML.
In this example, all four matrices x1, x2, y1, and y2 should become set to the same values after their respective MODULEIN calls. Here are the attribute table entries:
routine changd module=trymod returns=long;
arg 1 input num format=rb8. byvalue;
arg 2 update num format=rb8.;
routine changdx module=trymod returns=long
  transpose=yes;
arg 1 input num format=rb8. byvalue;
arg 2 update num format=rb8.;
routine changi module=trymod returns=long;
arg 1 input num format=ib4. byvalue;
arg 2 update num format=ib4.;
routine changix module=trymod returns=long
  transpose=yes;
arg 1 input num format=ib4. byvalue;
arg 2 update num format=ib4.;
Here is the PROC IML step:
proc iml;
     x1 = J(4,5,0);
     do i=1 to 4;
        do j=1 to 5;
           x1[i,j] = i*10+j+3;
           end;
        end;
     y1= x1; x2 = x1; y2 = y1;
     rc = modulein('changd',6,x1);
     rc = modulein('changdx',6,x2);
     rc = modulein('changi',6,y1);
     rc = modulein('changix',6,y2);
     print x1 x2 y1 y2;
     run;
The following are the results of the PRINT statement:
 X1
 20        31        42        53        64
130       141       152       163       174
240       251       262       273       284
350       361       372       383       394
 X2
 20        31        42        53        64
130       141       152       163       174
240       251       262       273       284
350       361       372       383       394
 Y1
 20        31        42        53        64
130       141       152       163       174
240       251       262       273       284
350       361       372       383       394
 Y2
 20        31        42        53        64
130       141       152       163       174
240       251       262       273       284
350       361       372       383       394