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>

2016年12月15日木曜日

tile splash

はじめに

ウェブ上で地図の配信を行う手法は、タイルに分割された地図画像をajaxにより書き換える方法と、ベクトルデータを全部・あるいは部分的に読み込む手法が主でした。近年、ベクトルデータをタイル分割し、ajax により書き換える方法が登場しました。それが VectorTileです。
 このVectorTileの配信を行うオープンソースのモジュールのうちのひとつがTileSplashです。TileSplash は node.js という javascript によるWebサーバのエンジン用ライブラリとして書かれています。プロジェクトは、GitHubで下記にて公開されています。

 https://github.com/faradayio/tilesplash

実行に必要なもの

TileSplash を実行させるには、以下のモジュールが必要です。 node.js npm PostgreSQL server PostGIS インストール方法については、割愛します。

TileSplash 環境の構築

コマンド・プロンプトにて、npm を利用して SplashTile をインストールします。
$ npm install tilesplash
尚、-g オプションを使用すると、グローバル環境にモジュールがインストールされますが、ここでは、コマンドを実行したディレクトリ上にモジュールがインストールされるようにします。
 PostgreSQL Server に図形テーブルを構築します。注意点として、図形の座標系は、EPSG4326 でないと動作しません。また、ST_Transform(geom,4326) と言った書き方をしてもエラーになるので注意してください。既存のgeometryフィールドが異なるSRIDで作成している場合は、以下のようにフィールドを追加すると良いでしょう。
 alter table target_table add column geom2 geometry(‘MultiPolygon’, 4326);
    update table target_table set geom2 = ST_Transform(geom,4326); 
