Delphi7 リモートデスクトップ環境でのデフォルトプリンターの取得に関する問題
今日はニッチな話。
過去記事の FreeRDP によるシンクライアントの運用を行う中で、以下の謎の現象に悩まされていた。
- リモートデスクトップ環境の中で Delphi アプリケーション(Delphi7 でビルド)からデフォルトプリンターに印刷すると、デフォルトではないプリンターに印刷されてしまう。
- この時、Excel 等のアプリケーションで印刷すると、問題なくデフォルトプリンターから印刷される
- この現象は必ず起こるわけではないが、一度発生すると何度印刷しても同じ
色々調べたところ、以下のことがわかった。
Delphi で、デフォルトプリンターを得るコード(Printers.pas: SetToDefaultPrinter()の中にある)は以下の通り。
- EnumPrinters() の第1引数に PRINTER_ENUM_DEFAULT を渡してデフォルトプリンターを得る
- これに失敗した場合は、GetProfileString() でデフォルトプリンターを得る
どの Windows のバージョンからかはわからないが、少なくとも Windows 2012 Server では EnumPrinters() でデフォルトプリンターを得ることはできなくなっているようだ(EnumPrinters function (Windows) には PRINTER_ENUM_DEFAULT 引数について触れられていない)
- そのため、GetProfileString() でデフォルトプリンターを得ることになるが、これは非常に古い API であるため、おそらくリモートデスクトップのようなマルチユーザー環境のことは考えられていないと思われる
- つまり、GetProfileString() では、後からリモートデスクトップ環境にログインしたユーザーのデフォルトプリンター名が返されてしまう
解決方法
Windows 2000 以降では、GetDefaultPrinter() という API が用意されているのでこれを利用するように Printers.pas を改変する。
interface 部に以下を追加
function GetDefaultPrinterA(pszBuffer: PChar; var pcchBuffer: DWORD): BOOL; stdcall; external 'winspool.drv' name 'GetDefaultPrinterA';
SetToDefaultPrinter() に以下の変数を追加
LBuffer: String;
LSize: DWORD;
同じく
EnumPrinters(PRINTER_ENUM_DEFAULT, nil, 5, PrinterInfo, ByteCnt, ByteCnt, StructCnt); if StructCnt > 0 then Device := PrinterInfo.pPrinterName else begin GetProfileString('windows', 'device', '', DefaultPrinter, SizeOf(DefaultPrinter) - 1); Cur := DefaultPrinter; Device := FetchStr(Cur); end;
をコメントアウトし、代わりに以下を追加。
SetLength(LBuffer, 255); LSize := Length(LBuffer); GetDefaultPrinterA(PChar(LBuffer), LSize); Device := PAnsiChar(LBuffer);
とりあえずこれで正しく動作するようになった。Windows95/98 をサポートするには少し変更する必要があるが、さすがにもう良いような気がする...。