高度な検索
Developer Connection
Member Login ログイン | ご入会 ADC連絡先

Technote 1119

Serial Port Apocrypha


目次

両APIの共通点

シリアルポートの旧API

Open TransportのAPI

両アービトレータの物語

要約
のテックノートはMac OS上でシリアルポートを利用する際の様々な注意事項について解説します。このテックノートに記載されている情報は他でも公開されていますが、ここではすべての注意点を一箇所にまとめています。

具体的にはシリアルポートの旧APIとOpen TransportのAPIを使って、シリアルポート名の取得、シリアルポートのオープン、クローズ、そして譲る際の正しい方法を説明します。また、旧シリアルポートアービトレータやOpen Transportのシリアルポートアービトレータの設計と使い方を解説します。

このテックノートはシリアルポートを利用するすべてのデベロッパが対象です。


両APIの共通点

Mac OSではシリアルポートを利用するためのAPIが2つあります。デバイスマネージャの'DRVR'ドライバを元にした旧API(Inside Macintosh: Devicesを参照)とOpen TransportのAPI(Inside Macintosh: Open Transportを参照)です。以下の2点は両APIが対象です。

必要な時だけオープン/クローズ

コンピュータのシリアルポートは同時使用のできないリソースです。一つのアプリケーションがシリアルポートをオープンしている限り、他のアプリケーションはそのシリアルポートを利用できません。このため、シリアルポートは必要な時だけオープンして、使い終わったらクローズする必要があります。

例えば、ユーザ登録の一貫としてシリアルポートを利用するようなアプリケーションは、ユーザ登録の開始直前にシリアルポートをオープンして、使い終わったらすぐにクローズします。

シリアルポートを譲る

シリアルポートは受身的に利用することが可能です。この場合、シリアルポートを利用したいアプリケーションから要求があれば、シリアルポートを譲ることが可能です。そして、アプリケーションがシリアルポートを使い終わったら、再びシリアルポートを受身的に利用できます。

例えば、Appleリモートアクセス(バージョン2.1以前)を待ち受け状態にセットしても、FreePPPなどでPPP接続が行えます。これは、AppleリモートアクセスがFreePPPにシリアルポートを譲っているからです。FreePPPがシリアルポートを閉じると、Appleリモートアクセスは再びシルアルポートにアクセスして、待ち受け状態となります。

シリアルポートの旧API

シリアルポートの旧APIはInside Macintosh: Devicesに記述されている通り、デバイスマネージャのドライバ'DRVR'を元にしています。ここではシリアルポートの旧APIでシリアルポート名の取得、シリアルポートのオープン、クローズ、そして譲る際の正しい方法を説明します。

シリアルポート名の取得

特定のコンピュータで利用できるシリアルポートの一覧を得るにはCommunications Toolboxの一部であるCommunications Resource Manager(CRM)のCRMSearch ()関数を利用します。残念ながら、Communications Resource Managerの文献(Inside the Macintosh Communications Toolbox)はオンラインでご覧になれないので、CRMSearch ()についての説明を探すのに苦労するかもしれません。ここではCRMSearch ()を利用する際のサンプルコードを用意しましたので、参考にして下さい。



