NativeアプリとManaged DLLの橋渡しをするMixed mode assembly

SQLiteのアカウントデータベースを使って、ユーザー名、パスワードで認証の可否、アカウント情報を取得するようなコンソールアプリを作った。

C++からパイプ経由で呼び出されるのだが、EF使ってたりデータベースのパスワード保護をやってるせいか、接続の初期化に時間がかかって非常に遅かった。

パイプ繋ぎっぱなしというのも考えたけど、使う人が嫌がったので、
ラッパーDLLをリンクしてC++アプリの起動時にだけ接続処理がされるようにしようと思った。
最終的には以下のような構成になった。

accounts.db (SQLite3データベース System.Data.SQLiteの機能で暗号化)
AuthLib.dll (Managed DLL、System.Data.SQLite.dllを使ってデータベースを操作する)
AuthLibHost.exe (Native C++、メインの実行ファイル)
AuthLibHost.exe.config (接続文字列などの情報が書かれた設定ファイル)
AuthLibNative.dll (Mixed mode assembly、AuthLibHostとAuthLibの橋渡し役)
System.Data.SQLite.dll (Mixed mode assembly)

データベースに関するビジネスロジックはAuthLib.dllにすべて実装されているが、
これに対応するラッパー関数をAuthLibNativeにたくさん書くのが面倒だったので、
すべてEvalという一つの関数を経由するようにした。

Evalの引数はコマンドラインインターフェースと類似したargc, argsといったものにして、
加えて出力を格納するための、linesという文字列の配列とその要素数の参照を渡せるようにする。
成功、失敗をEvalの戻り値にする。

ラッパーが不要なC++/CLIを選ぶか、ラッパー書くのが面倒だけど
LINQ to Entitiesとか使えるC#を選ぶかというところですな。

tclkitの仲間

KitCreator Build and Test Status (Nightly build)
最新のバイナリが入手可能。サイズが大きめ。
Windowsの場合はダウンロードしたファイルの拡張子をexeに変える。
http://www.rkeene.org/devel/kitcreator/kitbuild/nightly/

Cookit
Tclkitとはちょっと方式が違うらしい。バイナリはやや古いものしかない。
http://www.endorser.org/en/blog/tcl/cookit/download

Tclkit
バイナリはかなり古く、更新停止中。
https://code.google.com/p/tclkit/wiki/BuildingTclkit

Kitgen build system
自分で作りたい方向け
http://sourceforge.net/projects/kbskit

Tcl8.6系でTDBCをビルトインしてるのがあったらほしいけど今のところ見当たらない。
他にもあった気がするけど思い出したら追記する。

Kitgenで作った小さめのバイナリが手元にあるけど、どこかに置いとこうかな。。。

kitgenをVisual Studio 2012でビルドする

kitgenが正式にサポートしているのはVC6からVC8(2005)までです。
VS2008~VS2013についても、いくつか変更を加えてやればビルドできます。

ただ、これはTclに限ったことではないのですが、VS2012で普通にビルドした実行ファイルはXPで動作しません。SDK 7.1Aにリンクすればビルドできますが、VS2010以前を持ってる人にとっては面倒なだけでメリットがないので古いVSを使った方がいいです。(参考ページ)。

あと、これはいいのかどうか分かりませんが、VC6でビルドするとOS標準添付のC++ランタイム(msvcrt.dll)をリンクするので、別途ランタイムをインストールすることなく動作します。MinGWでビルドした場合もそうなるのでまあ問題ないんじゃないでしょうか。

kitgen/Makefile.vc

70c70
< CFLAGS  = -W3 -D_WINDOWS -DWIN32 -DSTATIC_BUILD
---
> CFLAGS  = -W3 -D_WINDOWS -DWIN32 -DSTATIC_BUILD -D_CRT_SECURE_NO_WARNINGS
142c142,148
< !if $(VCVERSION) >= 1500
---
> !if $(VCVERSION) >= 1800
> VCVER=12
> !elseif $(VCVERSION) >= 1700
> VCVER=11
> !elseif $(VCVERSION) >= 1600
> VCVER=10
> !elseif $(VCVERSION) >= 1500

kitgen/8.x/mk/tcl/mk4tcl.cpp

