EternalWindows
RAS / ダイヤル

エントリや認証情報、プロパティの取得などを見てきたとこところで、 いよいよ実際にダイヤルする関数について見ていきたいと思います。 次に示すRasDialは、指定された情報を基にダイヤルを開始ます。

DWORD RasDial(
  LPRASDIALEXTENSIONS lpRasDialExtensions,
  LPCTSTR lpszPhonebook,
  LPRASDIALPARAMS lpRasDialParams,
  DWORD dwNotifierType,
  LPVOID lpvNotifier,
  LPHRASCONN lphRasConn
);

lpRasDialExtensionsは、関数の機能を拡張するためのRASDIALEXTENSIONS構造体のアドレスですが、 NULLを指定しても問題ありません。 lpszPhonebookは、電話帳ファイルのフルパスを指定します。 lpRasDialParamsは、ダイヤルに必要な情報で初期化されている RASDIALPARAMS構造体のアドレスを指定します。 dwNotifierTypeは、lpvNotifierに何を指定するのかを決定します。 0を指定した場合、lpvNotifierにはRasDialFuncというコールバック関数のアドレスを 指定することができ、1を指定した場合はRasDialFunc1という先とは形式の違う コールバック関数を指定することができます。 lpvNotifierは、RasDial関数を非同期に実行するための仕組みで、 NULLを指定した場合は関数は同期的に実行されます。 つまり、RasDial関数がダイヤルを終えた後に制御を返します。 一方、NULLでない値を指定した場合は関数は非同期で実行されます。 たとえば、dwNotifierTypeに0を指定しているならば、 システムはlpvNotifierにRasDialFunc型のコールバック関数が指定されているものとみなし、 必要に応じてそれを呼び出そうとします。 lphRasConnは、NULLで初期化されているHRASCONN型変数のアドレスを指定します。 関数が成功した場合、この変数はRAS接続を表すハンドルとして働きます。

RasDialが受け取るRASDIALPARAMS構造体は、 ダイヤルに必要な認証情報を表すメンバを持っています。 これは、前節で示したRasGetCredentialsなどを呼び出したり、 メンバに直接初期化したりすることもできるのですが、 RasGetEntryDialParamsという初期化に特化した関数を呼び出すのが最も効率的だといえます。

DWORD RasGetEntryDialParams(
  LPCTSTR lpszPhonebook,
  LPRASDIALPARAMS lprasdialparams,
  LPBOOL lpfPassword
);

lpszPhonebookは、電話帳ファイルのフルパスを指定します。 lprasdialparamsは、RASDIALPARAMS構造体のアドレスを指定します。 どのエントリのダイヤル情報を取得するのかを明示すべく、 szEntryNameメンバをエントリ名で初期化しておくことになります。 また、dwSizeメンバは構造体のサイズで初期化しておきます。 lpfPasswordは、パスワードを取得できたかを受け取る変数のアドレスを指定します。 この値がTRUEである場合、szPasswordメンバにパスワードが格納されているということなのですが、 あくまでパスワードを参照するためのハンドル値であったことを忘れてはいけません。 なお、szPhoneNumberメンバは初期化されることはないため、 この値を得るためにRasGetEntryDialParamsを呼び出してはいけません。

RasDialで確立したRAS接続は、不要になったときにRasHangUpで切断することになります。

DWORD RasHangUp(
  HRASCONN hrasconn
);

hrasconnは、RAS接続のハンドルを指定します。 多くの場合は、アプリケーションが利用していたハンドルを指定しますが、 RasEnumConnectionsで取得した他のアプリケーションによって作成された ハンドルを指定することもできます。

今回のプログラムは、RasDialによってダイヤルアップ接続を行います。 また、状況通知用のコールバック関数を作成することで、 ダイヤル状況をリストボックスに追加しています。

#include <windows.h>
#include <ras.h>

#pragma comment (lib, "rasapi32.lib")

HWND g_hwndListBox = NULL;

