STLのrandomを使う場合の備忘

確認したのはC++ Builder 10.xのTokyoとRioだけなのでCLang一般ではないと思う。
しかし、インプリメントを確認しないで実装するとセキュリティホールになると思われる。

STLのrandomライブラリーはCベースのrandの代わりになる乱数ライブラリだ。
実用的な乱数発生器として重宝するのだが、シーダーのインプリメントによっては、乱数を推定できる。

mt19937の初期化として、よく紹介される以下のようなコードがある。

  mt19937 my_rand;
  random_device seeder;

  my_rand.seed( seeder() );

実際、適切にインプリメントされている場合、seeder()は良い乱数を発生させてmt19937の乱数発生器を初期化する。

残念ながら、C++ Builder 10.x系のCLang処理系において上記のテストを行うと、必ず同じ初期化子が渡されて初期化されることを確認した。
seederを繰り返し呼び出しても、常に同じ系列が返されるので予測可能となる。
確認のために次のコードを示す。

#include <iostream>
#include <random>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
  random_device seeder;

  cout << seeder();
  // 結果は常に同じ
  return 0;
}

実行するたびに常に同じ値が示されることが確認できるだろう。
mt19937についてもseedを設定しないで実行した場合、やはり同じ系列の乱数を返すので完全に予測が可能だ。
オブジェクトの生成毎に同じ乱数系列を設定するので、以下の例では同じ数字が3回繰り返される。

#include <iostream>
#include <random>
using namespace std;

void my_mt19937()
{
  mt19937 my;

  cout << my() << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
  my_mt19937();
  my_mt19937();
  my_mt19937();
	return 0;
}

乱数をつかっているつもりが、アプリケーションで乱数発生器の利用回数が分かれば乱数が簡単に推定されてしまう。

ということで、将来は分からないが現時点では乱数発生器へ渡すシードは昔ながらのアドホックに頼るのが一番ではないかと思う。
面倒なので、Windowsのみの場合を、他では時間をとるかな?

  mt19937 my;
  my.seed( ::GetTickCount() );

もっと良い方法があれば教えてほしい。

C++ Builderでアイコンをリソースに登録する場合の備忘

Windowsプログラムでアプリケーションのアイコンの他に、関連付けファイルのためのアイコンなどを登録したい場合がある。
ただし、何も考えないで登録すると、アプリのアイコンがリソースで登録したアイコンになってしまう。

C++ Builder(Delphiもそうだろう)は、リソースを、リソース識別子の文字コード順(まあアルファベット順と思っていいだろう)に並べ替えてアプリケーションにマージする。
アプリケーションのアイコンは、オプションのアプリケーションアイコンの設定で行う。それ以外のアイコン等のリソースは、プロジェクトのリソースと画像で登録する。
リソースにアイコンを登録する場合は、事前にアイコンを作成して、前述のリソースと画像のダイアログで設定する。このとき一意のIDとしてリソース識別子を設定するが、自動で設定される場合はIcon_1から順に割り付けられる。

オプションで設定するアプリケーションのアイコンはリソース識別子としてMAINICONが割り付けられる。このため、Icon_1などより後になる。
例として、リソースにアイコンを一つ追加すると、アイコンリソースの0番はリソースで追加されたアイコン(デフォルトのIcon_1のリソース識別子)で、1番はアプリケーションアイコン(MAINICON)となる。

そして、アプリケーションのアイコンとして表示されるアイコンリソースは、通常0番であるため、意図した結果と異なって困るわけだ。

これを避けるには、MAINICONより後ろになるリソース識別子を設定する。簡単にはIcon_1となっているものをNIcon_1などとすれば良い。

うちもやられたノートンさん

朝のPCを立ち上げてしばらくした頃、ノートンインターネットセキュリティが「脅威の処理中です」とメッセージを流してきた。
何かしらあったかなと疑問に思いながら処理完了後のログを見ると、「Heur.AdvML.B」ウィルスと判断されて削除された実行プログラムが複数あった。
危険度高ということで、即座に削除されていた。

削除されたのはC++Builderで自分が作成したテストプログラムと、インストールテスト用のテストEXE。
これらは自分でビルドしているので、ウィルスに感染するとすれば、作成に用いたアプリケーションかライブラリがウィルス感染しているか、未知のウィルスがビルド後にウィルスを付加するしかない。

アプリケーションかライブラリが感染しているのであれば、作成したすべてのプログラムがウィルスとなっているはずだ。サイズも大きくなるはず。
バージョンアップも最近は毎年やっているし、それはないかなぁと思う。

