2017年8月2日水曜日

Remote Desktop と Default Printer をめぐるバグ

世相を反映して、近年 Remote Desktop でアプリケーションを動作させる機会が増えました。
Remote Desktop App として、アプリケーションを実行すると、印刷ダイアログにて、デフォルトプリンタが取得できない。他のアプリでは出来ているから直せと言われたのが発端でした。

 デフォルトプリンタの取得について調べてみました。自分のアプリケーションでは WTL(Windows Template Library)を利用しています。デフォルトプリンタの取得には、GetProfileString という関数が使用されていました。

 このGetProfileStringを使ってデフォルトプリンタを取得する方法、一昔前においてはデファクトスタンダードでしたが、現在では GetDefaultPrinter というWindows API が提供されており非推奨です。特に Remote Desktop 環境で GetProfileString を使用すると正しくデフォルトプリンタを取得できません。

 デフォルトプリンタの取得方法には、EnumPrinters という Windows API を利用する方法がありました。この記事を書いた時点では、MSDN日本語訳 と MSDN英語 のドキュメントにも相違があり混沌としています。新しいドキュメントからは PRINTER_ENUM_DEFAULT というフラグの記述が消えており、GetDefaultPrinter API を使用しろとあります。

 以上から、WTL の OpenDefaultPrinter 関数を GetDefaultPrinter API を使って書き直しました。

 しかし、Remote Desktop App として、アプリケーションを実行すると、依然としてローカルのデフォルトプリンタが取得できない現象が発生しました。奇妙な事に、セッションを維持する設定にすると、セッションが保たれている状況では正しくローカルのデフォルトプリンタが取得できるのです。

 このバグが発生するシナリオは、以下のようなものでした。

   自分が書いた アプリケーション(COM Control) では、インスタンス生成時に、ページ設定ダイアログのインスタンスを生成しています。このダイアログ生成時に、デフォルトプリンタの取得も行われていました。

  1. Remote App 開始
  2. Remote Destop Session 開始
  3. ページ設定ダイアログ生成
  4. デフォルトプリンタ取得(call GetDefaultPrinter)
  5. この時点では Remote Desktop Session 内にローカルプリンタの情報が揃っていません。
  6. Remote Server 内のデフォルトプリンタが返される。
  7. Remote Desktop Session がローカルプリンタの情報を取得
ですので、Remote Desktop Session が既に接続済みな状態で Remote App を開始すると、GetDefaultPrinter はローカルプリンタのデフォルトプリンタを返すのです。

 対処法としては、自分が書いたアプリケーションにおいて、ページ設定ダイアログの生成を使われる直前まで先延ばしする事でした。

疑似コードは、以下のような感じです。
  CCustomPageSetupDialog* getPrintDialog() {
    if( NULL == print_dialog_ ) {
       print_dialog_ = new CCustomPageSetupDialog( this, ... );
    }
    return print_dialog_;
  }