このページはNetscapeNavigator 2.0以降及びMicrosoft Internet Exploler 3.0以降で正常に見られます。
Solly,Japanese only

★ BB研究所 ★
TWAIN

1.はじめに

TWAINはTWAIN Working Groupという団体が標準化した、主にスキャナをターゲットにしたソフトウェアインタフェースの規格です。
Windows2000からは、Microsoftが独自の新規格を提案していますが、 TWAINも引き続きサポートされるようです。
このTWAINは、個人的にはあまりきれいな規格とは思いません。
その為か、市販の画像ライブラリにはTWAINを簡単に あつかえる機能を持っているものが数多くあります。
また、初期のWindows95を除いて、Windows標準のツールとなっている、イメージングのOCXでもTWAINを扱えます。
簡単にスキャナーを使いたいのなら、これらを使うことをお勧めします。
しかし、細かな設定等を行うのならやはりTWAINを直接アクセスする必要があります。

TWAINアプリの開発に際しては、TWAIN Working Groupから 必要なツールキットが公開されています。このツールキット内にはサンプルソースも含まれているのですが、 すべての操作が行えるため、かなり複雑な構造をしています(そしてバグってます)。

そこで、今回は独自のサンプルを示しながらTWAINの導入を説明します。

TWAINは主にMAC用とWindows用が用意されていますが、ここではWindows(WIN32)を前提に話を進めます。
以下のものを、用意してください
ソースには、ヘッダファイルTWAIN.Hをインクルードして下さい。
また、ツールキットにはTWAIN.LIBは含まれていないため、LoadLibrary()を使用します。
※詳しくは7項を参照してください。
それでは、TWAINの説明をはじめます。

2.TWAINの構成

TWAINは、以下の3つの層で構成されています

アプリケーション
ソースマネージャ
ソース

 ここで言う「アプリケーション」はTWAINを操作するアプリケーションのことを指します。
要はこれから私たちが作ろうとしている部分です。
 「ソースマネージャ」は、アプリケーションとソースの橋渡しをする部分です。
 「ソース」は、アプリケーションからみて、スキャナ制御を行う部分になります。
アプリケーションは最終的にソースを通じてスキャナを制御したいのですが、
そのためには必ずソースマネージャを呼び出すことになります。
ソースマネージャとのインタフェースはDSM_Entry()という関数のみを使用します。
アプリケーションはDSM_Entry()にいろんな引数を指定して、ソースに命令を出します。


3.DSM_Entry()

アプリケーションがTWAINに発行する全てのコマンドは、DSM_Entry()を通じて行われます。
DSM_Entry()は以下のように定義されています。


TW_UNIT16 FAR PASCAL DSM_Entry
(	pTW_IDENTITY		pOrigin,	// source of message
  	pTW_IDENTITY		pDest,		// destination of message
 	TW_UNIT32		DG,		// data group ID: DG_xxx
	TW_UNIT16		DAT,		// data argument type: DAT_xxx
	TW_UNIT16		MSG,		// message ID: MESxxx
	TW_MEMREF		pData		// pointer to data 
);


pOrigin,pDestはTW_IDENTITY構造体へのポインタでそれぞれ命令の発行元と宛先を表す記述子のようなものです。
ここで発行元とは、アプリケーション、宛先はソースマネージャ又はソースになります。
DG/DAT/MSGはそれぞれデータグループ/データ引数タイプ/メッセージを表しています。
またこれらの値はマクロ定義されており、それぞれDG_/DAT_/MES_ではじまります。
TWAINコマンドはこの3つの値で決定するためTWAIN Specificationでは、 TWAINコマンドをDG_XXX/DAT_XXX/MES_XXXXという形で表現しています。

pDataは引数データへのポインタです。
TWAINのパラメータ設定である、DG_CONTROL/DAT_CAPABILITY/MSG_GET及び DG_CONTROL/DAT_CAPABILITY/MSG_SETでは、pDataのデータ型をいくつかのフォーマット(構造体)に標準化しており、 それをデータの入れ物という意味でコンテナと呼んでいます。
戻り値は、結果が格納されます。メッセージ(DG_/DAT_/MES_)の種類によって異なりますが、 主にTWRC_SUCCESS(成功),TWRC_CANCEL(キャンセル),TWRC_FAILURE(失敗)が戻されます。
更に詳細なエラーを取得するには、DG_CONTROL/DAT_STATUS/MSG_GETを使用します。


TW_UINT16 rc;
TW_STATUS twStatus;
char	  *strMsg;
	
rc = lpfnDSM_Entry(pAppId,pSourceId,DG_CONTROL,DAT_STATUS,MSG_GET,&twStatus);

