1本のソースモジュールで複数のOSに対応させる?
本来はOSによって対応するソースを分ける方が、作ることだけ考えれば簡単で楽です。しかしパッケージソフトなどの商用プログラムの場合、作って終わりになることはありません。バグの対応もあるし、バージョンアップなどのエンハンスもあります。むしろ作るコスト(工期、費用)よりもメンテナンスのコストの方が何倍も掛かります。
まったく違うOSならばともかく、基本的には互換だけど、細かなところに違いがある、と言う程度ならば、プログラム本来のロジックがまったく変わってしまうことはまずありません。9割は同じロジックのままでOKだが、残りの1割はOSに合わせて変える必要がある、と言う場合ならOS毎にソースを分けると、同じ9割の部分をダブって作成することになります。コピーしてペーストすればいいので、作る分には手間は掛かりませんが、後のメンテナンスはそういうわけにも行きません。
ほとんどのロジックが共通なら、なるべく同じソースモジュールで対応させたいものです。OSに依存する部分をサブルーチン化して対応し、サブルーチンだけをOS毎に分けて作る方法もあります。しかし場合によってはサブルーチン化することで、プログラム外の領域を参照しなくてはならなくなったり、返って元のロジックが複雑になってしまうこともあります。サブルーチン化などのプログラム分割ではない方法も併せて検討します。
AIFアセンブラー命令の活用
AIFはアセンブラー・ソース上での条件分岐を行います。条件が成立していれば、AIF命令の次から分岐先までの間にある、ソース・ステートメントはアセンブルされずにスキップされます。AIFは条件によってアセンブルさせるソース展開を変更させる場合などに用いることができます。
ここではプログラムを動かすOSによってソースモジュール内の展開命令を変更する目的でAIFを使う例を紹介します。
OS名をアセンブラー・パラメーターで指定する
AIF ('&SYSPARM' EQ 'XSP').XSPDCB1
SYSPRINT DCB DDNAME=SYSPRINT, +
DSORG=PS,MACRF=PM, +
RECFM=FBA,BLKSIZE=12100,LRECL=121
DC XL4'00' (RESERVED FOR XSP FCB LENGTH)
LNGDCB EQU *-SYSPRINT
AGO .ENDDCB1
.XSPDCB1 ANOP ,
SYSPRINT FCB ACNAME=SYSPRINT,DSORG=PS,MACRF=PM, +
RECFM=FBA,BLKSIZE=12100,LRECL=121
LNGDCB EQU *-SYSPRINT
.ENDDCB1 ANOP ,
//ASM EXEC PGM=ASMA90,COND=(EVEN),
// PARM=('OBJ,NODECK,LC(32767),XREF(SHORT),TERM,RLD,ASA,',
// 'US(MAP,WARN(11)),SYSPARM(MSP)')
OS名をアセンブル時にアセンブラーのPARMパラメーターで指定します。SYSPARM(os名)のパラメーターをPARMに追加します。指定したos名は、&SYSPARM変数で参照することができます。AIF文で&SYSPARM変数の値を参照して条件分岐するわけです。
サンプルではOSがXSPだったら、DCBではなくFCBを展開します。またXSPでなければ、DCBの後ろに4バイトのリザーブ領域を置きます。これはQSAMを使う場合、DCBは96バイトですがXSPのFCBは100バイトだからです。長さが違うぐらいではプログラムの動作に影響ありませんが、DCB/FCB以降のフィールドのオフセットが4バイト分ずれてしまいます。将来パッチによるプログラム修正などを考えると、命令や変数、定数のフィールドの位置は可能な限り同じに揃える方がいいです。そうすれば同じパッチをOS毎に分かれて生成された複数のロードモジュールに共通して適用することもできます。
OS名を設定するコピーメンバーを作成する
プログラムの本数が多ければ、共通して使用するアセンブラー変数の定義を独立させて、各モジュール内に定義せずに、変数を定義したメンバーをCOPY命令で展開させることができます。OS名の設定もその中に含めてしまう方法もあります。
(ZZGLOBS 共通変数定義メンバー)
GBLC &OPSYS OPERATING SYSTEM-ID
GBLB &SOSXA IBM MVS SYSTEM
GBLB &SOSF4 FACOM MSP SYSTEM
GBLB &SOSV3 HITAC VOS3 SYSTEM
GBLB &SOSX8 FACOM XSP SYSTEM
.*---------------------------------------------------------------------
COPY ZZOSTYP SET OPERATING SYSTEM-ID
&SOSXA SETB ('&OPSYS' EQ 'MVS')
&SOSF4 SETB ('&OPSYS' EQ 'MSP')
&SOSV3 SETB ('&OPSYS' EQ 'VOS3')
&SOSX8 SETB ('&OPSYS' EQ 'XSP')
.*---------------------------------------------------------------------
AIF (NOT &SOSXA).NXASP
SPLEVEL SET=6 WE WANT EXTEND EXPANSION FOR MVS MACRO
.NXASP ANOP ,
AIF (NOT &SOSF4).NF4SP
SPLEVEL SET=2 WE WANT EXTEND EXPANSION FOR MSP MACRO
.NF4SP ANOP ,
AIF (NOT &SOSV3).NV3SP
SPLEVEL SET=2 WE WANT EXTEND EXPANSION FOR VOS3 MACRO
.NV3SP ANOP ,
AIF (NOT &SOSX8).NX8SP
SPLEVEL SET=2 WE WANT EXTEND EXPANSION FOR XSP MACRO
.NX8SP ANOP ,
(ZZOSTYP OS名設定メンバー)
&OPSYS SETC 'MVS' SET OPERATING SYSTEM-ID = MVS
サンプルではZZGLOBSと言う名前で共通変数定義メンバーを作っています。その中からOS名を設定するZZOSTYPメンバーをCOPY命令で呼び出します。ZZGLOBSは各OSで共用できますが、ZZOSTYPだけはOS毎に中身を変える必要があります。例えば、MSP、VOS3、XSPです。
//ASM EXEC PGM=ASMA90,COND=(EVEN),
// PARM=('OBJ,NODECK,LC(32767),XREF(SHORT),TERM,RLD,ASA,',
// 'US(MAP,WARN(11))')
//SYSLIB DD DISP=SHR,DSN=PP1RJT.VOS3.MODGEN
// DD DISP=SHR,DSN=PP1RJT.COMMON.MACLIB
// DD DISP=SHR,DSN=PP1RJT.SOURCE
// DD DISP=SHR,DSN=PP1RJT.VOS3.MACLIB
ZZOSTYPは各OS専用のライブラリーに入れ(この例ではPP1RJT.VOS3.MODGEN)、アセンブル時にマクロライブラリーの定義でそのデータセットを指定します。MVS用のモジュールのアセンブル時はMVS用に設定するZZOSTYPが、MSP用のモジュールのアセンブル時はMSP用に設定するZZOSTYPが、それぞれ展開されるようにします。
AIF (&SOSV3).V3NSWAP
SLR R1,R1 WE DON'T CARE IF WE SWAP
SYSEVENT TRANSWAP MAKE US TO NON-SWAPABLE
AGO .ENDNSWP
.V3NSWAP ANOP ,
VOS3用のセットアップ処理
SYSEVENT ........ MAKE US NON-SWAPABLE
.ENDNSWP ANOP ,
ソースモジュールの中では、ZZGLOBSに設定した&OPSYS変数や&SOSXAフラグなどを参照して、必要に応じてソースの展開を変更します。
サンプルでは、VOS3用モジュールの場合は、.V3NSWAPで示されたラベルへ飛んで、VOS3用以外とは別の命令列を展開します。
ファンクションで完全に分割できるならサブルーチン化がいいでしょう。汎用化して特定のプログラムに依存しないデザインにすれば、さまざまなプログラムで共用することもできます。しかしSAVEAREAを持たないリエントラントルーチンや、プログラム内のDCB領域などのように、サブルーチンを呼び出すことが困難な場合や、ファンクションルーチンにならないものは、無理に分割しなくても、必要な部分の命令展開をアセンブル時にOSに合わせて変更するようにすれば、オブジェクトとロードモジュールは別になっても、元のソースモジュールは共通して管理、保守することができるようになります。

