hoehoe2

t-ashula / hoehoe2 てな形で,Tween ver. 1.1.0.0 GPLv3 版から fork して C# 化の区切りがついたのでエントリ起こし.

opentween ていう風にプロジェクトを構えて運用していくほどの気力はなくて,今のところは単純に自分用. twitter クライアントとしてどうこうというより  .NET と mono,C# と VB.net の互換性みたいな技術的な題材として見ている面が強い.

 実際公開はしたけれ,ライセンス的な問題,API キーの問題,多言語化,インストーラプロジェクトの変更,アップデート周りの整備と自分一人で使うには問題ないけど tween の oss fork プロジェクトとして野に放つには抱えてる問題は多い.なので,これまで維持してきた@kiri_feather たちはすげぇなと思うわけです.

で, 以下幾つかの抱えてる問題と技術的なお話.

issue

ライセンス的な問題

もともと v1.1.0.0 までは GPLv3 が適用されていたので,ココから GPLv3 に基づいて fork する分にはなんの問題もない.が,問題は幾つかのアイコン類のどこまでが GPLv3 の適用範囲になるかというところと Dynamic.vb

アイコン類

アイコン類は実際には大した問題じゃない.

Tween v1.1.0.0 に同梱のLICENSE.ja.txt に,Tween/Resources ディレクトリに含まれるアイコンファイルは GPL でライセンスされないと書かれているが, これらのファイルがオブジェクトコード ( Tween.exe ) の生成に必要な 対応するソースコードである以上 GPL と互換性のないライセンスで頒布することは出来無いはず.

なので,強行的に GPL とみなして使い続けてもライセンス上の問題はないと思う.

が,そんな微妙な状態のまま使うより,アイコンなので Tween と別物であるという意味も含めてライセンス的に問題ないものに置き換えて行く予定.

Dynamic.vb

 内部で使われている Dynamic.vb  (取得元 ) が MS-PL でライセンスされている.

FSFOSD 適合ライセンスだとしながらも, MSPL と GPL は 非互換との立場 をとっている.

