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

Technote 1002

On Launching an App with a Document


目次

アプリケーションを起動してファイルを開く時の概念

まとまったサンプルコード

要約


のテックノートではアプリケーションを起動させて、そのアプリケーションを使って特定のファイルを開く方法について解説しています。また、開くファイルのタイプ情報に基づいて、起動するアプリケーションを選択する方法も提示しています。

他のアプリケーションを起動するようなソフトウェアを開発しているデベロッパは、このテックノートを参考にして、アプリケーションを起動するだけではなく、指定のファイルを開くことができます。

このテックノートはInside Macintosh: Interapplication Communication「Creating and Sending Apple Events」Inside Macintosh: More Macintosh Toolbox「Desktop Manager」の内容に基づいています。また、OpenDocuments Apple eventはInside Macintosh: Interapplication Communication「Handling the Required Apple Events」(4-11ページ)で解説されています。


アプリケーションを起動してファイルを開く時の概念

アプリケーションを起動してファイルを開くには以下の手順に従います。

  1. ファイルを開くためのアプリケーションを特定する。
  2. アプリケーションにOpenDocuments Apple eventを送って見る。
  3. アプリケーションがすでに起動していなかったら、アプリケーションを起動して、開くファイルの情報をパラメータとして渡す。

ファイルを開くためのアプリケーションを特定する

すべてのファイルはクリエータタイプとファイルタイプの属性を持っています。ファイルを開くためのアプリケーションを特定するには、まずファイルのクリエータタイプを参照します。ファイルのクリエータタイプは次のようにFSpGetInfo ()で得られます。

err = FSpGetInfo (&spec, &fndrInfo);


ファイルのクリエータタイプはfndrInfo.fdCreatorに返されます。

OpenDocuments Apple eventを送信する際のターゲットアプリケーションはtypeApplSignatureレコードを作成して、先程得たクリエータタイプを指定します。指定されたクリエータタイプに該当するアプリケーションが起動していない場合はAESend ()がエラーを返しますので、今度はクリエータタイプを元に、マウントされているボリュームからそのクリエータタイプを持ったアプリケーションを探し出します。

アプリケーションを指定してファイルを開く

用途によっては、クリエータタイプと異なるアプリケーションでファイルを開きたいことがあります。例えば、すべてのテキストファイルを無条件にClarisWorksで開く場合がそうです。このような時は自分のアプリケーション内にファイルタイプをクリエータタイプへマップする関数を設けて、FSpGetInfo ()の結果の代わりに使えば解決します。以下のサンプルコードはこのようなマッピング関数の一例です。

LaunchTheDocumentで利用できるマッピング関数です。

void MyMapCreator (FSSpec *document, OSType type, OSType *creator)
{
if (type == 'TEXT')
*creator = 'BOBO'; /* テキストファイルはすべてClarisWorks
(クリエータタイプ = 'BOBO')で開く */
}


上記のマッピング関数は3つのパラメータを受け付けます。FSSpecレコードは開くファイルを指します。FSSpecレコードは関数内部でファイル情報が必要になった場合に使用します。typeとcreatorはファイルタイプとクリエータタイプです。マッピング関数を使えば、アプリケーションを特定する際のクリエータタイプを予め変更することが可能です。上記の例ではファイルタイプが'TEXT'であればClarisWorksのクリエータタイプにマップされ、テキストファイルについてはLaunchTheDocumentがClarisWorksを呼び出します。このテックノートの最後には上記のコードを含めたサンプルコードをまとめています。

OpenDocuments Apple eventを送る

Inside Macintosh: Interapplication Communication(4-13ページ)に記述されているように、OpenDocuments Apple eventは1つの必須パラメータがあります。このパラメータは開くファイルのAliasレコードの配列です。以下にOpenDocuments Apple eventの送信手順を記載しています。

まず、ターゲットアプリケーションのアドレスレコード(target_desc)を作成します。アドレスレコードにはfndrInfo.fdCreatorで得たクリエータタイプを使います。

err = AECreateDesc (typeApplSignature, (Ptr) &fndrInfo.fdCreator,
sizeof (OSType), &target_desc);