static void PrintInfoAboutAllSerialPorts (void)
//  このコンピュータで利用できるシリアルポートのリスト
//  を出力します。シリアルポート名、入力ドライバ名、
//  出力ドライバ名をstdoutに出力します。一般的には以下
//  のようなコードを使って、シリアルポートのポップアップ
//  メニューを構築します。
{
    CRMRec commRecord;
    CRMRecPtr thisCommRecord;
    CRMSerialPtr serialPtr;

    (void) InitCRM ();
     
    //  シリアルポートのみを返すよう、commRecordを設定します。

    commRecord.crmDeviceType = crmSerialDevice;
    commRecord.crmDeviceID = 0;
  
    //  CRMSearch ()を繰り返し呼び出して、すべてのシリアルポートの
    //  情報を得ます。
  
    thisCommRecord = &commRecord;
    do {
        thisCommRecord = (CRMRecPtr) CRMSearch ((CRMRecPtr) thisCommRecord);
        if (thisCommRecord != nil) {
             
            //  シリアルポートのCRMRecを得たら、crmAttributesフィールドを
            //  CRMSerialPtrにキャストして、シリアルポートの情報フィールドに
            //  アクセスします。
  
             serialPtr = (CRMSerialPtr) thisCommRecord->crmAttributes;
  
            //  このシリアルポートの情報を出力する。
             
            printf ("We have a port called: "%#s"\n", *(serialPtr->name));
            printf ("   input driver named: "%#s"\n", *(serialPtr->inputDriverName));
            printf ("  output driver named: "%#s"\n", *(serialPtr->outputDriverName));
            printf ("\n");
  
            //  CRMSearch ()が次のデバイスを返すように指定する。
             
            commRecord.crmDeviceID = thisCommRecord->crmDeviceID;
        }
    } while (thisCommRecord != nil);
}

?/TD>
註:
CRMはシステムソフトウェアバージョン7.0以降に標準搭載されています。システムソフトウェアバージョン6ではオプションなので、システムソフトウェアバージョン6で動作する製品を開発する場合はCRMがインストールされていることを確認する必要があります。Gestalt ()関数のgestaltCRMAttrセレクタを使って、gestaltCRMPresentフラグがセットされていることを確認して下さい。

重要:
CRMに登録されているシリアルポートは標準のシリアルポートと同じように動作するはずです。しかし、標準シリアルポートの動作を厳密に再現できない場合があります(アップルコンピュータやサードパーティの製品を含む)。CRMに登録されているシリアルポートを利用する場合はこのような制限に注意して下さい。例えば、MIDI系の外部クロックモード(csCode 15)を利用するアプリケーションは、シリアルポートが外部クロックモードを受け付けない事態も想定して設計する必要があります。


シリアルポートのオープン

シリアルポートの正しいオープンの仕方はMac OS SDKのCDに含まれているARA APIの文献に記述されています。しかし、あまりにも人目につかない文献な上にサンプルコードも古いので、以下に同じ内容を再度記述します。

ルールは至って簡単です:

シリアルポートアービトレータが存在する場合はOpenDriver ()を直接呼び出してシリアルポートを開け。存在しない場合はユニットテーブルを参照して、ドライバが既に開いていない場合だけシリアルポートを開け。

以下のサンプルコードではこのルールに従ってシリアルポートを開いています。


static OSErr OpenOneSerialDriver (ConstStr255Param driverName, short *refNum)
//  シリアルドライバの正しいオープンの仕方。まず、シリアルポートアービトレータが
//  存在するかどうかを確認します。存在する場合はいきなりOpenDriver ()を
//  呼び出して、シリアルポートアービトレータに判断を任せます。シリアル
//  アービトレータが存在しない場合はユニットテーブルを見て、ドライバが既に
//  開いているかどうかを確認します。
{
    OSErr err;

    if (SerialArbitrationExists ()) {
        err = OpenDriver (driverName, refNum);
    } else {
        if (DriverIsOpen (driverName)) {
            err = portInUse;
        } else {
            err = OpenDriver (driverName, refNum);
        }
    }
    return err;
}
  
static OSErr OpenSerialDrivers (ConstStr255Param inName, ConstStr255Param outName, 
                                SInt16 *inRefNum, SInt16 *outRefNum)
//  シリアルポートの入力と出力ドライバをオープンして、各々のrefNumを返します。
//  どちらかのドライバがオープンできなかった場合はrefNumが両方ともゼロとなります。
{
    OSErr err;
     
    err = OpenOneSerialDriver (outName, outRefNum);
    if (err == noErr) {
        err = OpenOneSerialDriver (inName, inRefNum);
        if (err != noErr) {
            (void) CloseDriver (*outRefNum);
        }
    }
    if (err != noErr) {
        *inRefNum = 0;
        *outRefNum = 0;
    }
    return err;
}


?/TD> 上記のサンプルコードでは、出力ドライバを先にオープンしています。標準シリアルポートのドライバやその他のCRM登録ドライバをオープンする場合は出力ドライバを先にオープンして下さい。これは、出力ドライバがシステムリソースの確保やシリアルポートの利用状況の確認を行っているからです。標準シリアルポートの場合、出力ドライバがエラーなくオープンできたら、入力ドライバも必ずオープンできるはずです。ただし、CRMに登録されているドライバがすべてそうだとは限らないので、必ずOpenDriver ()の返す値をチェックして下さい。

シリアルポートアービトレータが存在するかどうかは以下のように判断します。

enum {
    gestaltSerialPortArbitratorAttr = 'arb ',

    gestaltSerialPortArbitratorExists = 0
};
  
static Boolean SerialArbitrationExists(void)
//  Gestalt ()関数を使って、シリアルポートアービトレータがこの
//  コンピュータで存在するかどうかを確認します。
{
    Boolean result;
    long response;
     
    result = (Gestalt (gestaltSerialPortArbitratorAttr, &response) == noErr &&
              (response & (1 << gestaltSerialPortArbitratorExists) != 0) != 0));
    return result;
}


最後に必要なのは、DriverIsOpen ()と言う関数です。この関数はデバイスのユニットテーブルを参照して、特定のシリアルドライバが既にオープンされているかどうかを確認します。この関数はローメモリ変数を直接利用しているため良いコード例とは言えませんが、シルアルポートアービトレータが存在しない場合のみ実行されるので、あまり利用されないことを期待できます。


static Boolean DriverIsOpen (ConstStr255Param driverName)
//  指定されたドライバをユニットテーブル内で探し出して、
//  既にオープンされていればtrueを返します。ドライバが
//  オープンされていない場合や存在しない場合はfalseを
//  返します。
{
    Boolean found;
    Boolean isOpen;
    short unit;
    DCtlHandle dceHandle;
    StringPtr namePtr;
     
    found = false;
    isOpen = false;
     
    unit = 0;   
    while (!found && (unit < LMGetUnitTableEntryCount ())) {
     
        //  Device Control Entryのハンドルを得る。GetDCtlEntry ()
        //  はドライバrefNumを受け付けるので、NOTを使って
        //  ユニット番号から得ます。
         
        dceHandle = GetDCtlEntry (~unit);

        if (dceHandle != nil && (**dceHandle).dCtlDriver != nil) {
         
            //  RAMベースドライバの場合、dCtlDriverはハンドルです。
            //  それ以外の場合はポインタ扱いです。
             
            if (((**dceHandle).dCtlFlags & dRAMBasedMask) != 0) {
                namePtr = &(**((DRVRHeaderHandle) (**dceHandle).dCtlDriver)).drvrName [0];
            } else {
                namePtr = &(*((DRVRHeaderPtr) (**dceHandle).dCtlDriver)).drvrName [0];
            }
     
            //  ドライバ名のポインタを得たら、オープンされているかどうかは
            //  フラグを参照して確認します。
             
            if (EqualString (driverName, namePtr, false, true)) {
                found = true;
                isOpen = ((**dceHandle).dCtlFlags & dOpenedMask) != 0;
            }
        }
        unit += 1;
    }
    return isOpen;
}

?/TD>

註:
ローメモリアクセス関数LMGetUnitTableEntryCount ()は"LoMem.h"で定義されていますが、InterfaceLibではエクスポートされていません。Universal Interfaces 2.xを元にCFMコードをビルドしようとするとリンクの際にエラーとなります。この場合はローメモリアクセス関数を独自に書くか、新たにマクロが加えられたUniversal Interfaces 3.xをお使い下さい。


シリアルポートのクローズ

シリアルポートのオープンに成功したら、使い終わった時点で必ずクローズして下さい。シリアルポートのクローズはCloseDriver ()で行います。また、入力と出力ドライバの両方をクローズしなければなりません。以下のサンプルコードはシリアルポートの正しいクローズの仕方です。

static OSErr CloseSerialDrivers(SInt16 inRefNum, SInt16 outRefNum)
 {
     OSErr err;
  
     (void) KillIO(inRefNum);
     err = CloseDriver(inRefNum);
     if (err == noErr) {
         (void) KillIO(outRefNum);
         (void) CloseDriver(outRefNum);
     }
     
     return err;
 }


アプリケーションが正常終了しなかった場合でもシリアルポートのドライバはクローズして下さい。シリアルポートのドライバをクローズしないままアプリケーションが終了してしまうと、他のアプリケーションはコンピュータを再起動するまでシリアルポートを利用できません。

アプリケーションが正常終了しなかった場合でもシリアルポートのドライバを確実にクローズする方法は以下のようなものがあります。

  • CFMアプリケーションの場合は、CFMフラグメントの終了関数を使ってシリアルポートのクローズが可能です。詳しくはInside Macintosh: PowerPC System Softwareをご覧下さい。
  • Thread Managerが存在する場合は、SetThreadTerminator ()でアプリケーションのメインスレッドの終了関数を指定することが可能です。
  • 上記の方法が使えない場合でも、ExitToShell ()をパッチすることが可能です。

註:
Inside Macintosh II (247〜250ページ)はRAMとROMのシリアルドライバの違いやRAMSDOpen ()RAMSDClose ()について解説していますが、現在のMac OSには適応しないので、これらの記述は無視して下さい。


シリアルポートを譲る

シリアルポートの旧APIでは、シリアルポートを受身的に利用してダイナミックに他プログラムに譲ることに関して、ほとんどサポートされていないと考えて良いでしょう。AppleリモートアクセスはLink Tool ManagerのAPIを使ってシリアルポートを管理していますが、アップルコンピュータはこのAPIを公開していませんので、利用することはできません。

シリアルポートを受身的に利用して且つ他プロセスに譲る必要がある場合はOpen TransportのシリアルポートAPIを使って下さい。

Open TransportのAPI

Mac OS上のシリアル通信のもう一つのAPIはネットワーク関係のAPIとよく似ているOpen TransportのAPIです。Open Transportの現バージョン(このテックノートが書かれた時は1.3)は、旧シリアルドライバの上で動くレイヤーとなっています。つまり、どちらのAPIを使っても、シリアルポートはアプリケーション間でうまく共有しなければなりません。

Open Transportの一般的な説明についてはInside Macintosh: Open Transportをご覧下さい。

シリアルポート名の取得

Open Transportのシリアル通信APIを使う場合は、OTGetIndexedPort ()をループで呼び出して、ポート種別がkOTSerialDeviceの物を探します。以下のサンプルコードをご覧下さい。

static OSStatus PrintSerialPortInfo (const OTPortRecord *portRecord)
//  portRecordのシリアルポート情報を出力します。
{
    Str255 userVisibleName;
     
    //  OTGetUserPortNameFromPortRef ()は<OpenTptConfig.h>で定義されている、
    //  あまり知られていない関数です。しかし、Open Transportのポート名を
    //  得るのに非常に便利です。
     
    OTGetUserPortNameFromPortRef (portRecord->fRef, userVisibleName);
     
    printf ("Found a serial port with port reference $%08lx:\n", portRecord->fRef);
    printf ("  User visible name is                       "%#s".\n", userVisibleName);
    printf ("  String to pass to OTCreateConfiguration is "%s".\n",  portRecord->fPortName);
    printf ("  Name of provider module is                 "%s".\n",  portRecord->fModuleName);
    printf ("\n");
     
    return kOTNoError;
}
  
static OSStatus OTFindSerialPorts (void)
//  Open Transportを使って、利用可能なシリアルポートのリストを出力します。
{
    OSStatus err;
    Boolean portValid;
    SInt32 portIndex;
    OTPortRecord portRecord;
    UInt16 deviceType;

    //  portIndexはゼロから開始して、シリアルポートの数を超えるまで、
    //  OTGetIndexedPort ()を呼び出します。

    portIndex = 0;
    err = kOTNoError;
    do {
        portValid = OTGetIndexedPort (&portRecord, portIndex);
        if (portValid) {

            //  有効なポートについて、種別を得ます。シリアルポートであれば、
            //  エイリアスではないことだけ確認して、PrintSerialPort ()を
            //  呼び出して、ポート情報を出力します。エイリアスも含んでしまうと
            //  標準デスクトップ機の場合は3つのシリアルポートが出力されます:
            //  「serialA」、「serialB」、「serial」。
         
            deviceType = OTGetDeviceTypeFromPortRef (portRecord.fRef);
            if (deviceType == kOTSerialDevice && 
                    (portRecord.fInfoFlags & kOTPortIsAlias) == 0) {
                err = PrintSerialPortInfo (&portRecord);
            }
        }
        portIndex += 1;
    } while (portValid && err == kOTNoError);
    return err;
}

?/TD>

重要:
PowerPCコンピュータ上では、OTGetUserPortNameFromPortRef ()は68Kのプログラムから利用できません。詳しくはDTS Q&A NW 48 "68K Open Transport Code on Power Macintoshes"をご覧下さい。


Open Transport 1.1.1以降では、CRMのシリアルポートは自動的にOpen Transportのシリアルポートとしても登録されます。従って、内蔵シリアルポートとサードパーティのシリアルポートは上記のサンプルコードによって認識されます。Gestalt ()関数を使えば、インストールされているOpen Transportのバージョンが得られます。詳しくはDTS Q&A NW 41 "Gestalt Selectors for Mac Networking"をご覧下さい。

シリアルポートのオープン

使用するシリアルポートを決めたら、OTOpenEndpoint ()でそのシリアルポートに対するエンドポイントを作成します。この時点でシリアルドライバはまだオープンされないので安心です。Open Transportは、エンドポイントを実際に接続するまでシリアルポートのドライバをオープンしません。

シリアルポートを普通に使用するにはqlen = 0OTBind ()を呼び出して、引き続きOTConnect ()を呼び出します。シリアルポートはOTConnect ()を呼び出すまでオープンされません。

シリアルポートを受身的に使用する場合はqlen = 1OTBind ()を呼び出します。この場合、シリアルポートはすぐにオープンされます。そして、シリアルポート経由でデータが送られてくると、コールバック関数にT_LISTENイベントが送信されます。

シリアルポートを利用する場合は、シリアルポートに関する重要な通知を受けるため、OTRegisterAsClient ()を呼び出してOpen Transportのクライエントとしてプログラムを登録して下さい。具体的には、kOTYieldPortRequestシリアルポートを譲るを参照)、kOTProviderIsDisconnectedkOTProviderIsReconnectedの通知を適切に処理しなければなりません。

シリアルポートのクローズ

シリアルポートを使い終わったら、必ずクローズして下さい。シリアルポートのドライバが実際にクローズされるタイミングは、シリアルポートをオープンした方法に左右されます。

通常はOTSndDisconnect ()で接続を解除します(エンドポイントはT_IDLE状態になります)。

受身的にシリアルポートを利用している場合は、OTUnbind ()でバインドが解除します。

また、OTCloseProvider ()でエンドポイントをクローズした場合も、シリアルポートのドライバがクローズされます。

シリアルポートの旧APIと違って、Open Transportはシリアルポートを利用しているアプリケーションを常に監視しています。アプリケーションがシリアルポートの利用中に異常終了した場合でも、エンドポイントとシリアルポートのドライバはすべてクローズされます。ただし、アプリケーション以外(コードリソースや共有ライブラリ)はメモリからアンロードされる前に必ずCloseOpenTransport ()を呼び出す必要があります。

シリアルポートを譲る

重要:
ここではOpen Transport上でのシリアルポート管理の本来の仕組を解説しています。しかし、Open Transportの最新バージョン(このテックノートが書かれた時は1.3)はバグがあり、シリアルポートの管理がうまく動作しません。アプリケーション側から見ると、受身的にシリアルポートを利用しているプログラムがあると、例えそのシリアルポートが使用可能であっても、OTYieldPortRequest ()は常にkBUSYErrを返します。


シリアルポートの旧APIと違って、Open Transportはシリアルポートアービトレーション用のAPIを備えています。シリアルポートアービトレーションは以下のような手順で行われます:

  1. シリアルポートを受身的に利用するプログラムはシリアルポートを開いて、qlen = 1でバインドします。すると、外部からの接続に対して、待機状態となります。

  2. シリアルポートを通常に利用するアプリケーションはOTConnect ()でシリアルポートを開きます。しかし、待機しているプログラムが既にシリアルポートを利用しているため、OTConnect ()kBUSYErrエラーを返します。

  3. このエラーに対し、アプリケーションはOTYieldPortRequest ()を呼び出します。

  4. Open Transportは指定されたシリアルポートを受身的に利用しているプログラム(OTRegisterAsClient ()で登録されているプログラムに限る)にkOTYieldPortRequest通知を送ります。

  5. この時、待機しているプログラムはシリアルポートを譲るか、要求を拒否することができます。

  6. 待機しているプログラムが要求を拒否した場合、OTYieldPortRequest ()はエラーを返し、アプリケーションはシリアルポートを利用することができません。この時、OTYieldPortRequest ()はシリアルポートの利用を拒否したプログラム名と拒否理由をリストにして返します。アプリケーションはこの情報に基づいて「シリアルポート使用中」ダイアログを表示することができます。

  7. 待機しているプログラムがすべてシリアルポートを譲った場合、アプリケーションはシリアルポートを利用することができます。この時、アプリケーションは特定の時間内(およそ10秒)にシリアルポートを開かなければなりません(qlen = 1でバインドするか、OTConnect ()を呼びだします)。特定時間内にシリアルポートを開かないと、シリアルポートの利用件は再び待機していたプログラムに移ります。

  8. アプリケーションがシリアルポートを開くと、待機していた各プログラムにはkOTProviderIsDisconnected通知が送られます。

  9. アプリケーションがシリアルポートを使い終わって閉じると、待機していた各プログラムにはkOTProviderIsReconnected通知が送られます。


上記の手順は少し複雑ですが、Inside Macintosh: Open Transportでは詳しく解説されています。

両アービトレータの物語

シリアルポートアービトレータはMac OSの中ではもっとも知られていない部分の一つと言えるかもしれません。一つの理由はシリアルポートアービトレータがMac OSの標準インストールではなく、Appleリモートアクセスによってインストールされることです。ここではシリアルポートアービトレータの重要性と両シリアルポートアービトレータの機能について解説します。

問題の発端

Mac OSのデバイスマネージャは元々奇妙な設計がされていました。特定のドライバが開かれると、その後OpenDriver ()はそのドライバのrefNumを返すだけで、ドライバ側にはなんの通知もされません。これは多くのドライバ(例えばフロッピーディスクドライバ)にとって問題となることはありませんが、シリアルポートドライバのように、常に一つのクライエントしかサポートしないドライバにとって、シリアルポートを「所有」できない制限は大きな問題です。

MultiFinderの登場までMacintoshは一度に一つのアプリケーションしか走らせることができませんでした。従って、動作中のアプリケーションがシリアルポートを所有するモデルが成立しました。しかし、MultiFinderの登場で複数のアプリケーションが同時に走る状況となり、シリアルポートの所有問題が発生しました。

問題への対応

この問題への対応は単純でした。シリアルポートが既に開いていれば、他のアプリケーションが利用していると言うことなので、この場合はシリアルポートの使用を控えることで解決しました。デバイスのユニットテーブルを直接参照する必要があったため、奇麗な解決策とは言えませんでしたが、動作上の問題はありませんでした。

新たな問題

Appleリモートアクセスの登場で新たな問題が発生しました。Appleリモートアクセスはバックグラウンドで待機し、電話がかかってくるのを待ち受ける機能があります。しかし、Appleリモートアクセスが待機している間は、シリアルポートが常に使われているため、Appleリモートアクセスの待機モードを切らないと、シリアルポートを利用する通常のアプリケーションは利用できませんでした。

この問題は、最初の問題への対応策の結果、解決することが非常に難しくなりました。上記のモデルに従って設計されたアプリケーションはデバイスのユニットテーブルをチェックするようになりましたが、Appleリモートアクセスが既にシリアルポートを利用するため、これらのアプリケーションはシリアルポートを使用中と見なし、OpenDriver ()も呼びだしません。Appleリモートアクセスはシリアルポートが利用されようとしていることを関知できないため、自動的に待機モードを切ることも不可能でした。

新たな問題への対応

この問題については2段階の対策がとられました。まず、デベロッパがシリアルポートを利用する際のルールが変わりました。このルールは上記でも述べられていますが、ここで繰り返します。

シリアルポートアービトレータが存在する場合はOpenDriver ()を直接呼び出してシリアルポートを開け。存在しない場合はユニットテーブルを参照して、ドライバが既に開いていない場合だけシリアルポートを開け。

2つ目に、Appleリモートアクセスはシリアルポートアービトレータとともに出荷されました。シリアルポートアービトレータは_Open_Closeをパッチして、シリアルポートを利用するアプリケーションを監視します。アプリケーションがシリアルポートを開くと、シリアルポートはそのアプリケーションの所有物となり、他のアプリケーションが同じシリアルポートを開こうとすると、シリアルポートアービトレータはportInUseエラーを返します。

註:
シリアルポートアービトレータが返すportInUseエラーコードはシリアルハードウェアがLocalTalkなどの他のドライバで使われている場合に返すportInUseと同様です。エラーコードは同じでも、エラーの状況は大きく違います。シリアルポートアービトレータのportInUseは他プロセスによるシリアルポートの利用を意味します。シリアルドライバのportInUseはシリアルハードウェアが他のドライバに利用されていることを意味します。


シリアルポートアービトレータはAppleリモートアクセス1.0の一部として初めて出荷されました。シリアルポートアービトレータはAppleリモートアクセスのLink Tool Managerを利用していますが、Link Tool Manager自体のAPIは公開されていません。

さらに新しい対策

コンピュータ業界は静的なものではないので、Mac OSも、Appleリモートアクセスもバージョンアップしています。Appleリモートアクセス3.0では、Link Tool Managerとシリアルポートアービトレータを無くす方向で設計されていました。しかし、デベロッパはシリアルポートアービトレータに慣れていたため、いきなりOpenDriver ()を呼び出すアプリケーションが多数あり、シリアルポートアービトレータを無くすとこれらのアプリケーションは他のアプリケーションと接触してしまう恐れありました。

このため、Appleリモートアクセス3.0には過去のシリアルポートアービトレータの機能を含む、新しいシリアルポートアービトレータ(OpenTpt Serial Arbitrator)が導入されました。初代シリアルポートアービトレータと同様、_Open_Closeはパッチされており、シリアルポート管理が行われます。従って、上記のルールは変わりません。

註:
OpenTpt Serial ArbitratorはOT/PPP 1.0で初めて登場しました。しかし、OT/PPPは単にAppleリモートアクセス3.0の縮小版なので、ここでは同等のソフトウェアと見なします。

註:
新旧シリアルポートアービトレータが両方インストールされている場合はどうでしょうか?標準のMac OS 8インストーラでOT/PPPとAppleリモートアクセス2.1を両方インストールするとこのような事態になります。答えは、旧シリアルポートアービトレータが優先されます。

註:
旧シリアルポートアービトレータやOpenTpt Serial Arbitratorはプロセスマネージャの有無を確認さずに、_Openのパッチ内からプロセスマネージャを呼び出していました。結果として、起動時に機能拡張がシリアルポートを利用しようとすると、システムがクラッシュしてしまいました。このバグはシリアルポートアービトレータの両バージョンにおいて直されています。


残る問題

Mac OSはまだシリアルポート極楽に到達していません。OpenTpt Serial Arbitratorにはいくつかの問題点が残っています。

  • シリアルポートの所有者はProcessSerialNumber(詳細についてはInside Macintosh: Processesを参照)を使って管理されますが、これは非アプリケーションのプログラムがシリアルポートを利用した場合に問題となります。例えば、SystemTaskをパッチして、パッチ内部からシリアルポートを数分利用したとします。シリアルポートを開いた時はアプリケーションAのコンテクスト内であっても、閉じた時はアプリケーションBのコンテクスト内かもしれません。これはシリアルポートアービトレータを混乱させます。

  • 旧シリアルポートアービトレータとOpenTpt Serial Arbitrator間は限られたやり取りしかされません。このため、Open Transportでシリアルポートを受身的に利用するアプリケーションは、旧API(OpenDriver ())でシリアルポートを利用するアプリケーションにシリアルポートを譲ることができません。

  • 上記の制限のため、Appleリモートアクセス3.0がシリアルポートを利用している間は、Z-Termなどの旧シリアルポートAPIを利用しているアプリケーションはシリアルポートを利用することができません。

  • Open Transportのシリアルポートアービトレータはバグのため、正しく動作しません。しかし、幸いAPIを呼び出すのは安全なので、Open Transportがいずれ直ることを見越して、APIを利用することを勧めます。

このテックノートは上記の問題について対応がされるとともに更新していく予定です。

要約

Mac OSのシリアルポートはうまくプログラム間で共有しなければなりません。シリアルポートの真の所有者はユーザですが、プログラム同士がシリアルポートをうまく利用しないとユーザは不満になります。このテックノートで述べられているルールに従えば、コンピュータの利用可能なシリアルポートを正しく認識し、他プログラムともうまく共存でき、世界中のMacintoshユーザに愛されることでしょう。

参考文献