未知のウィルスがビルド後に付加する方もさらに無理筋だろう。
テストプログラムなので、コアとなる機能だけの実装をしている。誤検知されたものを見るとWindowsレジストリやシステムフォルダーに関わるコードを書いていたので、この辺がウィルスと判断されたのかもしれない。

インターネットで調べると、SONARで「Heur.AdvML.B」と誤検知されることはかなり多いようだ。
自分で作って、自分のところだけで誤検知が起こるのなら良いが、よかれと思って公開して、利用者側で誤検知され、悪意なくウィルスソフトだと風評が広まる場合は困る。
実際、外国の方から自分の別のフリーソフトウェアが一部のセキュリティソフトでウィルスと検知されると連絡があり、自分でも確認した上で誤検知だと説明したがうまく伝わっただろうか。

それでも自分はセキュリティソフトウェアはこのくらい厳しくチェックしてくれた方が良いと思っている。

QGIS初心者がCRSの設定ではまったこと

判っている人にとっては当たり前のことでも、知らない人は思いもつかないことがある。
今回身をもって思い知った。

QGISは以前触ったことがあるものの、頻繁に使うわけでもない。まあ初心者といってもいいだろう。
オープンソース系で活発な活動を行っているソフトだけに、1年前の情報は使えない場合も多いので、今回使うに当たって、予習をすることにした。

QGISは、最新版の安定版(3.4.7)をインストールしてテストを開始した。
目的としては、CSV形式のデータをQGIS上で表示・印刷することだ。
地図イメージは国土地理院のタイルマップを用いることにした。

流れとしては次のようになる。

  1. プロジェクトの新規作成
  2. 地理院タイルマップをレイヤーに追加
  3. CSVデータのレイヤーにインポート

QGISでは、プロジェクトに座標参照系を設定することはもちろん、レイヤー毎に座標参照系を設定できる。
座標参照系は、JGD2011か、さらにいうと日本の平面直角座標系(JGD2011で各系)にしたい。

まずは、プロジェクトの座標参照系をJGD2011(EPSG:6666)に設定した(実はこの選択にも問題がある)。
次に地理院タイルマップをレイヤーに追加する。この時点でなぜかプロジェクトの座標参照系がWGS 84/Pseudo-Mercator(EPSG:3857)に変わる。
地理院タイルのプロパティを見るとこの座標参照系がセットされているので、これに引っ張られているのだろう。

プロジェクトの座標参照系をおもむろにJGD2011(EPSG:6666)に直す。
そうすると、地理院タイルが表示されなくなる。
通常レイヤーを右クリックして「レイヤーの領域にズーム」をクリックすれば座標系の変換がうまくいっておればそのレイヤーの全体が表示されるので、そうしてみるが、白い画面だけが表示される。
ここで勘違いだが、地理院タイルのレイヤーの座標参照系をJGD2011(EPSG:6666)にすれば正しく表示されるのではないかと思いつく。
そうすると、正しく表示されているように見える。ただし、妙に縮尺が大きかったり、座標表示を度分秒にするとおかしな値を示す。

この状態でCSVのインポートを行う。QGISのVer.2.x.x系とは異なり、インポートは、メニューのレイヤ→レイヤの追加→CSVテキストレイヤの追加を選択する。
QGISでは、メニューの構成がよく変わるのでそのたびに探すことになる。

ファイル名等は良いとして、GIS上に表示させるには座標を読み込ませる必要がある。
ダイアログのジオメトリ定義を開いて、ポイント座標のXフィールドと、Yフィールドを設定する。
ここで、CSVの1行目がカラムヘッダーである場合、その定義名を指定できる。X,Yをそれぞれ指定して、ジオメトリのCRS(座標参照系)をJGD2011(EPSG:6666)に指定して追加をする。

さて、どうなるかというと、追加したポイント座標は大西洋のアフリカ沖(北緯0度東経0度付近)にポイントされる。
度分秒のチェックを入れて、それに対応したフォーマットのCSVを用意しても同じ結果である。

何が悪いのかよくわからず、座標参照系をいろいろ変えて試行錯誤した。
インターネットや各種情報では、判を押したように以下のように説明している。

  1. CSV読み込みの座標(X,Y)は緯度経度の浮動小数点値である。
  2. 読み込み時の参照座標系は好きにして良い。