ソースコードでの頒布時に MSPL にしないとダメだからというのが理由のようだが,(cf. http://lwn.net/Articles/254717/ ) 今ひとつはっきりしていない.

個人的には,ライセンス混在とか面倒なので正直避けたいのでいずれ置き換える予定.

API キーの問題

オリジナルの Tween が対応していた幾つかの Web サービスの API キーについては置き換える必要がある.ソースのどこにそのキーが書かれているかとか,どこに問い合わせればキーが得られるかは文書化されてないので,1つずつ調べて置き換えていくしかない.別に自分が使ってないサービスならどうでもいいかと思わんでもないので正直しんどい.

これを書いてる時点では,Twitter の ConsumerKey と ConsumerSecret 以外は置き換えてない. 

多言語化

オリジナルの Tween  だと英語と中国語のリソースがあって切り替えられるようになっていたのだけども,VB から C# に変換するときに,バッサリと切り替え周りの処理を切ってしまったのと,検証用の環境が無いので無用の長物になってるんじゃないかな.個人用なら別に問題ないので予定は未定. 

インストーラ

InstallShield をつかったプロジェクトがオリジナルのソリューションにはあって,個人用なら別にインストーラ要らないし,InstallShield 有料だし,WiX とかで置き換えるのも技術的には面白いかもねとかあって決めかねてる.

アップデート周り

 オリジナルの Tween の SourceForge.jp のプロジェクトの URL を参照してるのでこのへん削除するなり,置き換えるなりが必要.

 TweenUp.exe の方も変換するなり削除するなりやらないとマズイ. 

 個人用なら別に(以下略 

その他細かい技術的なことを言うと,

  1. テストコード無い
  2. 命名法がない
  3. 確たるコーディングスタイルがない
  4. WinForms べったり.せめて DataBinding を.
  5. etc

という話があったりなかったりで誰が幸せになるのか分からないけど,まあ楽しいからいいや.

 

 

vb.net から c# に書き換えたお話.

hoehoe2 の話の続きで,とくに実装の方の話.

変換

ShapeDevelop に Tween.sln を読み込ませて,Tween プロジェクトを右クリックして Convert -> From VB.Net to C# とやってハイおしまい.と行ければよかったのだけどもそんなことはなく色いろあるので対応.

  1. まず異常終了するので,変換後のプロジェクトが入るフォルダを見てどのファイルまで変換して落ちたのかを特定.
  2. ファイルを特定できたら上から半分を  #if 0 #endif で隠して変換.
  3. それでも落ちたら下半分を if-endif で隠して変換.と二分探索を繰り返してメソッド一つまで特定.
  4. ちなみに,落ちてたのはAppendSettingDialog.vb の 2466行目あたり,Twitter.vb の 3083 行目あたりと3143行目あたりなので目印つけてコメントアウトし変換.
  5. 異常終了はしないものの,変換できないファイルがあると,変換のログ出力に言われるのでそれの対応をする.
  6. ちなみに変換に失敗してたのは,Dynamic.vb の nullable, ImageDictionary.vb の Dictionary の From を使った初期化,Tween.vb での AndAlso/OrElse と LINQ の文末の判定処理,Spliter2.Panel2.Resize ハンドラ,Twitter.vb の Select Case の Is <> と言った箇所.続きは diff で
  7. これでめでたく C# になったので,Visual Studio で Tween.sln を開いてビルド出来る状態まで修正することになる.

ビルド出来るまで

めでたく逐語訳で VB.Net から C# になったものの,いくつか超えられない文法上の壁や ShapeDevelop 側のバグでビルド出来無いので対応する.変換器の異常終了への対応でコメントアウトした部分はここで手作業で変換した.

ディレクティブ

VB.Net だと #If 0/#End If#If UA = "True" Then/#End If とできるが C# だとできないので #if NOTDEFINED/#endif などで置き換える.

try-catch-finaly などで中括弧{}が挿入される箇所とディレクティブが重なるとうまく処理できないようで

Try
  [module] = assembly.DefineDynamicModule("Module")
Finally
#If ENABLE_LINQ_PARTIAL_TRUST Then
  PermissionSet.RevertAssert()
#End If
End Try

try{
  module = assembly.DefineDynbamicModule("Module");
#if ENABLE_LINQ_PARTIAL_TRUST 
}finally{
  PermissionSet.RevertAssert();
#endif
}

となってしまっていたので機械的に書き換えた.

Property

VB だと Property であっても,メソッドのように好き勝手に引数を付けて呼び出せるけども,C# だとそうは行かないので関数で置き換える.VB で引数のある無しで多重定義された Property もあるので適宜置き換えていく.SharpDevelop での変換でProperty の引数がなかったことにされているので,1つずつ確認していった.

配列

ary[idx] となるべきところがary(idx)と変換されないままになることがあるので,機械的に置き換えた.

LINQ

LINQ (query 式) の改行がなかったことにされたり,let や select がなかったことにされて意味が分からないことになってるので,vb 側のコードを見ながら置き換えた.

関数 ( ref/out )

C# だと関数の ref 引数にはデフォルトパラメータを与えられなかったり,property や null を ref に渡せなかったり,out にはちゃんと代入しないといけなかったり,値を返さないパスがあっちゃダメだったりと細かいところで VB が緩いので1つずつ潰していった.

Generic

変換器の制約で, Sub foo(Of int)foo<int>() となるべきところが ただの foo() にされていたので対応箇所を1つずつ確認していった.

その他

その他細かい文法上の違いで,interface に delegate を定義してたり(IHttpConnection.vb),内部クラスのそのまた内部 Interface を自分自身に適用させてたり(WebBrowserController.vb) と言った箇所を適当に変形させて,変換器が解釈をミスしまくってた Win32Api.vb をがっつり書き換えていった.

ビルド

このあたりで一応ビルドできるのでしてみると一応動作するものの関連 tweet をたどれなかったり,thumbnail が出なかったりで,時々例外を吐くのでそれを手がかりに変換ミスを探り,

VB だと field と同じように Property に初期値を 与えてるものがあって,変換の時にその処理が抜けていたのが原因だったので,プロパティの初期化をコンストラクタに追加して修正.

これでひとまず文法上はC# になったのでより C# らしく,Microsoft.VisualBasic.dll への参照を順次削除する作業にかかった.

より C# らしく

DateAndTime,Strings,vbConstants

DateAndTime は DateTime に,Strings の各関数は同等の機能に,vbConstants の CrLf は "\r\n" にと機械的に置き換えた.

イベント

まず VB.net ではコントロールのイベント処理(例:ボタンのクリックイベント)がこんな感じになる.

  1. Form1.Designer.vb で Private WithEvents button1 as Button としてボタンを宣言
  2. Form1.Designer.vb の InitializeComponent() で button1 = new Button()
  3. Form1.vb で Private Sub button1_click() Handles button1.click として処理内容を書く

これに対して,C# だと

  1. Form1.Designer.cs で private Button button1; と宣言
  2. Form1.Designer.cs  の InitializeComponent() で button1 = new Button(); して button1.click += button1_click;
  3. Form1.cs で private void button1_click() で処理内容を書く

となる.通常のフォームのコントロールであれば InitializeComponent() で一回作ればフォームが死ぬまで作り変えられることはないのでイベントハンドラの追加も一回あればよく,C# で明示的にハンドラを追加していることに特に問題はない.逆に言うと,C# では new して作ったオブジェクトには明示的にハンドラを追加してやらないと問題がでるということである.これを解決するために ShapeDevelop が取った方法が,プロパティの set{} に隠蔽するという方法で,汎用性を考えると悪くない方法なのだが,Control には周りくどい上デザイナでうまく扱えないので InitializeComponent() で追加するように全て置き換えた.

この変換に関して VB で WithEvents が付いていて同時に初期化もされるフィールドを,C# で フィールドとプロパティにしたときにフィールドの初期化はするものの,プロパティの set {} を経由しないのでイベントハンドラが割当てられないという SharpDevelop の変換処理の不具合があって,タイマー処理がされなくなっていたのでタイマーの new 直後にイベントハンドラの追加コードを追加して修正した.

Exit For

VB だと割りと自由にブロックを抜け出せる.

例えば,

For Each Ele In Elements
  Select Case Ele.attr
     Case AttrA
       foo()
       Exit For
  End Select
Next
bar()

とすれば Ele.attr が AttrA のとき bar() に制御を移せるが,C# でこれを実現する文法上の方法はないので

bool flg = false;
foreach( var Ele in Elements ){
  switch(Ele.attr){
    case AttrA: 
      foo(); 
      flg = true;
      break;
  }
  if ( flg ) { break; }
}
bar();

と一手間掛ける必要がある.

幸いにも,変換処理でこの手の Exit にはすべて TODO: が付けられているので 1つずつ適切に変換することが出来た.

Static Local

VB にだけある機能として Sub で Static な変数を定義できる.Sub が呼び出されるても値が初期化されず残る C や C++ の関数の static 変数と同じ機能がある.ところが,C# の文法にはないので,クラスのフィールドと Microsoft.VisualBasic.CompilerServices.StaticLocalInitFlag を使うように変換される.ところが,実際にはフィールドにするくらいで十分(だろう)と判断してさっくりと削除した.

My

VB のアプリの最大の特徴とも言えるのがこの My で,この My の下に Settings や Resources があり,その他にすべてのフォームのインスタンスやApplication,キーボード,オーディオとあらゆるものが詰め込まれていてほとんどどこからでも呼び出せる.C# だと Settings や Resources は普通 Properties 以下に配置され,Application ではなく Assembly.GetExecutingAssembly を経由する.

名前空間は置き換えれば済んだし,その他も同等の機能を実装して対応した.

My.Forms を経由した別フォームへのアクセスも実際には TweenMain (form) が Owner として設定されているのでそれを経由することで対応した.

最後まで残ったのは MyApplication である.

MyApplication

MyApplication とは何かというのは,そもそもアプリがどう起動するかという話で,面倒なので http://www.atmarkit.co.jp/fdotnet/dotnettips/631vbappfx/vbappfx.html でも読んでもらうとして,結局は Main にベタに書けば済むことなので applicationEvents.vb から Program.cs に移してめでたく終了.

 

My や MyApplication の理解があやふやで, 実際 Culture 周りが残ったままなので ,これじゃマズイのかもしれないけどもひとまずの記録として.