エディタで、sptile.js を作成し編集します。
var Tilesplash = require('tilesplash');
// username に postgresql へ接続するユーザ名を指定
// localhost にpostgresql server ホスト名を指定
// dbname に postgresql のデータベース名を指定
var app = new Tilesplash('postgres://username@localhost/dbname);

// layer_name はレイヤ名で、ajax によるリクエストの一部として扱われる
// table_name は図形テーブル名
// geom は図形フィールド名
// ここではシンプリファイをかけていますが、tile パラメータの x 値により
// シンプリファイをかけるパラメータを変更する必要があると思われます
app.layer('layer_name', function(tile, render){
  render('SELECT ST_AsGeoJSON(ST_Simplify(geom,0.005)) as the_geom_geojson’
     ‘FROM target_table WHERE ST_Intersects(St_Simplify(geom,0.005), !bbox_4326!)');
});
// ポート3000にて、Webサーバを起動します
app.server.listen(3000);
このまま稼働させると、レスポンスが javascript になるため、違うサイト間でのリクエストで CORS(Cross Origin Resource Sharing)の制約にひっかかって動作しない可能性があります。具体的には「No 'Access-Control-Allow-Origin' header is present」といったエラーがブラウザにより返されます。これを解消するには、以下のモジュールを利用します。
http://www.slideshare.net/kitfactory/web-api-34814937
$npm install corser

でモジュールをインストールし、
node_mojules/tilesplash/lib/index.js
ファイルに以下の修正を施します。
  this.server.use(pgMiddleware(dbOptions));

  var corser = require("corser");      // 以下の2行を CORS 対策として挿入
  this.server.use(corser.create());

  this.cacheOptions = cacheOptions || {};
  this._cache = new Caching(cacheType || 'memory', cacheOptions);
これは、レスポンス・ヘッダに
  Access-Control-Allow-Origin: *

を付加する事になります。

クライアント実装例

ローカルホストの node サーバに対して、アクセスする例です。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>GSI Tiles on OpenLayers 3</title>
<link rel="stylesheet" href="http://openlayers.org/en/v3.10.1/css/ol.css" type="text/css">
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<script src="http://openlayers.org/en/v3.10.1/build/ol.js" type="text/javascript"></script>


<style>
  body {padding: 0; margin: 0}
  html, body, #map {height: 100%; width: 100%;}
</style>
</head>
<body>
  <div id="page">
    <div id="head"></div>
    <div id="main">  
      <div id="map" style="float:right"; width:640px; margin:0; padding:0; ></div>
      <div id="left" style="float:left; width:300px; margin:0; padding:0;"></div>
    </div>
  </div>       
<script>
var map = new ol.Map({
  target: "map",
  renderer: ['canvas', 'dom'],
  layers: [
    // 地理院タイル・レイヤ
    new ol.layer.Tile({
      source: new ol.source.XYZ({
        attributions: [
          new ol.Attribution({
            html: "<a href='http://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>"
          })
        ],
        url: "http://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png",
        projection: "EPSG:3857"
      })
    }),
    
    // topoJson レイヤ (splashtile から引く)
    new ol.layer.Vector({
      source: new ol.source.TileVector({
        format: new ol.format.TopoJSON(),
        projection: 'EPSG:3857',
        tileGrid: new ol.tilegrid.createXYZ({
          maxZoom: 19
        }),
        url: 'http://localhost:3000/' +
          'test_layer/{z}/{x}/{y}.topojson'
        }),
       style: new ol.style.Style({
         fill: new ol.style.Fill({ color: '#9db9e8' }),
         stroke: new ol.style.Stroke({ color: '#FF0000' })
       })
    })
  ],
  controls: ol.control.defaults({
    attributionOptions: ({
      collapsible: false
    })
  }),
  view: new ol.View({
    projection: "EPSG:3857",
    center: ol.proj.transform([138.7313889, 35.3622222], "EPSG:4326", "EPSG:3857"),
    maxZoom: 18,
    zoom: 5
    })
});
</script>
</body>
</html>
実行すると、こんな感じになります。 file:/// 上から実行すると、CORSにひっかかります。chrome ではなく、safari で実行しました。 CORS対策の部分は、もうちょい真面目にやらないとダメかもしれません。

2016年8月24日水曜日

digest 作成備忘録

OpenSSL の SHA-256 を利用してダイジェスト文字列を作る備忘録です。
英数字を小文字にするのどうやるんだっけ?ってあたりから、激しくドレインしてます。
#include <fstream>
#include <iostream>
#include <iterator>
#include <algorithm> // transform
#include <string>
#include <openssl/rsa.h>
#include <openssl/sha.h>
#include <boost/algorithm/hex.hpp>
#include <boost/algorithm/string/case_conv.hpp>

namespace {
  const char heading[] = "heading";
}


void show_usage() {
  std::cout << "make_digest [string]" << std::endl;
}

std::string digest( const char* str ) {
  unsigned char hash[SHA256_DIGEST_LENGTH];
  SHA256_CTX ctx;
  SHA256_Init(&ctx);
  SHA256_Update(&ctx, str, strlen(str));
  SHA256_Final(hash, &ctx);
  std::string result( (const char*)hash, SHA256_DIGEST_LENGTH );
  return result;

}

int main(int argc, char* argv[]) {

 if( argc != 2 ) {
  show_usage();
  return 1;
 }
 std::string charange( heading );
 charange += std::string( argv[1] );
 std::string hoge = digest( charange.c_str() );
 std::string digest;
 boost::algorithm::hex( hoge, std::back_inserter( digest ) );
 boost::algorithm::to_lower( digest );
 //std::transform( digest.begin(), digest.end(), digest.begin(), ::tolower );
 std::cout << digest << std::endl;

  return 0;
}

2016年8月13日土曜日

70-200レンズ

 Sigma 70 - 200 mm F2.8 を売ろうかと考えてます。
というのも、室蘭の夜景でブレないで撮れた試しもなく。マニュアルフォーカスしても、微妙にピントがズレる事も多く。オートフォーカスは遅くて、おまけにシャッターが下りなくてシャッターチャンスは無いに等しい。Merrill で撮影しても、撮れるシーンなんて、ほぼ限られる。このレンズで撮影して、やった!って思った写真は、考えてみても、ほぼゼロ。倍率も中途半端で、イマイチ。
 あと、予定外の出費が嵩んだのもあります。