Apple event(the_apple_event)を作成する際は、上記のアドレスレコードを指定します。

err = AECreateAppleEvent (kCoreEventClass, kAEOpenDocuments,
&target_desc, kAutoGenerateReturnID, kAnyTransactionID,
&the_apple_event);


次に、開くファイルのAliasレコードの配列を作成します。ファイルを1つしか開かないにも関わらず、OpenDocuments Apple eventには配列を渡す必要があります。この場合は1つのAliasレコードを含む配列になります。

まず、配列を新規作成します(files_list)。

err = AECreateList (NULL, 0, false, &files_list);


次にdescriptorレコード(file_desc)とファイル(the_file)のAliasを作成します。

err = NewAlias (NULL, &the_file, &the_alias);
HLock ((Handle) the_alias);
err = AECreateDesc (typeAlias, (Ptr) (*the_alias),
GetHandleSize ((Handle) the_alias), &file_desc);
HUnlock ((Handle) the_alias);


そして、Aliasレコードを含んだdescriptorレコード(file_desc)を配列(files_list)に追加します。

err = AEPutDesc (&files_list, 0, &file_desc);


これでApple event(the_apple_event)とファイルのAliasレコードを含んだ配列(files_list)ができましたので、配列をApple event(the_apple_event)のパラメータとして渡します。

err = AEPutParamDesc (&the_apple_event, keyDirectObject, &files_list);


OpenDocuments Apple eventはこれででき上がりです。あとはターゲットアプリケーションに送るだけです。

err = AESend (&the_apple_event, &the_reply, kAENoReply,
kAENormalPriority, kNoTimeOut, NULL, NULL);



アプリケーションが起動していない場合

ターゲットアプリケーションが起動していないと、AESend ()connectionInvalidを返します。これは指定されたクリエータタイプのアプリケーションがProcess Managerに登録されていないと言うことです。この場合はアプリケーションを起動する必要があります。アプリケーションを起動させるにはPBDTGetAPPL ()を使って、マウントされているボリュームからクリエータタイプに該当するアプリケーションを特定します。

アプリケーションを特定するには以下のようなコードを実行します。

HVolumeParam vol_pb;
DTPBRec desktop_pb;
FSSpec application;
vol_pb.ioNamePtr = NULL;
err = fnfErr; /* デフォルトの値 */
vol_pb.ioNamePtr = NULL;

/* マウントされているボリュームについて, 1, 2, ... */

for (vol_pb.ioVolIndex = 1;
PBHGetVInfoSync((HParmBlkPtr) &vol_pb) == noErr;
vol_pb.ioVolIndex++) {

  /* PBHGetVolParmsを使用してローカルボリュームであることを確認 */

  param_pb.ioVRefNum = vol_pb.ioVRefNum;
  if (PBHGetVolParmsSync((HParmBlkPtr)&param_pb) == noErr &&
  volinfo.vMServerAdr == 0) {

    /* PBDTGetPathでボリュームのデスクトップファイルを特定する */

    desktop_pb.ioCompletion = NULL;
    desktop_pb.ioVRefNum = vol_pb.ioVRefNum;
    desktop_pb.ioNamePtr = NULL;
    desktop_pb.ioIndex = 0;
    if (PBDTGetPath(&desktop_pb) == noErr) {
      desktop_pb.ioFileCreator = app_creator_bytes;
      desktop_pb.ioNamePtr = application.name;
      if (PBDTGetAPPLSync(&desktop_pb) == noErr) {
        application.vRefNum = vol_pb.ioVRefNum;
        application.parID = desktop_pb.ioAPPLParID;

        /* アプリケーションが特定できました */

        err = noErr;
        break;
      }
    }
  }
}


注:
上記のコードは比較的単純な検索方式です。イジェクトされているディスクを無視したり、特定のボリュームを優先するように改善をするのもいいかもしれません。このコードはローカルボリューム(PBHGetVolParms ()でローカルボリュームであることを確認)のみを検索します。このテックノートの最後にあるまとまったサンプルコードではオプションでローカルボリュームに続いて、ネットワークボリュームを検索します。



