Accessing External DLLs from SAS under Windows |
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, so 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 the PEEKCLONG function and the PEEKLONG function in SAS Language Reference: Dictionary.
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.;
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
Copyright © 2010 by SAS Institute Inc., Cary, NC, USA. All rights reserved.