2013年11月5日火曜日

NOTE: 要約化中にディスク処理が発生しました。

時間が出来たときに、ツールで以下のメッセージを捕捉するように修正する。

  • Processing on disk occurred during summarization. Peak disk usage was approximately nnn Mbytes. Adjusting SUMSIZE may improve performance. 
  • NOTE: 要約化中にディスク処理が発生しました。 ディスクの最大使用はおよそ 2454 M バイトです。 SUMSIZE の調整によって、パフォーマンスを改善できるかもしれません。

Calls a specific routine or module that resides in an external dynamic link library (DLL).

SASからWindowsのAPIを呼び出す方法を整理しています。
その理由を列挙しますと

  • SASのバージョンアップの作業で、Xステートメントが規制されている
  • SASデータセットをCOPYプロシージャで移行するとタイムスタンプが維持できない
  • タイムスタンプが変わると元ファイルが更新されたときに再転送が必要かわからない
  • DOS窓からの実行だと遅い

VBScriptから制御する手もありますが、古いデータセットから順に移行したり、停止の制御を考えるとSAS1本でやるのが良さそうです。

基本はDLLを呼び出すためのI/Fを"The SASCBTBL Attribute Table"で定義して、MODULE関数で呼び出します。この仕組みが繊細で、定義を間違えるとSAS.EXEが落ちます。WinAPIの型を調べて、変数を割り当てるのは面倒な作業です。

2013年11月4日月曜日

SASからCopyFileAを呼び出して、ファイルをコピー

SASからWindowsのAPIをコールするサンプルコードです。バージョンアップによるデータの引越しで、今回はWinAPIを使ったツールを作る予定です。動作確認したのは、Windows7+SAS9.2です。

ネットにはいくつか見本が転がっていますが、PDFで文字が化けていたり、APIがまったく変わってしまったようなものもあります。素直に動くものが少ないので、いくつかの手本を元に作成しました。APIの定義をテキストで別に用意している例が多いのですが、カタログを使った例がまとまりが良いです。

/*
*++
* WinAPI(CopyFileA)を呼び出して、ファイルをコピー
*--
*/

filename winapi catalog 'WORK.Windows.WINAPI.SOURCE';

data _null_;
 file winapi;
 input;
 put _infile_;
 cards4;
routine CopyFileA
 module=KERNEL32
 minarg=3 maxarg=3 stackpop=called
 returns=ushort
;
arg 1 input char format=$cstr200.; * FROM ;
arg 2 input char format=$cstr200.; * TO ;
arg 3 input num format=pib4. byvalue;
* 1=Do Not Overwrite Existing ;
* 0=Overwrite Existing File ;
;;;;
run;

filename winapi;
filename sascbtbl catalog 'WORK.Windows.WINAPI.SOURCE';

data _null_;
 rc = modulen('*e', 'CopyFileA', 'c:\temp\foo.txt', 'c:\temp\foo_copy.txt', 0);
 put _all_;
run;

filename sascbtbl clear;


SASからSetFileTimeを呼び出して、ファイルのタイムスタンプを設定

コードに間違いがあったので修正しました。
SASからWindowsのAPIをコールするサンプルコードです。バージョンアップによるデータの引越しで、今回はWinAPIを使ったツールを作る予定です。動作確認したのは、Windows7+SAS9.2です。ネットにはいくつか見本が転がっていますが、PDFでうまく転記できなかったり、APIがまったく変わってしまったようなものもありました。

/*
*++
* WinAPI(SetFileTime)を呼び出して、ファイルのタイプスタンプを更新
*--
*/
filename winapi catalog 'WORK.Windows.WINAPI.SOURCE';

data _null_;
  file winapi;
  input;
  put _infile_;
  cards4;
routine CreateFileA
   module=Kernel32
   minarg=7
   maxarg=7
   stackpop=called
   returns=long