どこを間違ったのだろうか?
実を言うといくつも間違いがあって、それが複合していた。

  1. 地理院タイルレイヤーの座標参照系を変更した。(これはソースの参照座標系なのでそのままにしなくてはならない)
  2. unitがメートルの座標系を選択していた
  3. CSV取り込みに関する情報は正確な説明ではなかった

1番目は、説明不要だがレイヤーのプロパティで設定する参照座標系は、例えばタイルのオリジナルの作成座標系を指定しなくてはならず、通常は読み込み時の座標系で正しい。これを変えるのは、間違った座標系が初期設定されているか、座標系が不明になっている場合だ。

2番目は、インターネットでも書籍でも記事を見かけない。当たり前といえば当たり前なのだが、座標参照系の種類によって、座標は緯度経度であったり、XYのメートルであったりする。実を言うとJGD2011(EPSG:6666)はメートル座標系なのだ。一方、JGD2011(EPSG:6668)は緯度経度で表す。

3番目は、2番目の問題と同根である。取り込み時の座標とCRS(座標参照系)の設定は次のように表現するのが正確だろう。

  1. 取り込む座標系に即したXとYをCSVに設定して、その値のあるカラムをXフィールドとYフィールドに設定する。
  2. 参照座標系は、X,Yに設定した座標を示す参照座標系を選択する。

緯度経度で設定した座標であれば、例えばGPSで取得した座標は通常WGS84であるはずなので、Xに経度、Yに緯度を与え、CRSにはWGS 84を設定する。
平面直角座標系で設定した座標であれば、Xに平面直角座標系のY値を、Yに平面直角座標系のX値を与え、CRSにはJGD2011の各系を設定する。XYが逆なので注意が必要だ。

今回の一件は、注意深く考えていれば当たり前のことに躓いている案件だ。

なお、Ver3.x.x系では、CSV読み込みの設定に度分秒のチェックがあり、度の小数点表記でなくとも読み込めるようになった。
例えば、34度45分12.33秒は、34(半角スペース)45(半角スペース)12.33と半角スペースで区切ったり、あるいは/などで区切りを入れることで取り込みが可能だ。繰り返すようだが、この場合においてもCRSを平面直角座標系などにすると間違った場所にポイントされる。

Excel2010で令和元年

改元は、日本の伝統と独自性とは思うものの、実務での利用はつらいものだ。
自分の会社内では西暦で通すことにしているが、お客への対応はまた別なので、やきもきしながらWindowsの対応を待っていた。
さて、ようやくWindows10(1809)の改元対応Updateが降りてきたのでチェック。
既知の問題があるようだが、普通の環境では問題になりそうにない。

日本人として、令和1年は令和元年とした方がしっくりくる。Excelでは元年表記ができるように更新されているが、そのための表示形式はひとひねりが必要だ。その辺の記事は以下のHPにある。

日本の年号変更と元年 - Office サポート

さて、これに従ってExcel2010で、表示形式を変更してみた。

f:id:konishih:20190504161138p:plain
Excel2010での元年指定

上記のHPの通り指定をすると(上から4番目)、元年にはなるが元号がつかず、月日もおかしい。
表示形式を再度開くとなぜか[$-ja-JP]の箇所が[$-139E49]となっている。
Office2019では正しく表示されるので、このja-JPという設定は2010では対応していないのだろう。
なお、43585と43831のマジックナンバーは、2019/4/30のシリアル値(43585)と2020/1/1のシリアル値(43831)なので、当然のことながら令和以外の元号において元年表記はならない。なんともまあアドホックだ。

Windowsのバージョン(あるいは過去のパッチバージョン)によっては一番下の設定で元年となる場合もあるらしい。VBAのFormatでも同じ情報が流れている。

簡単で良いのだが、残念ながら最新版では却下されたようだ。

現時点で、エクセルで令和元年5月1日と入力しても2019/5/1に変換されず、文字列として認識されるようだ。やはり元年表記の取り扱いの難しさが、元年を含めた相互変換の実装を諦める方向に動いたのだろう。

ポインターのポインターを返すコードで参照を使うべきではないと思う。特にDLLでは

DLLを使ったアプリケーション開発で、サードパーティーのコードではまった件。

特徴的なのがDLLでエクスポートされている関数の定義が下の様になっていた。
なお、DLL特有の修飾は今回関係ないので省略している。

void func( char*& data );

さて、この関数は、文字列をセットして返すことがわかっている。
では、値を返すバッファー(data)はどちらが用意するのだろうか?

通常、DLLを使うアプリケーションでは、バッファーを用意するのは利用者側だ。
そうでないと、メモリアロケーションがらみで予想しないトラブルが発生する場合があるためだ。