アプリケーションを起動する

アプリケーションを特定したら、AESend ()で使ったパラメータをLaunchApplication ()が受け付けるタイプに変換します。タイプを変換するには以下のようにAECoerceDesc ()を呼び出して、typeAppParametersを指定します。

err = AECoerceDesc(&the_apple_event, typeAppParameters, &parameter_desc);


タイプを変換したら、あとはLaunchApplication ()のパラメータブロックを埋めていきます。

launch_pb.launchBlockID = extendedBlock;
launch_pb.launchEPBLength = extendedBlockLen;
launch_pb.launchFileFlags = 0;
launch_pb.launchControlFlags =
  launchContinue + launchNoFileFlags;
launch_pb.launchAppSpec = &application;

/* launchAppParametersはポインタを受け付けるので、
dataHandleをロックして参照します */
HLock ((Handle) parameter_desc.dataHandle);
launch_pb.launchAppParameters =
  (AppParametersPtr) (*parameter_desc.dataHandle);
err = LaunchApplication(&launch_pb);
HUnlock ((Handle) parameter_desc.dataHandle);



ターゲットアプリケーションがApple eventに対応していない場合

ターゲットアプリケーションがApple eventに対応していない場合(System 7.x以前)、Apple Event Managerは自動的に旧式のランチパラメータを使ってアプリケーションを起動します。しかし、System 6とMultiFinderが利用していた'mstr''mst#'リソースはもうサポートされていないので、アプリケーションがすでに起動している場合はファイルを開くことができません。


まとまったサンプルコード

以下のサンプルコードはこのテックノートで紹介されたコードを1つにまとめた物です。

注:
作者は以下のコードをMPW C、MPW PPC、Symantec C、MrC、CodeWarrior Cでコンパイルテストを行いました。また、System 7.0以降においてバグが無いと確信しています。


/* File SendOpen.c

アプリケーションを起動してファイルを開くサンプルコード。

by John Montbriand, 1995.

Copyright (C) 1995 by Apple Computer, Inc. All Rights Reserved.*/

#include <Types.h>
#include <QuickDraw.h>
#include <Fonts.h>
#include <Windows.h>
#include <TextEdit.h>
#include <Dialogs.h>
#include <Menus.h>
#include <AppleEvents.h>
#include <StandardFile.h>
#include <Files.h>
#include <Errors.h>
#include <Aliases.h>

/* MapCreatorProcsではマッピング関数を指定できます。マッピング関数
は*creatorのクリエータタイプを変更することができます。*creatorを
変更しなかった場合は元のクリエータタイプがそのまま使用されます。 */

typedef void (*MapCreatorProc) (FSSpec *document, OSType type,
OSType *creator);

/* LaunchTheDocumentは指定のファイルを開こうとします。必要であれば
アプリケーションを起動します。必要なアプリケーションが見つからない
場合はfnfErrが返されます。また、ネットワーク上のボリュームの
アプリケーションを起動するかどうかはuse_remote_appsで指定します。
マッピング関数を指定することも可能です。 */