switch (twStatus.ConditionCode){
	case TWCC_BADCAP:
        ・・・
		break;
	case TWCC_CAPUNSUPPORTED:
        ・・・
		break;
	case TWCC_CAPBADOPERATION:
        ・・・
		break;
	case TWCC_CAPSEQERROR:
        ・・・
		break;
	case TWCC_BADDEST:
        ・・・
		break;
	case TWCC_BADVALUE:
        ・・・
		break;
	case TWCC_SEQERROR:
        ・・・
		break;
	default:
		//Unknown Error
        ・・・
		break;
}


4.TWAINのデータ型

Twain.hでは、各データ型をTW_xxxという形式で表現しています。
殆どのデータ型はそのまま、C言語の型を置き換えたものですが、 実数型のTW_FIX32型はC言語のfloat型とは異なるので注意が必要です。

TW_FIX32型とfloat型の変換は以下の関数を使用します。


float Fix32ToFloat (TW_FIX32 fix32)
{
	float floater;
	floater = (float)fix32.Whole + (float)fix32.Frac / (float)65536.0;
	return floater;
}

TW_FIX32 FloatToFix32 (float floater)
{
	TW_FIX32 Fix32_value;
	TW_INT32 value = (TW_INT32) (floater * 65536.0 + 0.5);
	Fix32_value.Whole =(short)( value >> 16);
	Fix32_value.Frac =(short)( value & 0x0000ffffL);
	return (Fix32_value);
}



5.TWAINからのイベント処理


TWAINソースからの状態変化等の各種イベント通知されます。
この通知は、ウィンドウのイベントループで取得することが出来ます。
以下に、その部分を記述します。

【注】



TW_EVENT twEvent;
TW_INT16 rc;
while (GetMessage ( (LPMSG) &msg, NULL, 0, 0) ){
	// もしソースがイネーブルの場合以下を実行
	// ※実際にはif文等でイネーブル時だけ実行させるようにして下さい
	twEvent.pEvent = (TW_MEMREF)&msg;
	twEvent.TWMessage = MSG_NULL;
	rc = lpfnDSM_Entry(pAppId,pSourceId,
		DG_CONTROL,DAT_EVENT,MSG_PROCESSEVENT,
		(TW_MEMREF)&twEvent);
	// ソースからのメッセージ取得
	switch (twEvent.TWMessage){
		case MSG_XFERREADY:
			// TWAINがレディ(状態6)になった
			SetupAndTransferImage(NULL);
			break;
		case MSG_CLOSEDSREQ:
			// ユーザ指示でTWAINから
			// ソースのディセーブル&クローズ要求が来た
			DisableAndCloseSource(NULL);
			break;
		case MSG_NULL:
			// ソースからメッセージは来ていない
			break;
	}
	// 以下は通常の(TWAIN以外の)ウィンドウイベント
	if (rc == TWRC_NOTDSEVENT){
		TranslateMessage( (LPMSG) &msg);
		DispatchMessage( (LPMSG) &msg);
	}
}



6.TWAINの状態


TWAINには以下の7状態があります。
各状態で、できることが異なってきます

1.Pre-Session(作業前)ソースマネージャはロードされていない
2.Source Manager Loaded(ソースマネージャロード済み)アプリ:エントリポイントの取得
3.Source Manager Opened(ソースマネージャオープン済み)ユーザ:ソースのセレクト
4.Source Open(ソースオープン)能力の調整(ネゴ)
5.Source Enable(ソースイネーブル)ソース:ユーザインタフェースの提示
6.Transfer Ready ( 転送準備済み)アプリ:画像(オーディオ)情報の要求 
7.Transferring (転送中) データの転送

アプリケーションの起動時は状態1になっています。
TWAINからデータを取得するためには、状態1から最終的に状態7まで遷移させる必要があります。
上記の状態の遷移条件を以下に示します。

1→2アプリ:ソースマネージャのロード
1←2アプリ:ソースマネージャのアンロード
2→3アプリ:ソースマネージャのオープン
2←3アプリ:ソースマネージャのクローズ
3→4アプリ:ソースのオープン
3←4アプリ:ソースのクローズ
4→5アプリ:ソースをイネーブルにする
4←5ソース:転送ディセーブルをアプリに通知 or アプリ:ソースをディセーブルにする
5→6ソース:アプリケーションに転送準備が整ったことを通知
5←6ソース:転送保留分の画像がもう無いことをアプリに通知
6→7アプリ:転送をソースに指示
6←7アプリ:転送終了に応答


7.TWAINライブラリのロード(状態1→2)/アンロード(状態1←2)