その場合、以下の様なコードが考えられる。簡便化のため、バッファサイズの最大値はあらかじめ予約されているとする(MAX_BUFFER)。

char* buf = new char[MAX_BUFFER];
func( buf );
...結果を利用するコード...
delete[] buf;

この場合、funcの実装が以下の様であれば正常に動作する。

void func( char*& data )
{
strcpy( data, "文字列" );
}

しかし、&は何のために必要なのかという問題がある。

普通、宣言は、以下の様にして&はつけない。

void func( char* data );

この関数の実装が実際どうなっていたかというと、以下の通り

void func( char*& data )
{
static char* buf = "文字列";
data = buf;
}
バッファーを用意せず、内部データへのポインターをコピーしている。
利用者側の先ほどのコードを実行すると、この実装の場合、下の箇所でアクセス違反が発生する。
delete[] buf;
違反が発生しない場合も、もっと複雑なオブジェクトの場合は、メモリーアクセスができなくて往生する。
利用者側からすると、原因がもっともわかりずらいパターンだ。今回はDLLのソースコードがあったので、気づくことができて助かった。

一般的に関数でポインターの参照渡しの場合は、そのポインターを変更しないのがマナーだと思う。
なぜなら、自分がはまった様に関数の利用者が勘違いしやすいためだ。
適当なオブジェクト(Object)のポインターを使った呼び出しで、参照渡しとポインター渡しの二つを比べると違和感を感じてもらえるだろうか。

 Object *pObj;

// ポインターを渡して、Objectを操作するのを期待していると利用者は考える
func_refer( pObj );


// ポインター自体を操作するのを期待していると利用者は考える
func_pointer( &pObj );

また、DLLの実装からすると、DLL側に保持データを持たせる(DLL側のメモリーアロケータだよりになる)状況はできるだけ避けるべきだ。
そうでなければ例外の発生などのシチュエーションでDLLとの接続が正しく終わらなかったとき、幽霊オブジェクトが発生するからだ。

私ならこうする。

// 関数の実装
void func( char* data, int nSize )
{
static char* buf = "文字列";
strncpy( data, buf, n );
}

// 呼び出し側
std::vector buf( MAX_BUFFER+1 );
buf[MAX_BUFFER] = 0;

func( &buf[0], MAX_BUFFER );

SelectDirectoryでリンカエラー

SelectDirectoryを使うプログラムを書いているとき、リンカエラーを起こすことがある。
どうも、よくわからずいろいろしている内にリンクできてなんとなく放置していた。

今回、C++ Builderが10.3(Rio)になったのので、既存のプログラムをコンバートしてみると、リンカエラー・・・。

[ilink32 エラー] Error: 未解決の外部シンボル '__fastcall Vcl::Filectrl::SelectDirectory(System::UnicodeString, 
System::WideString, System::UnicodeString&, System::Set, Vcl::Controls::TWinControl *)' が
D:\(中略)\WIN32\DEBUG\UNIT1.OBJ から参照されています

他にも、Rioには問題があって、まだ移行はしていないが、導入テストを繰り返していた。
この問題にも腰を据えてかかることにした。サポートさんお世話になりました。

まず、なぜかうまくいったときのプロジェクトと、うまくいかないときのプロジェクトを比較してみた。
そうすると、プロジェクトファイル中の<AllPackageLibs>にvclx.libがあるかないかで決まることがわかった。
プロジェクトファイルの<AllPackageLibs>にvclx.libを追加するとリンクが成功する。


何か環境依存なのか悩んでいたが、フォームにvclx.libを必要とするコンポーネント(TFileListBox)を貼り付けてみてコンパイルしたときに付加されるインクルード文を見て何が問題かが明らかになった。

自動的に付加されるインクルード文は以下の通り

#include <Vcl.FileCtrl.hpp>

Windows環境ではファイル名の大文字小文字は区別されないので、普段はあまり大文字小文字を気にせずインクルード文を書くことが多い。

#include <vcl.filectrl.hpp>
こんな風に書いても、コンパイラーは文句を言わない。
しかし、ライブラリのサーチを行うプログラムは大文字小文字を区別するので、上の小文字だけだとライブラリ(この場合はvclx.lib)を追加しないのだ。

ということで、SelectDirectoryでリンカエラーとなる問題に対する解決方法は以下の通り。
「インクルード文を#include <Vcl.FileCtrl.hpp>と大文字小文字を区別して記述する」