OSErr LaunchTheDocument (FSSpec *document, Boolean use_remote_apps,
MapCreatorProc remap) {
	OSErr err;
	AEAddressDesc target_desc; // ターゲットアプリケーション
	AEDescList files_list;
	AEDesc file_desc, parameter_desc;
	AppleEvent the_apple_event;
	AppleEvent the_reply;
	AliasHandle the_alias;
	FInfo fndrInfo;
	OSType app_creator_bytes;
	LaunchParamBlockRec launch_pb;

	/* レコードをすべて初期化する */

	AECreateDesc (typeNull, NULL, 0, &target_desc);
	AECreateDesc (typeNull, NULL, 0, &files_list);
	AECreateDesc (typeNull, NULL, 0, &file_desc);
	AECreateDesc (typeNull, NULL, 0, &the_apple_event);
	AECreateDesc (typeNull, NULL, 0, &the_reply);
	AECreateDesc (typeNull, NULL, 0, &parameter_desc);
	the_alias = NULL;

	/* ファイルのクリエータタイプを得る */

	err = FSpGetFInfo (document, &fndrInfo);
	if (err != noErr)
		goto launch_the_document_termination;

	/* 必要であればマッピング関数を呼び出す */

	app_creator_bytes = fndrInfo.fdCreator;
	if (remap != NULL)
		remap (document, fndrInfo.fdType, &app_creator_bytes);

	/* OpenDocuments Apple eventを新規作成する */

	err = AECreateDesc (typeApplSignature, (Ptr) &app_creator_bytes,
	      sizeof (OSType), &target_desc);
	if (err != noErr)
		goto launch_the_document_termination;
	err = AECreateAppleEvent (kCoreEventClass, kAEOpenDocuments,
	      &target_desc, kAutoGenerateReturnID,
	kAnyTransactionID, &the_apple_event);
	if (err != noErr)
		goto launch_the_document_termination;

	/* 開くファイルを配列に入れて作成します */

	err = AECreateList (NULL, 0, false, &files_list);
	if (err != noErr)
		goto launch_the_document_termination;
	err = NewAlias (NULL, document, &the_alias);
	if (err != noErr)
		goto launch_the_document_termination;
	HLock ((Handle) the_alias);
	err = AECreateDesc (typeAlias, (Ptr) (*the_alias),
	      GetHandleSize ((Handle) the_alias), &file_desc);
	HUnlock ((Handle) the_alias);
	if (err != noErr)
		goto launch_the_document_termination;
	err = AEPutDesc (&files_list, 0, &file_desc);
	if (err != noErr)
		goto launch_the_document_termination;

	/* OpenDocuments Apple eventに配列を追加する */

	err = AEPutParamDesc (&the_apple_event, keyDirectObject,
	      &files_list);
	if (err != noErr)
		goto launch_the_document_termination;

	/* Apple eventを送信する */

	err = AESend (&the_apple_event, &the_reply, kAENoReply,
	      kAENormalPriority, kNoTimeOut, NULL, NULL);


	/* アプリケーションが起動していない場合の処理 */

	if (err == connectionInvalid) {
		HVolumeParam vol_pb;
		DTPBRec desktop_pb;
		FSSpec application;
		GetVolParmsInfoBuffer volinfo;
		HIOParam param_pb;

		/* まずローカルボリュームから探します */

		param_pb.ioNamePtr = NULL;
		param_pb.ioBuffer = (Ptr) &volinfo;
		param_pb.ioReqCount = sizeof(volinfo);
		err = fnfErr; /* デフォルトの値 */
		vol_pb.ioNamePtr = NULL;
		for (vol_pb.ioVolIndex = 1;
		  PBHGetVInfoSync ((HParmBlkPtr) &vol_pb) == noErr;
		  vol_pb.ioVolIndex++) {
			param_pb.ioVRefNum = vol_pb.ioVRefNum;
			if (PBHGetVolParmsSync ((HParmBlkPtr)&param_pb) == noErr
			  && volinfo.vMServerAdr == 0) {
				desktop_pb.ioCompletion = NULL;
				desktop_pb.ioVRefNum = vol_pb.ioVRefNum;
				desktop_pb.ioNamePtr = NULL;
				desktop_pb.ioIndex = 0;
				if (PBDTGetPath (&desktop_pb) == noErr) {
					desktop_pb.ioFileCreator = app_creator_bytes;
					desktop_pb.ioNamePtr = application.name;
					if (PBDTGetAPPLSync (&desktop_pb) == noErr) {
						application.vRefNum = vol_pb.ioVRefNum;
						application.parID = desktop_pb.ioAPPLParID;
						err = noErr;
						break;
					}
				}
			}
		}

		/* 次にリモートボリュームを探します */

		if (err != noErr && use_remote_apps)
			for (vol_pb.ioVolIndex = 1;
			  PBHGetVInfoSync ((HParmBlkPtr) &vol_pb) == noErr;
			  vol_pb.ioVolIndex++) {
				param_pb.ioVRefNum = vol_pb.ioVRefNum;
				if (PBHGetVolParmsSync ((HParmBlkPtr) &param_pb) ==noErr
				  && volinfo.vMServerAdr != 0) {
					desktop_pb.ioCompletion = NULL;
					desktop_pb.ioVRefNum = vol_pb.ioVRefNum;
					desktop_pb.ioNamePtr = NULL;
					desktop_pb.ioIndex = 0;
					if (PBDTGetPath (&desktop_pb) == noErr) {
						desktop_pb.ioFileCreator = app_creator_bytes;
						desktop_pb.ioNamePtr = application.name;
						if (PBDTGetAPPLSync (&desktop_pb) == noErr) {
							application.vRefNum = vol_pb.ioVRefNum;
							application.parID = desktop_pb.ioAPPLParID;
							err = noErr;
							break;
						}
					}
				}
			}

		if (err != noErr)
			goto launch_the_document_termination;

		/* パラメータを変換します */

		err = AECoerceDesc (&the_apple_event, typeAppParameters,
		      &parameter_desc);
		if (err != noErr) goto launch_the_document_termination;

		/* アプリケーションを起動します */

		launch_pb.launchBlockID = extendedBlock;
		launch_pb.launchEPBLength = extendedBlockLen;
		launch_pb.launchFileFlags = 0;
		launch_pb.launchControlFlags =
			launchContinue + launchNoFileFlags;
		launch_pb.launchAppSpec = &application;
		HLock ((Handle) parameter_desc.dataHandle);
		launch_pb.launchAppParameters =
			(AppParametersPtr) (*parameter_desc.dataHandle);
		err = LaunchApplication (&launch_pb);
		HUnlock ((Handle) parameter_desc.dataHandle);
	}

launch_the_document_termination:

	/* 終了処理 */

	if (the_alias != NULL)
	    DisposeHandle ((Handle) the_alias);
	AEDisposeDesc (&parameter_desc);
	AEDisposeDesc (&target_desc);
	AEDisposeDesc (&file_desc);
	AEDisposeDesc (&files_list);
	AEDisposeDesc (&the_apple_event);
	AEDisposeDesc (&the_reply);
	return err;
}