;
arg 1 char input format=$cstr260.; *  LPCTSTR lpFileName;
arg 2 num  input format=pib4. byvalue;  * DWORD dwDesiredAccess;
arg 3 num  input format=pib4. byvalue;  * DWORD dwShareMode;
arg 4 num  input format=pib4. byvalue;  * LPSECURITY_ATTRIBUTES lpSecurityAttributes (set to null,pass 0 byvalue);
arg 5 num  input format=pib4. byvalue;  * DWORD dwCreationDispostion;
arg 6 num  input format=pib4. byvalue;  * DWORD dwFlagsAndAttributes (set to zero);
arg 7 num  input format=pib4. byvalue;  * HANDLE hTemplateFile (ignored);
*-------------------------------------------------------------;


routine CloseHandle
   module=Kernel32
   minarg=1
   maxarg=1
   stackpop=called
   returns=long
;
arg 1 num  input format=pib4. byvalue;  * HANDLE hObject;

*-------------------------------------------------------------;

routine SystemTimeToFileTime
 minarg=9 
 maxarg=9 
 stackpop=called 
 module=Kernel32 
 returns=long
; 
arg 1 num input  fdstart format=pib2.; * WORD wYear ; 
arg 2 num input          format=pib2.; * WORD wMonth ; 
arg 3 num input          format=pib2.; * WORD wDayOfWeek ; 
arg 4 num input          format=pib2.; * WORD wDay ; 
arg 5 num input          format=pib2.; * WORD wHour ; 
arg 6 num input          format=pib2.; * WORD wMinute ; 
arg 7 num input          format=pib2.; * WORD wSecond ; 
arg 8 num input          format=pib2.; * WORD wMilliseconds ; 
arg 9 num output fdstart format=pib8.;

*-------------------------------------------------------------;

routine LocalFileTimeToFileTime 
 minarg=2 
 maxarg=2 
 stackpop=called 
 module=kernel32 
 returns=long
; 
arg 1 input  fdstart num format=pib8.; * lpLocalFileTime // converted file time; 
arg 2 output fdstart num format=pib8.; * lpFileTime, // UTC file time to convert; 

*-------------------------------------------------------------;

routine SetFileTime 
  minarg=4 
  maxarg=4 
  stackpop=called 
  module=Kernel32 
  returns=long
; 
arg 1 num input byvalue format=pib4.; * HANDLE hFindFile // file search handle ; 
arg 2 num input fdstart format=pib8.; * CONST FILETIME * lpCreationTime, // pointer to creation file time ; 
arg 3 num input fdstart format=pib8.; * CONST FILETIME * lpModifiedTime, // pointer to creation file time ; 
arg 4 num input fdstart format=pib8.; * CONST FILETIME * lpAccessedTime, // pointer to creation file time ; 
;;;;
run;

filename winapi;
filename sascbtbl catalog 'WORK.Windows.WINAPI.SOURCE';

%macro touch(path=, year=);
 data _null_ ; 

  GENERIC_READ = 080000100x; /* GENRIC_READ + FILE_WRITE_ATTRIBUTES(0x0100) */
  FILE_SHARE_READ = 1;
  OPEN_EXISTING = 3;


  length path $260; 
  path = "&path";
  HANDLE = modulen('CreateFileA', path, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0,0);

  If HANDLE >= 1 Then Do; 

   cdt_n = datetime();
   MINUTE = Minute( cdt_n); 
   HOUR = Hour( cdt_n); 
   DAY = DAY( DatePart( cdt_n) ); 
   DAYOFWK = WeekDay( DatePart( cdt_n) ); 
   MONTH = MONTH( DatePart( cdt_n) ); 
   YEAR = &year;
   SECONDS = 0;
   MSECONDS = 0;
   CREATED = .;
   CREATED1 = .;

   rc = ModuleN('SystemTimeToFileTime', YEAR, MONTH, DAYOFWK,  DAY, HOUR, MINUTE, SECONDS, MSECONDS, CREATED); 
   rc = ModuleN('LocalFileTimeToFileTime', CREATED, CREATED1);
   put rc= created= created1=;

   ACCESSE1 = CREATED1;
   WRITTEN1 = CREATED1;

   rc = ModuleN('SetFileTime', HANDLE, CREATED1, ACCESSE1, WRITTEN1);
   put rc=;
   rc = Modulen('CloseHandle', HANDLE); 
   put rc=;
  end; 
 run; 
%mend;

%touch(path=c:\temp\foo.txt, year=2009);

filename sascbtbl clear;