2017年12月19日火曜日

Windows 環境変数適用 備忘録

環境変数を値に置き換える
#include <boost/regex.hpp>
#include <string>
#include <iostream>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

std::string getEnvValue(const std::string& str) {
  std::string result;
  std::string inp = str.substr( 1, str.size() - 2 );
  //std::cout << inp << std::endl;
  char buf[ 4096 ];
  DWORD sz = sizeof(buf);
  if( GetEnvironmentVariable( inp.c_str(), buf, sz ) > 0 ) {
    result = buf;
  }
  return result;
}

bool replaceEnv(std::string& str) {
  boost::regex r("(%[a-zA-Z0-9_]+%)");
  boost::smatch m;
  if( boost::regex_search(str, m, r) ) {
    std::string eval = m.str(1);
    std::string sval = getEnvValue( eval );
    while( std::string::npos != str.find(eval) ) {
      str.replace( str.find(eval), eval.size(), sval );
    }
    return true;
  }
  return false;
}

int main(int argc, char* argv[]) {
  std::string str = "%USERPROFILE%\\hoge\\fuga";
  while( replaceEnv( str ) ) {}
  std::cout << str << std::endl;

  

  return 0;
}

2017年12月5日火曜日

MSVCを使ったOSSのビルドについて

C++アドベントカレンダー2017のDAY5の記事になります。


皆さんOSS(オープン・ソース・ソフトウェア)のビルド、どうしてますか?
Linux 系だとパッケージメンテナーが存在していて、apt get コマンドや yum コマンド一発でビルドされたライブラリが取得できますし、プラットフォームが更新されれば、update をかませば一括してライブラリもそれに合わせて更新されます。
それに大抵は ./configure し、make し、make install で済んでしまいます。

Windows 環境でOSSを利用したい時、どうしてますか?
僕は毎回ソースをダウンロードしてから、install.txt や readme.txt やサイトを検索してビルド方法を試行錯誤していました。一度ビルドしてしまえば、当面ビルドの必要無いですしね?
現状を振り返ってみて、気がついてみれば、結構いろんなOSSを利用していました。
それに、会社ではサポートしなければならないOS等の関係もあり、長らくVisual Studio 2005を使っていました。情勢が変わって、Windows 7 の期限も近づいており、さすがに Visual Studio 2010 に切り替えました。これを契機にOSSのビルドを自動化して時間の効率化を図ろうと決意しました。

 ちょっと脱線しますが、自動化しようと思った動機について、もう少し書こうと思います。

 ひとつは、MSの提供するライブラリよりもOSSに依存した方が安全だと思った事です。ISAPI が C++でサポートされなくなったり、Regex系のライブラリが消えたり、Web系のC++ライブラリがサポートされなくなったり。特にSoapを使った独特のIDL言語を使った機構がサポートされなくなったのは、かなりの痛手です。MSのライブラリに依存することは、危険です。使いやすいからWTLでアプリを組んでいましたが、リモート・アプリケーションでのプリンタが認識できない問題で、印刷コモンダイアログを自作する羽目になりました。幸いWTLは、まだメンテナンスが行われており、Issues 等を提案すると取り込んでもらえる状況なのが救いです。Regex系のライブラリが消えた時は、移植で死にそうになりました。Soapがサポートされなくなった時は、MSはC#が大切でC++なんか気にしちゃいないと実感しました。

 もうひとつは、自社で開発している既存のソフトウェアを32bit環境から64bit環境へ移行する目論見も出てきました。移行する時にOSSライブラリを個々に調べてビルドしていたら大変です。

 最後は、あわよくばプルリクエストをもらって楽しよう共闘できると思ったからです。そんなプロジェクト、どこにも無いですし、それなりに価値があるのではないかと思っています。

そうやって作成したのが、こちら
build-oss-library-on-windows
です。

 自動化するにあたっては、いろいろと躓きました。Visual Studio の IDE を使ってコンパイルしなければならないのが面倒で調べたところ、msBuild というコマンドで IDEのプロジェクトをビルドできる事が分かりました。ところが、このIDEはバージョン依存で正しいビルドツールを指定しないとエラーでビルドできません。正しいビルドツールの番号、気が狂ってます。Visual C++ のバージョンを判定するバッチファイルの修正履歴を見ていただければ、わかると思いますが、現実は予想の斜め上を行ってました。信じらない事に Microsoft からVisual C++のバージョンに関する公式の表がどこにも掲載されていません
Visual Studio 製品名VC通称ツールセットのバージョンコメント
Visual Studio 2003VC7v70
Visual Studio 2005VC8v80
Visual Studio 2008VC9v90
Visual Studio 2010VC10v100
Visual Studio 2012VC11v110
Visual Studio 2013VC12v1202015がVC14だからVC12で合ってるか調べる羽目になったやんけ(#^ω^)ピキピキ
Visual Studio 2015VC14v140なんで一個バージョン飛んでるねん?
Visual Studio 2017VC15v141v141って殺すぞ何ぞ?(^ω^)
また、*.sln プロジェクトファイルは、それぞれのバージョンに合わせなければなりません。幸い devenv /upgrade でコマンドラインからアップグレードできるのが救いです。

 今のところ Visual Studio 2010 win32 プラットフォームでしかビルド検証されていない状況ですが、ドキュメントを参考に是非、利用してみてください。特に自前で、コツコツと毎回ビルドされている方には使ってみて欲しいです。時間の節約になると思います。そして共闘しましょう٩( 'ω' )و

 最後に…
安全という名の緊縛の言語 Java氏ね Java氏ね Java氏ね 

追記:コメント頂いた。vcpkg にて、凄い勢いでビルドパッケージが構築されているので、Visual Studio 2015 以上を使う場合には、vcpkg を利用した方が良さそうです。
尚、Visual Studio を巡るバージョン情報は、@yumetodo さんにより更新されております(これ、凄い労力だな…)


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_;
  }

