Microsoft Print to PDF でコードからファイル名指定

Windows 10 標準装備の仮想プリンタ Microsoft Print to PDF ですが、作成されるPDFファイル名をアプリケーションのコードから指定できないか探していたところ、以下を見つけました。

How to programmatically print to PDF file without prompting for filename in C# using the Microsoft Print To PDF printer that comes with Windows 10

https://stackoverflow.com/questions/31896952/how-to-programmatically-print-to-pdf-file-without-prompting-for-filename-in-c-sh

C++ の場合は、Windows API 関数 CreateDC の第3パラメータにデフォルトの "PORTPROMPT:" ではなくファイル名を指定すればいいです。

    HANDLE hDC = CreateDC("winspool", "Microsoft Print to PDF", "C:\\HOGE\\HOGEHOGE.PDF");

OWLNext の場合は、TPrinter インスタンスメンバ関数 GetData を呼び出して取得する TPrintDialog::TData クラスインスタンスに SetDevNames で設定するときの第3パラメータが相当します。OWLNext ではそのデータを元に TCreatedDC::TCreatedDC にて、Windows API 関数 CreateDC を呼び出すようになっています。

    TPrinter printer;
    printer.GetData()->SetDevNames("winspool", "Microsoft Print to PDF", "C:\\HOGE\\HOGEHOGE.PDF");

自前のインサートイテレータ

BCC582 (C++Builder 2006) では問題なかった下記のような自前のインサートイテレータがあります。

template <class X>
class MyInsertIterator {
public:
    MyInsertIterator(X& x) : x_(&x) {};
    MyInsertIterator& operator =  (const Foo& v) { x_->Add(v); return *this; }
    MyInsertIterator& operator *  ()    { return *this; }
    MyInsertIterator& operator ++ ()    { return *this; }
    MyInsertIterator& operator ++ (int) { return *this; }
protected:
    X* x_;
};

template <class X>
inline MyInsertIterator<X> MyInserter(X& x)
{
    return MyInsertIterator<X>(x);
}

これを std::copy で使っているのですが、VC141 (Visual Studio 2017 の Visual C++) でデバッグ用ビルドすると warning C4996 と error C2794 が報告されます。

まず error C2794 対策、

エラーメッセージは

'iterator_category': 'std::iterator_traits<_Iter>' の基底クラスの直接的または間接的なメンバーではありません。

ですが、イテレータの特性が取得できないみたいです。そこで MyInsertIterator クラス定義内に以下を追加します。

public:
    using iterator_category = std::output_iterator_tag;
    using difference_type = void;
    using value_type = void;
    using pointer = void;
    using reference = void;
    using container_type = X;

つづいて warning C4996 対策、

警告メッセージは

'std::copy::_Unchecked_iterators::_Deprecate': Call to 'std::copy' with parameters that may be unsafe - this call relies on the caller to check that the passed values are correct. To disable this warning, use -D_SCL_SECURE_NO_WARNINGS. See documentation on how to use Visual C++ 'Checked Iterators'

ということで、マクロ _SCL_SECURE_NO_WARNINGS を定義するとか、pragma で warning C4996 を無効にしてしまう方法もありますが、他への影響を最小限にするため、MyInsertIterator クラス定義の後に以下を追加してイテレータを checked の扱いにしてしまいます。

template<class _Container>
struct std::_Is_checked_helper<MyInsertIterator<_Container> > : public std::true_type {};

最後に、全体のコード例を載せて起きます。

長くなりそうだったので X::Add(Foo) の代わりに X::push_back(int) になってます。

#include <iostream>
#include <algorithm>
#include <iterator>
#include <vector>

using namespace std;

template <class X>
class MyInsertIterator {
public:
    using iterator_category = output_iterator_tag;
    using difference_type = void;
    using value_type = void;
    using pointer = void;
    using reference = void;
    using container_type = X;
    MyInsertIterator(X& x) : x_(&x) {};
    MyInsertIterator& operator =  (const typename X::value_type& v) { x_->push_back(v); return *this; }
    MyInsertIterator& operator *  ()    { return *this; }
    MyInsertIterator& operator ++ ()    { return *this; }
    MyInsertIterator& operator ++ (int) { return *this; }
protected:
    X* x_;
};

template <class X>
struct _Is_checked_helper<MyInsertIterator<X> > : public true_type {};

template <class X>
inline MyInsertIterator<X> MyInserter(X& x)
{
    return MyInsertIterator<X>(x);
}

int main()
{
    vector<int> v = { 2, 3, 5, 7, 11, 13 };
    vector<int> w;
    copy(v.begin(), v.end(), MyInserter(w));
    cout << "w:";
    for (auto& i : v) {
        cout << ' ' << i;
    }
    cout << endl;
}

cmake で bcc582 と vc141 一気にビルド

1本の CMakeLists.txt と数本の短いバッチフィルで、2つのコンパイラ bcc582 と vc141 をそれぞれ使って、さらにそれぞれの debug バージョンと release バージョンを一気にビルドするためのメモです。

今後付け足していく予定です。

ディレクトリ構造

. ; ソース, CMakeLists.txt, バッチファイル, 改名したEXEファイルをコピーしてくる
|
+-- build ; ビルド ディレクトリ
     |
     +-- bcc582d ; bcc582 debug バージョン用 ビルド ディレクトリ
     |
     +-- bcc582 ; bcc582 release バージョン用 ビルド ディレクトリ
     |
     +-- vc141 ; vc141用 ビルド ディレクトリ