void WINAPI RasDialFunc(UINT unMsg, RASCONNSTATE rasconnstate, DWORD dwError);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HRASCONN hrasconn = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		BOOL          bPassword;
		RASDIALPARAMS dialParams = {0};

		if (MessageBox(NULL, TEXT("ダイヤルを開始してもよろしいですか。"), TEXT("ダイヤルアップ接続"), MB_YESNO) == IDNO)
			return -1;

		g_hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);

		dialParams.dwSize = sizeof(RASDIALPARAMS);
		lstrcpy(dialParams.szEntryName, TEXT("EntryName"));	
		RasGetEntryDialParams(NULL, &dialParams, &bPassword);

		RasDial(NULL, NULL, &dialParams, 0, RasDialFunc, &hrasconn);
		
		return 0;
	}
	
	case WM_SIZE:
		MoveWindow(g_hwndListBox, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		if (hrasconn != NULL) {
			RasHangUp(hrasconn);
			Sleep(3000);
		}
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

void WINAPI RasDialFunc(UINT unMsg, RASCONNSTATE rasconnstate, DWORD dwError)
{
	if (dwError != 0) {
		TCHAR szBuf[256];
		RasGetErrorString(dwError, szBuf, sizeof(szBuf));
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		return;
	}

	switch (rasconnstate) {
	case RASCS_OpenPort:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("ダイヤルしています。"));
		break;
	case RASCS_PortOpened:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("ポートをオープンしました。"));
		break;
	case RASCS_ConnectDevice:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("デバイスに接続しています。"));
		break;
	case RASCS_DeviceConnected:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("デバイスに接続しました。"));
		break;
	case RASCS_AllDevicesConnected:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("すべてのデバイスに接続しました。"));
		break;
	case RASCS_Authenticate:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("認証を開始しました。"));
		break;
	case RASCS_AuthNotify:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("認証中です。"));
		break;
	case RASCS_AuthRetry:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("認証されました。"));
		break;
	case RASCS_AuthCallback:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("コールバック認証中です。"));
		break;
	case RASCS_AuthChangePassword:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("パスワードを変更中です。"));
		break;
	case RASCS_AuthProject:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("プロジェクションフェーズを開始しました。"));
		break;
	case RASCS_AuthLinkSpeed:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("リンク速度を計算中です。"));
		break;
	case RASCS_AuthAck:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("認証を受け付けました。"));
		break;
	case RASCS_ReAuthenticate:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("再認証中です。"));
		break;
	case RASCS_Authenticated:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("認証を完了しました。"));
		break;
	case RASCS_PrepareForCallback:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("コールバックの準備中です。"));
		break;
	case RASCS_WaitForModemReset:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("モデムのリセットを待っています。"));
		break;
	case RASCS_WaitForCallback:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("コールバックを待っています。"));
		break;
	case RASCS_Interactive:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("対話中です。"));
		break;
	case RASCS_RetryAuthentication:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("認証のリトライ中です。"));
		break;
	case RASCS_CallbackSetByCaller: 
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("呼び出し元によってコールバックが設定されました。"));
		break;
	case RASCS_PasswordExpired:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("パスワードが破棄されました。"));
		break;
	case RASCS_Connected:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("接続しました。"));
		break;
	case RASCS_Disconnected:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("切断しました。"));
		break;
	default:
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("不明なエラーが発生しました。"));
		break;
	}
}

プログラムを実行するとダイヤルするかどうかの確認が行われます。 これは、ステレスダイヤルと呼ばれるような意図しないダイヤルを防ぐためで、 キャンセルをした場合は、何もせずアプリケーションは終了します。 次のコードは、RasDialの呼び出しに関わる部分です。

dialParams.dwSize = sizeof(RASDIALPARAMS);
lstrcpy(dialParams.szEntryName, TEXT("EntryName"));	
RasGetEntryDialParams(NULL, &dialParams, &bPassword);

RasDial(NULL, NULL, &dialParams, 0, RasDialFunc, &hrasconn);