2597c2597
< EXTERN int Mk4tcl_Init(Tcl_Interp *interp) {
---
> int Mk4tcl_Init(Tcl_Interp *interp) {
2601c2601
< EXTERN int Mk_Init(Tcl_Interp *interp) {
---
> int Mk_Init(Tcl_Interp *interp) {
2605c2605
< EXTERN int Mk4tcl_SafeInit(Tcl_Interp *interp) {
---
> int Mk4tcl_SafeInit(Tcl_Interp *interp) {
2609c2609
< EXTERN int Mk_SafeInit(Tcl_Interp *interp) {
---
> int Mk_SafeInit(Tcl_Interp *interp) {

kitgen/8.x/mk/tcl/mk4tcl.h

382a383,391
> 
> 
> EXTERN int Mk4tcl_Init(Tcl_Interp *interp);
> 
> EXTERN int Mk_Init(Tcl_Interp *interp);
> 
> EXTERN int Mk4tcl_SafeInit(Tcl_Interp *interp);
> 
> EXTERN int Mk_SafeInit(Tcl_Interp *interp);

kitgen/8.x/itcl/win/rules.vc

195c195,201
< !if $(VCVERSION) >= 1500
---
> !if $(VCVERSION) >= 1800
> VCVER=12
> !elseif $(VCVERSION) >= 1700
> VCVER=11
> !elseif $(VCVERSION) >= 1600
> VCVER=10
> !elseif $(VCVERSION) >= 1500

kitgen/8.x/mk/win/rules.vc

187c187,195
< !if $(VCVERSION) >= 1400
---
> !if $(VCVERSION) >= 1800
> VCVER=12
> !elseif $(VCVERSION) >= 1700
> VCVER=11
> !elseif $(VCVERSION) >= 1600
> VCVER=10
> !elseif $(VCVERSION) >= 1500
> VCVER=9
> !elseif $(VCVERSION) >= 1400
189,190d196
< _VC_MANIFEST_EMBED_EXE=if exist $@.manifest mt -nologo -manifest $@.manifest -outputresource:$@;1
< _VC_MANIFEST_EMBED_DLL=if exist $@.manifest mt -nologo -manifest $@.manifest -outputresource:$@;2
195a202,205
> !if $(VCVERSION) >= 1400
> _VC_MANIFEST_EMBED_EXE=if exist $@.manifest mt -nologo -manifest $@.manifest -outputresource:$@;1
> _VC_MANIFEST_EMBED_DLL=if exist $@.manifest mt -nologo -manifest $@.manifest -outputresource:$@;2
> !endif

kitgen/8.x/thread/win/rules.vc

195c195,201
< !if $(VCVERSION) >= 1500
---
> !if $(VCVERSION) >= 1800
> VCVER=12
> !elseif $(VCVERSION) >= 1700
> VCVER=11
> !elseif $(VCVERSION) >= 1600
> VCVER=10
> !elseif $(VCVERSION) >= 1500

kitgen/8.x/vqtcl/win/rules.vc

195c195,201
< !if $(VCVERSION) >= 1500
---
> !if $(VCVERSION) >= 1800
> VCVER=12
> !elseif $(VCVERSION) >= 1700
> VCVER=11
> !elseif $(VCVERSION) >= 1600
> VCVER=10
> !elseif $(VCVERSION) >= 1500

8.6.1をビルドするには、kitgen、Tcl、Tkのソースをダウンロードして、
以下にコピーします。

C:\src\kitgen
C:\src\kitgen\8.6.1\tcl
C:\src\kitgen\8.6.1\tk

Visual Studioのコマンドプロンプトを開き、

mkdir C:\src\kitgen\8.6.1\kit-msvc
cd C:\src\kitgen\8.6.1\kit-msvc
echo all: lite heavy > Makefile
echo !include ..\..\Makefile.vc >> Makefile
nmake -f Makefile.vc -nologo VERSION=86 KITOPTS=”-t -z”

ちなみに、tdbc関連のモジュールが大量にビルド失敗しますが、使わない限りは問題ありませんでした。

kitgen+VC6でtclkitを作るときの注意点

TclAppを使うと、Tcl/Tkのスクリプトと実行環境を単一のEXEファイルにラップすることができるのですが、このときprefixファイルといって、ベースとなる実行ファイルを指定する必要があります。これをtclkitとかbasekitとか呼び、標準ライブラリや最低限のエンコーディングファイルなどが含まれていて、自分で開発したアプリに必要な、スクリプト一式、ライブラリ、エンコーディングなどを、TclAppを使って追加することで単体のアプリとして動作するようになります。アイコンやバージョン情報なども、prefixに組み込まれているものを置き換えることもできます。

prefixとして使えるファイルは、実はActiveTclにbasekitという名前でついてきます(Tcl/binフォルダにある)。これがあれば別に自分でtclkitを入手する必要はないのですが、2点ほど問題があります。

  • 実行ファイルのサイズが比較的大きい
  • バイトコードコンパイルすると、スペースおよびマルチバイト文字を含むパスから起動できない

ということで、私にとっては不都合だったので、basekitをやめてtclkitを使うことにしました。tclkitのWindowsバイナリはTcl8.5.13まではhttp://www.patthoyts.tk/tclkitで配布していたのですが、8.5.14がなかなか出ないなあと思っていたら今日見たら落ちてました。また、配布しているものにはtzdataというタイムゾーンの定義ファイルがないので、時刻表示が狂う場合がありました。そういう理由があって、自分でコンパイルすることにしました。前置きが長くなりましたが、以下がその手順です。Windows7 Professional SP1 32bit上で試しました。

必要なものをそろえる

Visual Studio 6.0 (Visual Studio 2003でもいいと書いてあった。)
Microsoft Platform SDK Febuary 2003 (Last version with VC6 support)

kitgenのソースコードを取ってくる

C:\src\kitgenに展開。

Tcl/Tkのソースコードを取ってくる

C:\src\kitgen\8.5\tcl
C:\src\kitgen\8.5\tkに展開。

ビルド用のフォルダとMakefileを作る

VC6のコマンドプロンプトを開く
mkdir C:\src\kitgen\8.5\kit-msvc
cd C:\src\kitgen\8.5\kit-msvc
echo all: lite heavy > Makefile
echo !include ..\..\Makefile.vc >> Makefile

リソースファイルを編集する

これはお好みですが、kitgenに添付されているものを使うと、TclAppでバージョン情報を編集できなくなるので、変更することをお勧めします。
C:\src\kitgen\tclkit.rcをテキストエディタで開き、CommentとPaddingを削除する。

            VALUE "Comments", "Comments\0More Comments\0"
            VALUE "Padding",
               "                                                            "
               "                                                            "
               "                                                            "
               "                                                            \0"

ビルドする

Platform SDKを参照するようにします。また、KITOPTS=-zというオプションを与えることで、tzdataが含まれるようにします。他にもオプションがありますが、kitgenのREADMEファイルを読んでください。

set INCLUDE=C:\PROGRA~1\MIC1C5~1\include;%INCLUDE%
nmake -f Makefile.vc -nologo VERSION=85 KITOPTS=”-z”

C:\src\kitgen\8.5\kit-msvcの中にできる、tclkit-cli.exe、tclkit-gui.exeをTclAppのprefixに指定できます。

UI Automation Clients

UI Automationについて参考になりそうなページを50個ほどブックマークしたものの、多すぎるので入口として役立ちそうなページを抜粋しておく。
本来はUIテストやアクセシビリティを目的としているようだが、今回は他人が作ったソフトを自動制御するために使う。
プロバイダとクライアントの説明があるが、今回はクライアントのことが分かればよいはず。
Windows Forms、WPF、サードパーティのUIコンポーネントでもたいていはオートメーションプロバイダとして扱えるようだ。
確証はないがUISpy.exeを使ってAutomationIdなどが確認できれば対応していると思っていいのではないかと思っている。

UI Automation のススメ

UI Automation Fundamentals

UI Automation Client Programmer’s Guide (Windows)

Use the AutomationID Property

Bugslayer – こちらGUIコントロール、トム少佐応答せよ

UIツリーを辿り、プロパティを確認するにはUISpyやInspect Objectというのがある。
UI Spy(UISpy.exe)が含まれているWindows SDK Version

現状はC#でやるのが無難そうだ。
TclでもGarudaを使うと、インプロセスで.NET Frameworkのクラスを利用できるのだが、まだ未完成、ドキュメントがないに等しいので難しい。クラスメソッドやイテレーションを使えるのかすら分からない。
COMは茨の道だと思われるのでできれば避けたい。

ちまちまUI Treeを歩き回るコードを書く代わりに、もっと楽にしてくれるライブラリというのもあるみたいだ。
White
White Tutorial
StackOverflow White – File Open Dialog Box
White – Working with window

UISpyみたいなインターフェースと、UI Automationプロバイダとして必要な機能を持っているかチェックするためのソフト。
Visual UI Automation Verify
UIVerifyが内部で使っているライブラリがこれ。
UI Automation COM-to-.NET Adapter
.NET版のAPIがあるのに何でラッパーを作る必要があるのかは気になるが、まだ調べてない。

Tclでベクトル計算するサンプル

SXGAのリモートデスクトップウィンドウをアスペクト比を保ちつつ65%に縮小し、FullHDのディスプレイの右下に配置したい。手計算でも簡単にできることだが、今まで使ってなかった行列計算ライブラリの練習台に使うことを思いついた。

+-----------------------------+
|                             |
|                             |
|                             |
|                             |
|                             |
|               nw------------+
|               |xxxx         |
|               |   x rec x   |
|               |        xxxx |
|               |           xxx
+---------------+------------se
package require math::linearalgebra
namespace eval LinearAlgebraSample {
  namespace import -force ::math::linearalgebra::*
 
  set scale 0.65
  set v(se) [mkVector 2]
  set v(rec) [mkVector 2]
 
  setelem v(se) 0 0 1920
  setelem v(se) 1 0 1280
 
  setelem v(rec) 0 0 1280
  setelem v(rec) 1 0 1024
 
  set v(nw) [sub_vect $v(se) [scale $scale $v(rec)]]
  show $v(nw)
}

例題がしょぼすぎて練習になってないな。

線形代数苦手でプログラミングにも活用してないし、Maxima、Scilab、Octave、Rとかも使ってない。
ロボットの研究室にいたのに行列計算が苦手というのは勉強してなかった証拠ですね。

Tcl SQLite build configuration

Prerequisites

Build tool = MinGW GCC v4.8.1
Tcl build path = /c/src/tcl8.6.1/win
Tcl install path = /c/bin/tcl8.6.1
SQLite3 build path = /c/src/sqlite-autoconf-3080200/tea
wxSQLite3 secure source path = /c/src/wxsqlite3-3.0.6/sqlite3/secure/src

Tcl

./configure –enable-threads –prefix=(Tcl install path)
make
make install

Tk

./configure –enable-threads –with-tcl=(Tcl build path)
make
make install

TclSQLite

Copy the files in (wxSQLite3 secure source path) to (SQLite3 build path)/generic.
Open tclsqlite3.c and replace “../../sqlite3.c” by “sqlite3secure.c” in the line #4.
cd (SQLite3 build path)
./configure –enable-threads –with-tcl=(Tcl build path) –prefix=(Tcl install path) CFLAGS=”-DSQLITE_HAS_CODEC”
make
make install

This also works with ActiveTcl.

I could not make sqlite3 v3.8.2 independent from libgcc_s_dw2-1.dll with MinGW gcc v4.8.1.
I don’t know the reason. But I could build it with nmake.
For example:
nmake -f makefile.vc TCLDIR=(Tcl install path) INSTALLDIR=(Tcl install path) OPTDEFINES=”-DSQLITE_HAS_CODEC”

Ref. 暗号化APIを有効にしたSQLite3のTclバインディングをコンパイルする

ネットワークアドレス変換関連の用語

NAT (Network Address Translation)

グローバルIPとローカルIPを1:1対応させる。

NAPT (Network Address Port Translation)

グローバルIPアドレスとポート番号のペアをローカルIPとポート番号のペアに1:1対応させる。

NAPTには多数の同義語が存在する

  • IPマスカレードはNAPTのLinuxにおける実装。
  • NATP(NAT+)、PAT(Port Address Translation)
  • ポートマッピング、ポートフォワーディング
  • 仮想サーバー、バーチャルサーバー、ローカルサーバー
  • NATオーバーロード、オーバーロード変換

静的NAT、静的NAPTは外部から内部のネットワークへのアクセスに使う。一般的なブロードバンドルータの設定に存在するのはこれらの設定。

動的NAT、動的NAPTは内部から外部への通信に対する応答が、送信元に返るようにするための機能であり、通常ユーザーが意識することはない。

と思う。

参考

IPマスカレード(NAPT,PAT,NAT+)を設定する

NAT と IPマスカレード(ポートフォワーディング) について

4.インターネットへの接続形態

Excelオートシェイプでフリーフォームの頂点を直角にする

Excel2007登場以来、なんかいい方法はないものかと探していたけど、ついに見つけた。ありがたや。

図形描画で直線の頂点を編集するには?

Public Sub main() 
    On Error GoTo L_Exit 
    With Selection.ShapeRange.Nodes 
        On Error Resume Next 
        Dim i As Integer 
        For i = 1 To .Count 
            .SetSegmentType i, msoSegmentLine 
            .SetEditingType i, msoEditingCorner 
        Next 
    End With 
L_Exit: 
    On Error GoTo 0 
End Sub