アプリケーションからTWAIN(マネージャ&ソース)にコマンドを出すためには、TWAINライブラリのDSM_Entry()を使用します。
TWAINライブラリは、Win32(Windows95/98/NT/2000/Me等)では、Twain_32.dllを使用します。
通常DLLの使用にあたっては対応するヘッダファイルTwain.hと スタティックリンクライブラリ(.lib)が必要になります。
しかし、TWAINの場合、Twain.hはTWAINツールキットに含まれているものの、 .libが含まれていません。このため、動的にDLLをロードし、DSM_Entry()のポインタを取得する必要があります。

DLLをロード/アンロードはLoadLibrary()を使用します。
またDLL内からエントリー(関数)のポインタを取得するには、GetProcAddress()を使用します。
これらのWIN32 APIの使い方はVisualC++のオンラインヘルプ等で調べて下さい。

DLLのロード/アンロード例は「TWAIN Specification」に記述されていますが、 ちょっと煩雑なので、もう少し簡単にしたものを以下に示します。


DSMENTRYPROC	lpfnDSM_Entry;
HMODULE		hDSMDLL;
・・・

BOOL LoadDLL()
{
	char szDllFile[MAX_PATH];
	OFSTRUCT of;
	
	// DLLパスの作成
	GetWindowsDirectory (szDllFile, sizeof(szDllFile));
	if (szDllFile [(lstrlen (szDllFile) - 1)] != '\\'){
		lstrcat( szDllFile, "\\" );
	}
	lstrcat( szDllFile, "TWAIN_32.DLL" );

	// DLLファイルの確認
	if (OpenFile(szDllFile, &of, OF_EXIST) == -1)
		return FALSE;

	// DLLのロード
	if ( (hDSMDLL = LoadLibrary(szDllFile)) == NULL)
		return FALSE;

	// ポインタの取得
	if( (lpfnDSM_Entry = (DSMENTRYPROC)GetProcAddress(hDSMDLL,"DSM_Entry")) == NULL){
		UnLoadDLL();
		return FALSE;
	}
	return TRUE;
}

BOOL UnLoadDLL()
{
	if(hDSMDLL)
		return FreeLibrary(hDSMDLL);

	return TRUE;
}


上記の例では、ポインタlpfnDSM_Entryにエントリポインタを取得しました。
こうすることにより、lpfnDSM_Entry(...);という形式でDSM_Entry()を呼び出すことが出来るようになります。


8.ソースマネージャのオープン(状態2→3)/クローズ(状態2←3)


ソースマネージャのオープンは、DG_CONTROL/DAT_PARENT/MSG_OPENDSM命令で実施します。
このときの第1引数(pAppId)はTW_IDENTITY型構造体へのポインタで、アプリケーションの属性を記述します。
実際には以下のように初期化して、DSM_Entry()を呼び出します。


TW_UINT16 rc;		// 戻値

// アプリケーションIDの初期化
pAppId->Id = 0;		// 0にする
pAppId->Version.MajorNum = 0;	// アプリケーションのバージョンメジャー番号
pAppId->Version.MinorNum = 1;	// アプリケーションのバージョンマイナー番号
pAppId->Version.Language = TWLG_JAPANESE;// 言語
pAppId->Version.Country = TWCY_JAPAN;	// 国
lstrcpy (pAppId->Version.Info, "0.1");	// バージョン文字列
pAppId->ProtocolMajor = TWON_PROTOCOLMAJOR;
pAppId->ProtocolMinor = TWON_PROTOCOLMINOR;
pAppId->SupportedGroups = DG_IMAGE | DG_CONTROL;
lstrcpy (pAppId->Manufacturer, "BearBeetle");	//アプリのメーカー
lstrcpy (pAppId->ProductFamily, "BB LAB");	//アプリの製品ファミリー
lstrcpy (pAppId->ProductName, "BB TWAIN SAMPLE");//アプリの製品名

// ソースマネージャのオープン
rc=lpfnDSM_Entry(pAppId,NULL,DG_CONTROL,DAT_PARENT,MSG_OPENDSM,(TW_MEMREF)&hWnd);

if(rc != TWRC_SUCCESS)
	return FALSE;

return TRUE;


ソースマネージャのクローズはDG_CONTROL/DAT_PARENT/MSG_CLOSEDSM命令で実施します。
引数は、オープンとまったく同じです。


・・・
rc=lpfnDSM_Entry(pAppId,NULL,DG_CONTROL,DAT_PARENT,MSG_CLOSEDSM,(TW_MEMREF)&hWnd);

if(rc != TWRC_SUCCESS)
	return FALSE;

return TRUE;