RasDialを呼び出すには、まずRASDIALPARAMS構造体を初期化しなくてはなりません。 dwSizeメンバに構造体のサイズを代入し、szEntryNameに実在するエントリ名を指定すれば、 RasGetEntryDialParamsで認証情報に関わるメンバが初期化されるため、 後はそれをRasDialに指定すればよいことになります。 RasDialの第6引数で返される接続ハンドルはダイヤルを終了するまで保持し、 不要になったときはRasHangUpで確実に切断します。

if (hrasconn != NULL) {
	RasHangUp(hrasconn);
	Sleep(3000);
}

このプログラムは終了時まで接続を確立するようにしているため、 RasHangUpの呼び出しはWM_DESTROYとしています。 注意しなければならないのはRasHangUpの呼び出し後にSleep(3000);としているところで、 これはシステム内部のデータとの兼ね合いから必要な処理のようです。 詳しい理由は分かりませんが、リファレンス通り記述しておくことにします。

RasDialの第5引数でコールバック関数のアドレスを指定し、 さらに第4引数の値が0になっていることから、 接続状況に応じてRasDialFunc型の関数が呼ばれることが分かります。 現在の状態はRASCONNSTATE型の変数で表され、それはRASCSで始まる定数で初期化されているため、 定数の名前を基にリストボックスに文字列を追加するようにしています。 この他、コールバック関数にはRasDialFunc1やRasDialFunc2といった型の関数も指定可能で、 それぞれRasDialの第4引数の値は1と2となります。 これらの関数では、どちらも引数としてRAS接続のハンドルが送られ、 特にRasDialFunc2ではコールバックIDと称してアプリケーション定義値が送られるため、 利用する際にはRASDIALPARAMS構造体のdwCallbackIdメンバを初期化しておくとよいでしょう。

オートダイヤルについて

ユーザーのダイヤルアップ接続の便宜を図るために、 システムにはオートダイヤルと呼ばれる機能が組み込まれています。 この機能は、インターネットへの接続が必要になったときに 自動でダイアログを表示するというもので、そこでエントリ名を決定して接続ボタンを押せば、 ダイヤルが行われるという仕組みになっています。 オートダイヤルの有効無効は、以下のレジストリキーから参照できます。

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings

上記キーには、EnableAutodialとNoNetAutodialというエントリが含まれています。 これらのエントリはIEのインターネットオプションの接続タブから変更できるため、 それらをチェックした結果と併せて値の意味を確認したいと思います。

ボタン EnableAutodial NoNetAutodial
ダイヤルしない 0 1
ネットワーク接続が存在しないときは、ダイヤルする 1 1
通常の接続でダイヤルする 1 0

EnableAutodialが0である場合、オートダイヤルの機能が働くことはありません。 それ以外の場合は、インターネット接続がオンラインでない限り、 オートダイヤルのダイアログが表示される可能性があります。 よくインターネットへの接続方法が示された資料などでは、 常時接続の際にはEnableAutodialが0になるようチェックを入れ、 ダイヤルアップ接続でもEnableAutodialが0になるよう明示されている場合がありますが、 後者の方にもきちんとした意図はあります。 NTT提供のエントリなどは専用のPPPoEツールを利用しないとインターネットに接続できないため、 オートダイヤルの機能を有効にしても意味がないためです。

オートダイヤルのダイアログは、主としてWinINetのInternetAttemptConnectや InternetAutodialなどで表示されますが、Winsockのgethostbynameとconnnect(DNS名指定)でも 次のレジストリキーのエントリ次第で呼び出されることがあります。

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Winsock\Autodial

デフォルトではAutodialキーは存在しないので、実際には明示的にキーを作成することになります。 このキーにAutodialDllName32というエントリを作成してその値をwininet.dllにし、 さらにAutodialFcnName32というエントリを作成して値をInternetAutodialCallbackにした場合、 オートダイヤルのダイアログが表示されることになるといわれています。



戻る