環境変数

コンパイラのインストールディレクトリを設定しておく

BCC582=C:\PROGRA~1\Borland\BDS\4.0
VC141=C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build

バッチファイル

build.bat

@echo off
cmd /c build_bcc582.bat
cmd /c build_vc141.bat
cp --update --no-target-directory build/bcc582d/foo.exe       ./foo_bcc582d.exe
cp --update --no-target-directory build/bcc582/foo.exe        ./foo_bcc582.exe
cp --update --no-target-directory build/vc141/release/foo.exe ./foo_vc141.exe
cp --update --no-target-directory build/vc141/debug/foo.exe   ./foo_vc141d.exe

build_bcc582.bat

@echo off
PATH=%BCC582%\Bin;%PATH%
mkdir build\bcc582d
cd build\bcc582d
cmake -DCMAKE_BUILD_TYPE=Debug -G "Borland Makefiles" ../..
make
cd ..\..
mkdir build\bcc582
cd build\bcc582
cmake -DCMAKE_BUILD_TYPE=Release -G "Borland Makefiles" ../..
make -DNDEBUG
cd ..\..

build_vc141.bat

@echo off
call "%VC141%\vcvarsall.bat" x86
mkdir build\vc141
cd build\vc141
cmake ../..
for %%a in (*.vcxproj) do msbuild %%a /m /p:Configuration=Debug
for %%a in (*.vcxproj) do msbuild %%a /m /p:Configuration=Release
cd ..\..

CMakeLists.txt

cmake_minimum_required(VERSION 3.11)
project(foo CXX)
add_executable(foo main.cpp)

main.cpp テスト用サンプル

#include <iostream>
int main()
{
    using namespace std;
#ifdef NDEBUG
    cout << "Release(NDEBUG defined)" << endl;
#else
    cout << "DEBUG(NDEBUG not defined)" << endl;
#endif
#ifdef __BORLANDC__
    cout << "BORLANDC: " << hex << __BORLANDC__ << endl;
#endif    
#ifdef _MSC_VER
    cout << "MSC: " << _MSC_VER << endl;
#endif    
    return 0;
}

Windows で ionice

Windows で、ビルド(特にリンク)とかディスクアクセスが多い処理をさせている最中、他の作業をやろうとしてもディスクアクセスが遅くてイライラすることがありませんか?

CPUの優先度を下げるのは start /low や cygwin の nice を使えばできますが、IOの優先度を下げるコマンドは無いみたいです。

Windows Vista から Windows API 関数 SetPriorityClass の2つめの引数にPROCESS_MODE_BACKGROUND_BEGIN, PROCESS_MODE_BACKGROUND_END が追加されています。

以下の挟まれた部分のコードが、低いIO優先度で実行されます。

SetPriorityClass(GetCurrentProcess(), PROCESS_MODE_BACKGROUND_BEGIN);
// ... 低いIO優先度で実行される
SetPriorityClass(GetCurrentProcess(), PROCESS_MODE_BACKGROUND_END);

これを利用して、I/O優先度を下げてコマンドを実行するコマンドを作ってみました。以下をコンパイルして ionice.exe を作ります。

linux の同じ名前のコマンドのようなオプション引数は対応してません。

一応 VC141 (Visual Studio 2017 の Visual C++) と BCC582 (C++Builder 2006) で確認してあります。

#include <windows.h>
#include <process.h>
#include <iostream>

// 定義がなかったら定義する
#ifndef PROCESS_MODE_BACKGROUND_BEGIN
#define PROCESS_MODE_BACKGROUND_BEGIN 0x00100000
#endif

#ifndef PROCESS_MODE_BACKGROUND_END
#define PROCESS_MODE_BACKGROUND_END 0x00200000
#endif

// _spanvp は BCCだと spanvp 引数・戻り値は同じ
#ifdef __BORLANDC__
#define _spawnvp spawnvp
#endif

int main(int argc, char** argv)
{
    using namespace std;
    if (argc < 2) {             // 引数無し
        cout << "書式: ionice <コマンドライン...>" << endl;
        return 0;
    }
    //
    if (!SetPriorityClass(GetCurrentProcess(), PROCESS_MODE_BACKGROUND_BEGIN)) {
        cerr << "SetPriorityClass(*, PROCESS_MODE_BACKGROUND_BEGIN) エラー: " << GetLastError() << endl;
        cerr << "でも続行します" << endl;
    }
    int result = _spawnvp(P_WAIT, argv[1], argv+1);
    if (!SetPriorityClass(GetCurrentProcess(), PROCESS_MODE_BACKGROUND_END)) {
        cerr << "SetPriorityClass(*, PROCESS_MODE_BACKGROUND_END) エラー: " << GetLastError() << endl;
        cerr << "でも続行します" << endl;
    }
    return result;
}

使い方

例1

        make nantoka

を低い IO 優先度で実行したい場合に

        ionice make nantoka

例2

場合によっては(バッチファイルとか?) cmd.exe を噛まさないと動かないかもしれません。

        build nantoka

を低い IO 優先度で実行したい場合に

        ionice cmd /c build nantoka