9.ソースのオープン(状態3→4)/クローズ(状態3←4)


ソースマネージャのオープンは、DG_CONTROL/DAT_PARENT/MSG_OPENDS命令で実施します。
このときの第1引数(pAppId)は8項と同じものを使用します。
pDataには、TW_IDENTITY型構造体を以下のpSourceIdのように初期化します。


・・・
// データソースIDはシステムデフォルト値にする場合
pSourceId->ProductName[0]='\0';
pSourceId->Id=0;


または、DG_CONTROL / DAT_IDENTITY / MSG_USERSELECT命令を使用すると、 ソースを選択するダイアログボックスが表示され、ユーザがソースを選択できます。
このときの取得したpDataの値をpSourceIdにすることもできます。


rc=lpfnDSM_Entry(pAppId,NULL,DG_CONTROL,DAT_IDENTITY,MSG_OPENDS,pSourceId);
if(rc != TWRC_SUCCESS)
	return FALSE;

return TRUE;
 ・・・

ソースマネージャのクローズはDG_CONTROL/DAT_PARENT/MSG_CLOSEDS命令で実施します。
引数は、オープンとまったく同じです。


rc=lpfnDSM_Entry(pAppId,NULL,DG_CONTROL,DAT_IDENTITY,MSG_CLOSEDS,pSourceId);

if(rc != TWRC_SUCCESS)
	エラー処理


10.設定の取得と変更(状態4)


 画像取り込み時の各種設定の取得/変更は状態4(Source Open)で行います。
 これらの設定はDG_CONTROL/DAT_CAPABILITY/MSG_GET 又は DG_CONTROL/DAT_CAPABILITY/MSG_SETで行います。
 この時、コンテナにTW_CAPABILITY型の構造体を使用します。
 この構造体のCapフィールド取得/設定したい内容を指定します。
Capフィールドに指定する値はマクロ定義されておりICAP_ではじまります。
例えば、CapフィールドにICAP_PHYSICALWIDTHを指定すると、 スキャンできる幅を取得することが出来ます。

 DG_CONTROL/DAT_CAPABILITY/MSG_SETで値を取得するときは以下のようなルーチンになります。


TW_CAPABILITY	twCapability;
TW_UINT16	rc;
float		flPhysWidth;

twCapability.Cap=ICAP_PHYSICALWIDTH;	// 設定内容をICAP_XXXで指定する
twCapability.ConType = TWON_ONEVALUE;	// コンテナのタイプを指定する
twCapability.hContainer = NULL;		// NULLを指定する

rc=lpfnDSM_Entry(pAppId,pSourceId,DG_CONTROL,DAT_CAPABILITY,MSG_GET,(TW_MEMREF)&twCapability);

if(twCapability.hContainer){
	ptwOneValue = (pTW_ONEVALUE) GlobalLock((HANDLE)twCapability.hContainer);
	flPhysWidth = Fix32ToFloat((pTW_FIX32)&ptwOneValue->Item);
	GlobalUnlock((HANDLE)twCapability.hContainer);
	GlobalFree((HANDLE)twCapability.hContainer);
}

if(rc != TWRC_SUCCESS){
	エラー処理
}


ここで、注意が必要なのは、取得したコンテナのhContainerフィールドの指す領域に ConTypeフィールドで指定した型のデータがTWAIN内でGlobalAlloc()により確保/格納されていることです。
上記例ではTW_ONEVALUE型のデータがhContainerフィールドの指す領域に格納されています。
アプリケーションはGlobalLock()を使ってポインタを取得し、そのデータに参照します。
そして、参照しなくなったらGlobalUnlock()/GlobalFree()を使用して領域を解放しなければなりません。

逆にDG_CONTROL/DAT_CAPABILITY/MSG_SETで値を設定する場合は、アプリケーションは必要なデータ領域を GlobalAlloc()を使用して確保/設定し、このハンドルをhContainerフィールドに格納しなければなりません。
以下の例は、解像度の設定です。


TW_FIX32	tw;
float		fReso;

// fResoに解像度を設定する
fReso = xxx.0;

// コンテナ確保
twCapability.hContainer = GlobalAlloc(GHND,sizeof(TW_ONEVALUE));
ptwOneValue = (pTW_ONEVALUE)GlobalLock(twCapability.hContainer);

// OneValueの値を設定する
ptwOneValue->ItemType=TWTY_FIX32;	
tw=FloatToFix32(flReso);
memcpy((void*)&ptwOneValue->Item,(void *)&tw,sizeof(TW_FIX32));