/* サンプルマッピング関数。単純にテキストファイルをすべてClarisWorksで
開きます。 */

void MyMapCreator (FSSpec *document, OSType type, OSType *creator)
{
	if (type == 'TEXT')
	*creator = 'BOBO'; /* テキストファイルはすべてClarisWorks
	(クリエータタイプ = 'BOBO')で開く */
}

#ifdef powerc
QDGlobals qd;
#endif

void main (void)
{
	SFTypeList typeList;
	StandardFileReply reply;

	InitGraf (&qd.thePort);
	InitFonts ();
	InitWindows ();
	TEInit ();
	InitMenus ();
	InitDialogs (0);
	InitCursor ();
	StandardGetFile (NULL, -1, typeList, &reply);
	if (reply.sfGood)
		LaunchTheDocument (&reply.sfFile, false, MyMapCreator);
}


SendOpen.rは'SIZE'リソースのRezコードです。Apple eventを利用する場合は必ずisHighLevelEventAwareをセットして下さい。
resource 'SIZE' (-1, purgeable) {
	reserved,
	acceptSuspendResumeEvents,
	reserved,
	canBackground,
	multiFinderAware,
	backgroundAndForeground,
	dontGetFrontClicks,
	ignoreChildDiedEvents,
	is32BitCompatible,
	isHighLevelEventAware,
	localAndRemoteHLEvents,
	isStationeryAware,
	dontUseTextEditServices,
	reserved,
	reserved,
	reserved,
	524288,
	524288
};



要約

System 7でアプリケーションを起動してファイルを開くには直接Apple Event Managerを使ってOpenDocument Apple eventを送信するか、LaunchApplication ()を呼び出して、その一環として間接的にOpenDocument Apple eventを送信します。このテックノートはサンプルコードで上記のような機能を実現する方法を提示しました。

参考文献


更新日: 1997 年 11 月 19 日