2017年2月15日水曜日

plpgsql 全角半角変換

そういえば、こういうの書いたなーと思って… PostgreSQL で、全角と半角の変換を行うストアドプロシジャです。
CREATE FUNCTION kanahan2zen(strhan text) RETURNS text
    AS $_$DECLARE
strHan ALIAS FOR $1;
str TEXT;
i integer;

-- 通常カナ(半角2バイト)
--tZen2 VARCHAR[] := ARRAY[ "ガ" ];
tZen2 VARCHAR[] := ARRAY['ガ', 'ギ', 'グ', 'ゲ', 'ゴ', 'ザ', 'ジ', 'ズ', 'ゼ', 'ゾ', 'ダ', 'ヂ', 'ヅ', 'デ', 'ド', 'バ', 'ビ', 'ブ', 'ベ', 'ボ', 'パ', 'ピ', 'プ', 'ペ', 'ポ', 'ヴ'];
--tHan2 VARCHAR[] := ARRAY[ "ガ" ];
tHan2 VARCHAR[] := ARRAY['ガ', 'ギ', 'グ', 'ゲ', 'ゴ', 'ザ', 'ジ', 'ズ', 'ゼ', 'ゾ', 'ダ', 'ヂ', 'ヅ', 'デ', 'ド', 'バ', 'ビ', 'ブ', 'ベ', 'ボ', 'パ', 'ピ', 'プ', 'ペ', 'ポ', 'ヴ'];

BEGIN

str := strHan;
-- 半角2バイトの変換
for i in 1..26 loop
str := replace(str, tHan2[i], tZen2[i]);
end loop;

-- 半角1バイトの変換
str := translate(upper(str)
, 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンァィゥェォッャュョワイエカケー、。・」「゙ ,<.>/?_}]*:+;{[~@|\`^=-)(&%$#""!'
, 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンァィゥェォッャュョヮヰヱヵヶー、。・」「゛ ,<.>/?_}]*:+;{[ ̄@|¥`^=-)(&%$#”!');

return str;

END;
$_$
    LANGUAGE plpgsql;




--
-- TOC entry 26 (class 1255 OID 18986)
-- Dependencies: 5 590
-- Name: kanazen2han(text); Type: FUNCTION; Schema: public; Owner: postgres
--

CREATE FUNCTION kanazen2han(strzen text) RETURNS text
    AS $_$DECLARE
strZen ALIAS FOR $1;
str TEXT;
i integer;

-- 通常カナ(半角2バイト)
--tZen2 VARCHAR[] := ARRAY[ "ガ" ];
tZen2 VARCHAR[] := ARRAY['ガ', 'ギ', 'グ', 'ゲ', 'ゴ', 'ザ', 'ジ', 'ズ', 'ゼ', 'ゾ', 'ダ', 'ヂ', 'ヅ', 'デ', 'ド', 'バ', 'ビ', 'ブ', 'ベ', 'ボ', 'パ', 'ピ', 'プ', 'ペ', 'ポ', 'ヴ'];
--tHan2 VARCHAR[] := ARRAY[ "ガ" ];
tHan2 VARCHAR[] := ARRAY['ガ', 'ギ', 'グ', 'ゲ', 'ゴ', 'ザ', 'ジ', 'ズ', 'ゼ', 'ゾ', 'ダ', 'ヂ', 'ヅ', 'デ', 'ド', 'バ', 'ビ', 'ブ', 'ベ', 'ボ', 'パ', 'ピ', 'プ', 'ペ', 'ポ', 'ヴ'];

BEGIN

str := strZen;
-- 半角2バイトの変換
for i in 1..26 loop
str := replace(str, tZen2[i], tHan2[i]);
end loop;

-- 半角1バイトの変換
str := translate(upper(str)
, 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンァィゥェォッャュョヮヰヱヵヶー、。・」「゛ ,<.>/?_}]*:+;{[ ̄@|¥`^=-)(&%$#”!'
, 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンァィゥェォッャュョワイエカケー、。・」「゙ ,<.>/?_}]*:+;{[~@|\`^=-)(&%$#""!');

return str;

END;
$_$
    LANGUAGE plpgsql;

2017年1月19日木曜日

VisualStudio C++共通設定 備忘録

Visual Studio 2010ぐらいになってから、include ディレクトリとか、libディレクトリをプロジェクト毎に設定しろなんて、ひどい状況になったので、それの対処法

C:/Users/[Your account]/AppData/Local/Microsoft/MSBuild/v4.0/

という場所に Microsoft.Cpp.Win32.user.props というXMLファイルがある。
そこに、include と lib の設定を書いておく。
こんな感じ
<?xml version="1.0" encoding="utf-8"?> 
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <UsrLib>D:\Libs\</UsrLib>
    <IncludePath>$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(WindowsSDK_IncludePath);$(WindowsSdkDir)include;$(UsrLib)cstdint;$(UsrLib)opencv3.0\include;$(UsrLib)boost\include\boost-1_60;$(UsrLib)zlib\include;$(UsrLib)openssl\include</IncludePath>
    <LibraryPath>$(VCInstallDir)lib;$(VCInstallDir)atlmfc\lib;$(WindowsSDK_LibraryPath_x86);$(WindowsSdkDir)lib;$(UsrLib)opencv3.0\lib\Release;$(UsrLib)Boost\lib;$(UsrLib)zlib\lib;$(UsrLib)lib</LibraryPath>
  </PropertyGroup>

</Project>