// コンテナを設定する
twCapability.Cap=ICAP_YRESOLUTION;	// 設定内容をICAP_XXXで指定する
twCapability.ConType = TWON_ONEVALUE;	// コンテナのタイプを指定する

// コマンド
rc=lpfnDSM_Entry(pAppId,pSourceId,DG_CONTROL,DAT_CAPABILITY,MSG_SET,(TW_MEMREF)&twCapability);

// コンテナ開放
GlobalUnlock(twCapability.hContainer);
GlobalFree(twCapability.hContainer);



11.イネーブル(状態4→状態5→状態6)/ディセーブル(状態4←状態5)


 TWAINをイネーブルにするには、DG_CONTROL/DAT_USERINTERFACE/MSG_ENABLEDを使用します。


TW_UINT16 rc;				// 戻値
TW_USERINTERFACE ui;

ui.ShowUI=FALSE;
ui.ModalUI=FALSE;
ui.hParent=親ウィンドウ(ShowUI=TRUEの時は必須)

rc=lpfnDSM_Entry(pAppId,pSourceId,DG_CONTROL,DAT_USERINTERFACE,MSG_ENABLEDS,&ui);

if(rc != TWRC_SUCCESS){
	エラー処理
}

この時、ui.ShowUI及びui.ModalUIをTRUEにすると、 エンドユーザから直接各種設定及び転送まで実行できるダイアログボックスが表示されます。
このダイアログボックスはソースが作成します。
イネーブルを発行して状態5(Source Enable)にすると、ソースは転送準備にかかる。
準備が整うとソースはイベント(MSG_XFERREADY)を発生させ状態6(Transfer Ready)に移ります。

ディセーブルにするには、DG_CONTROL/AT_USERINTERFACE/MSG_DISABLEDSを使用します


TW_UINT16 rc;				// 戻値
TW_USERINTERFACE ui;

ui.ShowUI=FALSE;
ui.ModalUI=FALSE;
ui.hParent=親ウィンドウ(要らないかもしれない)

rc=lpfnDSM_Entry(pAppId,pSourceId,DG_CONTROL,DAT_USERINTERFACE,MSG_DISABLEDS,&ui);

if(rc != TWRC_SUCCESS){
	エラー処理
}



12.データ転送(状態5←→状態6)


ソースからイベント(MSG_XFERREADY)を受け取るとデータ転送を行うことが出来ます。
(イベントの取り方は5項を参照して下さい)
データ転送は、DG_IMAGE/DAT_IMAGENATIVEXFER/MSG_GETを使用します。


TW_UINT16 rc;
TW_UINT32 hBitmap;
TW_BOOL PendingXfers = TRUE;
LPBITMAPINFOHEADER	lpDib;


// 画像転送
hBitmap = NULL;
rc = lpfnDSM_Entry(pAppId,pSourceId,DG_IMAGE,DAT_IMAGENATIVEXFER,MSG_GET,(TW_MEMREF)&hBitmap);

lpDib = (LPBITMAPINFOHEADER)GlobalLock((void*)hBitmap);

// 戻値のチェック
switch(rc){
	case TWRC_XFERDONE:
		if(lpDib!=NULL){
			// DIBオブジェクトを取得できました
			・・・・
		}
		break;
		case TWRC_CANCEL:
		// ユーザによってキャンセルされました
		・・・
		break;

	case TWRC_FAILURE:
		// 転送中にエラーが発生しました
		・・・
		break;
}
if(lpdib!=NULL){
	GlobalUnlock((void*)hBitmap);
	GlobalFree((void*)hBitmap);
}


ここで取得したlpDibがビットマップオブジェクトになるので、ディスプレイに表示するなり、 ファイルに保存するなりすることが出来ます。
スキャナが、オートフィーダを持っている場合、転送ペンディング分が 通知されるようですが、私自身ここまで調べていないのでよく分かりません。
もし必要でしたら、各自で調べてみて下さい。
尚、転送終了は、DG_CONTROL/DAT_PENDINGXFERS/MSG_ENDXFER, ペンディングリセットはDG_CONTROL/DAT_PENDINGXFERS/MSG_RESETを使用します。


13.おわりに


今回は、サンプルソフトを添付しませんでしたが、その分コード例を増やしたので参考にできると思います。 TWAINにはいろいろなモードやパラメータがあります。
ここでは、基本的な流れしか触れていませんが、TWAIN Specification を参照するための予備知識的はつける ことができたと思います。
これを足がかりに、皆さんのアプリケーションでTWAINをサポートしていきましょう!

[ホームへ戻る]
【改訂記録】
2000/06/21版:公開
2001/11/02版:誤記修正


Copyright (c) 1997-2001, BearBeetle, Allrights reserved.