WindowsプログラマのWebアプリへの挑戦日記。あとパンとか。

Archive for the ‘C++’ Category

無名のnamespaceの中にstatic?

水曜日, 1月 14th, 2009 Posted in C++ | 2 Comments »

C++の名前空間を定義するnamespaceってあんまり使わない。たぶん有用な使い方があるんだろうと思うけど、所属しているチームではほとんど使われていないので、今まで積極的に使おうとは思っていなかった。

using namespaceはたまに使う。まあアレです。

  1. using namespace std;

です。これを書いておくと

  1. std::vector v;

  1. vector v;

って書くことができる。

無名のnamespace

最近、新しいプロジェクトに関わるようになってきて、初めてのコードをよく読むようになりました。その中で、cppファイルのグローバルな領域に以下のような記述が。

  1. namespace {
  2.     const TCHAR szHoge[] = _T("Hoge\Hoge");
  3. }

ほう、こういう書き方があるるんだねぇと思って調べると、無名の名前空間はグローバルなstatic変数に変わる書き方で、C++ではグローバルなstaticの代わりに無名の名前空間を使うことが推奨されているらしい。

なるほど。無名の名前空間内の変数はグローバルなstatic変数と。よしよし。

と思って他のソースを読んでいると、今度は以下のような記述が!

  1. namespace {
  2.     static const int nHoge = 50;
  3. }

“static const”は定数を定義するときに使うんだけど、無名の名前空間内にstaticを付ける意味ってあるんだろうか。よくわからんのでこのコードを書いた人に聞いてみよう。

追記:書いた本人に聞いてみた

無名の名前空間内のstatic変数ですが、これはもともと

  1. static const int nHoge = 50;

だったコードがあって、チーム内の方針でグローバルな静的変数は無名の名前空間内で定義するように変更しようってことで、staticは付けたまま上記のようになったということです。つまりあんまり深い意味はないそうです。

C++のrand()関数でRAND_MAX以上の乱数を作る方法

火曜日, 1月 13th, 2009 Posted in C++ | 2 Comments »

C++で乱数を発生させるときに使うrand関数ですが、これは乱数の最大値がRAND_MAXまでなんですよね。で、今までは乱数と行っても0から1までとか、0から256までとかの範囲で求めることしかなかったので、あんまり気にしていなかったんですが、このRAND_MAXって0x7FFFって定義されているんです。

このあたりのことで以下のブログが実験してます。この人もrand()の最大値を意識していなかったってことですかね。

rand() の最大値 RAND_MAX (株式会社ディア Dear inc.)

それで、今回0xFFFFまでの乱数を発生させる必要があったんですが、これをどうやればrand()で実現できるか。よくわかりませんでした・・・。

範囲指定の公式があるらしい

乱数によると、rand()関数を使ってある範囲の乱数を作るときの公式は、以下となります。

  1. 最小値 + (int)( rand() * (最大値 - 最小値 + 1.0) / (1.0 + RAND_MAX) )

rand()を二乗することによって

  1. 0x7FFF × 0x7FFF = 0x3FFF0001

まで乱数を発生させることができます。これを上の式に当てはめると、

  1. 最小値 + (int)( rand() * rand() * (最大値 - 最小値 + 1.0) / (1.0 + RAND_MAX * RAND_MAX) )

となるのかな?ここで最小値は0を想定しているので、

  1. rand() * rand() * (最大値 + 1.0) / (1.0 + RAND_MAX * RAND_MAX)

となります。これって結局、

  1. (rand() * rand()) % (最大値 + 1.0)

と同じことですね。

ただrand()を使う方法だと偶数が出現する確率が大きくなるらしいので、精密な乱数が欲しい場合には乱数発生関数を自作するのがよさそうですね。

FAILEDマクロでS_FALSEはひっかからない

火曜日, 10月 7th, 2008 Posted in C++ | No Comments »

いつもどっちかよくわからなくなるのでメモ的にエントリー。次のコードを実行すると・・・

  1. TRACE( "S_FALSE Test\n" );
  2. HRESULT hr = S_FALSE;
  3. if( FAILED( hr ) ){
  4.     TRACE( "FAILED\n" );
  5. }else{
  6.     TRACE( "Not FAILED\n" );
  7. }
  8.  
  9. TRACE( "E_FAIL Test\n" );
  10. hr = E_FAIL;
  11. if( FAILED( hr ) ){
  12.     TRACE( "FAILED\n" );
  13. }else{
  14.     TRACE( "Not FAILED\n" );
  15. }

次のように出力される。

  1. S_FALSE Test
  2. Not FAILED
  3. E_FAIL Test
  4. FAILED

なぜなら、FAILEDやSUCCEEDEDは次のように定義してあって、

  1. #define SUCCEEDED(Status) ((HRESULT)(Status) >= 0)
  2. #define FAILED(Status) ((HRESULT)(Status)<0)

S_OK,S_FALSE,E_FAILは次のように定義してあるから。

  1. #define S_OK        ((HRESULT)0x00000000L)
  2. #define S_FALSE     ((HRESULT)0x00000001L)
  3. #define E_FAIL      _HRESULT_TYPEDEF_(0x80000008L)

WindowsアプリケーションのUIとしてFLASHを使う方法

木曜日, 8月 21st, 2008 Posted in C++, Flash | 3 Comments »

最近はAIRなんかが出てきて、デスクトップアプリのUIをFLASHで表現することが簡単になりました。

が、AIRではセキュリティに考慮してか、いろいろと制限があります。例えばレジストリがいじれないとか、デバイスが使えないとか。

まあそれはいいとして、WindowsのアプリケーションのUIとしてFLASHを使えれば、裏ではごりごりめんどくさいことやりつつも、UIはFLASHでステキな感じにできたりしますよね。

そんなわけで、WindowsアプリケーションのUIとしてFLASHを使う方法を解説します。

環境

開発環境は、VC6を想定しています。ボクの環境がVC6なので・・・。

Flash Playerは6以上で動くことを確認しています。今のバージョンが9とか10なので、まあ問題ないと思います。ActionScriptは2で確認しています。3もたぶん大丈夫だと思います。

WindowsアプリケーションとFlashの連携イメージ

わかりにくい絵で申し訳ないですが、以下がWindowsアプリケーションとFlashの連携イメージです。

win_flash.png

CWnd派生のCShockwaveFlashというクラスにFlashを貼り付けます。このCShockwaveFlashは、後で説明するFlashのOCXを挿入すると勝手に作られるクラスです。このクラスがFlash(SWF)と通信します。

FlashからWindowsへの通信は、ActionScriptで

ActionScript
< view plain text >
  1. fscommand( param1, param2 );

をコールします。するとWindowsアプリケーション側の指定したコールバック関数に飛んできます。このコールバック関数には、command=param1、args=param2という引数がくるので、commandごとに処理を分けることができます。

WindowsからFlashへの通信は、CShockwaveFlashの関数をコールすることによりFlashを制御できます。しかし細かい制御は難しいと思います。FlashとWidnowsアプリケーションで担当する役割をうまいことわけて実装する必要があります。

Windowsアプリケーション側からFlashの文字列を変更するために、

  1. SetVariable("v_text1", "テスト");

が使えます。v_text1というのはActionScriptの変数です。文字列以外のクラスやArray、MovieClipなどを直接制御したり、値を設定したりなどはできないようなので、SetVariableをうまく使って制御することになります。

VC6のプロジェクトにFlashを埋め込む方法

  1. テキトウにVCのプロジェクトを作ります。
  2. メニュー-[プロジェクト]-[プロジェクトへ追加]-[コンポーネントおよびコントロール]より、[コンポーネントおよびコントロール キャラリ]ダイアログが開くので、[Registered ActiveX Controls]を開きます。
  3. [Shockwave Flash Object]を選択して[挿入]ボタンをクリックします。
  4. [このコンポーネントを挿入しますか?]で[OK]をクリックします。
  5. [クラスの確認]ダイアログで、ファイル名など問題なければ[OK]ボタンをクリックします。
  6. [コンポーネントおよびコントロール キャラリ]ダイアログを閉じます。

この段階で、CShockwaveFlashクラスがプロジェクトに追加されています。

Flashを貼り付けるCWnd派生のクラスを作成します。ここでは仮にCFlashWndとしておきます。CFlashWndに次のような関数を作ります。

  1. BOOL CFlashWnd::AddFlashWindow(CString strSwfPath)
  2. {
  3.     //初期化
  4.     ::OleInitialize(NULL);
  5.  
  6.     //---- Flash Ctrlを作成します
  7.     DWORD   dwStyle = WS_CHILD|WS_VISIBLE;
  8.     UINT    nID     = IDC_SAMPLEWND;
  9.     CRect rc;
  10.     rc.SetRect(/*Flashの表示位置・サイズを指定する*/);
  11.     if( m_flashWnd.Create( NULL, NULL, dwStyle, rc, this, nID ) == FALSE ){
  12.         return FALSE;
  13.     }
  14.  
  15.     //FlashCtrlウインドウにSWFを読み込む
  16.     if( !strSwfPath.IsEmpty() ){
  17.         m_flashWnd.SetMovie( strSwfPath );
  18.         m_flashWnd.Play();
  19.         m_flashWnd.SetMenu( false );        //右クリックメニューを抑制
  20.     }
  21.  
  22.     //FlashCtrlウインドウをwindowにFitさせる
  23.     if( IsWindow( m_flashWnd.GetSafeHwnd() ) ){
  24.         CRect   rcClient;
  25.         GetClientRect( rcClient );
  26.         m_flashWnd.MoveWindow( rcClient );
  27.     }
  28.  
  29.     return TRUE;
  30. }

CFlashWnd.hには次のメンバを定義します。

  1. private:
  2.     CShockwaveFlash     m_flashWnd;

これで、CFlashWndを

  1. ShowWindow( SW_SHOW );

してやればFlashが表示されるはずです。

WindowsアプリケーションとFlashの通信

Flashに文字列を渡すときは、次のように書きます。

  1. m_flashNews.SetVariable( "key", "value" );

Flash側で”key”という変数を用意しておくと、keyの値が”value”となります。Flash側のEnterFrameで”key”を監視しておくと、値がセットされたことがすぐにわかります。

Flashからのイベントを受け取るには、コールバック関数を指定します。

  1. BEGIN_EVENTSINK_MAP(CFlashNewsWnd, CWnd)
  2.     //{{AFX_EVENTSINK_MAP(CFlashNewsWnd)
  3.     ON_EVENT(CFlashNewsWnd, IDC_NEWSWND, 150, OnFSCommandShockwaveflash, VTS_BSTR VTS_BSTR)
  4.     //}}AFX_EVENTSINK_MAP
  5. END_EVENTSINK_MAP()

ここでは、OnFSCommandShockwaveflashという関数にコールバックが飛んでくるようにしています。

  1. void CFlashNewsWnd::OnFSCommandShockwaveflash(LPCTSTR command, LPCTSTR args)
  2. {
  3.     CString com = command;
  4.     CString arg = args;
  5.  
  6.     switch( com ){
  7.     case "key1":
  8.         // key1の時の処理
  9.         break;
  10.    
  11.     case "key2":
  12.         // key1の時の処理
  13.         break;
  14.  
  15.     default:
  16.         break;
  17.     }
  18. }

たぶんこれで動くと思いますが、もし動かなければコメントなどください。

ソフトウェアを開発するときに仕様書を書くべき3つの理由

土曜日, 7月 5th, 2008 Posted in C++, PHP | No Comments »

Joel on Softwere」で、機能仕様書を書く重要性が延々と述べられているので、ちょっとまとめてみます。いきなりコーディング始めていませんか?

プログラムをデザインする

仕様書の最も重要な役割はプログラムをデザインすることだ。
仕様書を書くという行為自体によって-プログラムがどう機能するか詳細に記述するということによって-プログラムを実際にデザインするように強いられるのだ。

ここでいう仕様書とは機能仕様書のことで、内部仕様書(技術仕様書)のことではないです。機能仕様書とは、ユーザの観点から製品がどのように動くかを記述したドキュメントです。ダイアログとかメニューとかの仕様を書くアレです。

機能仕様書を書くことによって、プログラムがどのように動くかを深く考えることになります。これから作ろうとしているソフトウェアの動きを文章で書き、簡単な絵を添付した場合、上司や顧客からダメだしをされてもすぐに書き換えることができます。一方、2週間掛けて書いたプログラムをダメだしされた場合は・・・!?

アジャイル開発におけるテストファーストも、関数をコーディングする前に関数のテストを書くことによって、その関数の入力と出力の組み合わせを細かく考えます。そうすることで、その関数の仕様を詳細に検討することになります。

作ろうとするものがどう動くかをまず考えることが、デザインにつながります。

コミュニケーションにかかる時間を節約できる

開発のテスト期間中に、どんどん報告されるバグを片っ端からかたづけているとき、スピーディーに改修をこなしているアナタの集中力はMAX!(この状態をフロー状態と言います。)

しかし、突然鳴り出す電話。相手は試験チームのアイツ。いつもくだらない質問で電話してくるアイツです。

試験チームのアイツ:
「音楽CDを再生しながらプロパティダイアログを開くと、CDが停止するんですけど、これ不具合ですか?」

はぁ?不具合なわけないじゃん。つか今頃そんな質問すんなYO!

アナタ:
「それは仕様です。」

試験チームのアイツ:
「では仕様書への記載お願いします。」

げ。書いてなかったっけ?

こんな経験たぶん誰にでもあるんじゃないですか?ボクだけですか?

さて、ここからまた改修作業に戻るわけですが、「ピープルウェア」によるとさっきのフロー状態まで戻るのに15分はかかります。あぁ、なんという無駄。

2分間の仕様書への追記をサボったために、電話対応+フロー状態への復帰で20分の貴重な時間が無駄になります。20分の無駄も問題ですが、仕事が中断したことによるイライラで仕事へのモチベーションが下がってしまうことのほうが大問題です。

仕様書を書いておけば、こんなことにはならないのです。

スケジュールが立てられない

製品を開発するには時間がかかり、そしてお金がかかります。スケジュールを立てられるだけの仕様書がないということは、製品を開発するのにどれくらい時間がかかり、どれくらいのお金がかかるか分からないということです。

あなたは値段を見ずにジーンズを買ったりしないだろう。

まとめ

Joel on Softwere」の仕様書を書く理由を自分なりに解釈してますが、ほかにも仕様書を書かないことによってこんな問題があるよってのがあれば教えて欲しいです。仕様書の書き方については「達人プログラマー」が参考になります。これはまたいつかまとめたいと思います。できれば。

参考資料




C++でバイト列をBase64や16進数表示へ変換

金曜日, 6月 27th, 2008 Posted in C++ | 2 Comments »

バイト列をBase64や16進数表示へ変換

C++でバイト列をBase64変換するには、暗号化で使用するCrypt APIが使えます。Crypt APIにCryptBinaryToStringという関数があり、これを使うとBase64変換や16進数のASCII表示が簡単にできます。じつは、自前でBase64変換とか実装した後に気がついたんですけどね。

CryptBinaryToStringを使うには、Crypt32.libをリンクしてwincrypt.hをインクルードします。引数は次のとおりです。

第1引数:const BYTE*:変換元データ
第2引数:DWORD:変換元データのサイズ
第3引数:DWORD:変換形式のフラグ
第4引数:LPTSTR:変換後データ
第5引数:DWORD*:変換後データサイズ

CryptBinaryToStringの第3引数にフラグで変換形式を指定します。フラグは次のとおり。

  1. CRYPT_STRING_BASE64HEADER
  2. CRYPT_STRING_BASE64
  3. CRYPT_STRING_BINARY
  4. CRYPT_STRING_BASE64REQUESTHEADER
  5. CRYPT_STRING_HEX
  6. CRYPT_STRING_HEXASCII
  7. CRYPT_STRING_BASE64X509CRLHEADER
  8. CRYPT_STRING_HEXADDR
  9. CRYPT_STRING_HEXASCIIADDR

Base64文字列のみが欲しい場合は「CRYPT_STRING_BASE64」を指定します。「CRYPT_STRING_BASE64HEADER」や「CRYPT_STRING_BASE64REQUESTHEADER」を指定すると、前後に文字列が付いてきます。16進数で表示する場合は、「CRYPT_STRING_HEX」を指定します。16進数とASCII文字を表示する場合は、「CRYPT_STRING_HEXASCII」。16進数とASCII文字とアドレスを表示する場合は、「CRYPT_STRING_HEXASCIIADDR」。実際に試してみるとわかるのでフラグを変えていろいろやってみてください。

第4引数にNULLを指定すると第5引数に変換後のデータサイズが返ってくるので、メモリを確保してもう一度この関数を呼びます。

表示例

CRYPT_STRING_HEXASCIIADDRを指定するとバイト列は次のような文字列へ変換されます。

  1. 0000    81 29 fd 28 07 38 c6 a1  c8 fe a6 fd ce 5a 79 eb   .).(.8.......Zy.
  2. 0010    bb 8c 3c 75 be 6c 8b 72  d1 14 d3 5e 4d 70 73 2b   ..<u.l.r...^Mps+
  3. 0020    25 5b f0 87 88 bd ec 1e  3b 06 e0 88 6d 23 60 bf   %[......;...m#`.
  4. 0030    a4 9f af 57 aa b9 ca 06                            ...W....

CRYPT_STRING_BASE64を指定するとバイト列は次のような文字列へ変換されます。

  1. gSn9KAc4xqHI/qb9zlp567uMPHW+bIty0RTTXk1wcyslW/CHiL3sHjsG4IhtI2C/pJ+vV6q5ygY=

Base64文字列や16進数表示をバイト列に変換

Base64文字列や16進数表示からバイト列に変換する場合は、CryptStringToBinaryを使います。第5引数まではCryptBinaryToStringと同じです。第3引数にはCryptBinaryToStringのフラグに加えて、次のフラグが指定できます。

  1. CRYPT_STRING_BASE64_ANY
  2. CRYPT_STRING_ANY
  3. CRYPT_STRING_HEX_ANY

第6引数:DWORD*:文字列で実際にBase64文字列や16進数文字列が始まるまでの文字数を返します。NULLも指定できます。
第7引数:DWORD*:実際に変換に使われた形式のフラグが返ります。NULLも指定できます。

第7引数の使い道としては、変換しようとしている文字列が、次のうちどれで変換されたものかわからない場合に、

  1. CRYPT_STRING_HEXADDR
  2. CRYPT_STRING_HEXASCIIADDR
  3. CRYPT_STRING_HEX
  4. CRYPT_STRING_HEXRAW
  5. CRYPT_STRING_HEXASCII

第3引数にCRYPT_STRING_HEX_ANYをしていすると、正しい変換形式が自動で選択されて、第7引数に選択された変換形式が返ってくるという仕組みです。

詳しくは、CryptStringToBinary Function (Windows)を熟読してください。

実装例

では、例として次の4つの変換についてC++のコードを載せておきます。

  • バイト列を16進数のASCII表示に変換する
    1. DWORD dwDst = 0;
    2. LPTSTR pstrDst = NULL;
    3. if( CryptBinaryToString( bSrc, nLen, CRYPT_STRING_HEXASCIIADDR, NULL, &dwDst ) ){
    4.        pstrDst = new TCHAR[ dwDst + 1 ];
    5.        if( CryptBinaryToString( bSrc, nLen, CRYPT_STRING_HEXASCIIADDR, pstrDst, &dwDst ) ){
    6.                //pstrDst:バイト列の16進数表示文字列
    7.        }
    8. }
    9. delete pstrDst;
  • 16進数のASCII表示をバイト列に変換する

    1. DWORD dwDst = 0;
    2. BYTE* pbDist = NULL;
    3. if( CryptStringToBinary( bSrc, nLen, CRYPT_STRING_HEXASCIIADDR, NULL, &dwDst, NULL, NULL ) ){
    4.        pbDist = new BYTE[ dwDst ];
    5.        if( CryptStringToBinary( bSrc, nLen, CRYPT_STRING_HEXASCIIADDR, pbDist, &dwDst, NULL, NULL ) ){
    6.                //pbDist:バイト列
    7.        }
    8. }
    9. delete pbDist;
  • バイト列をBase64文字列に変換する
    1. DWORD dwDst = 0;
    2. LPTSTR pstrDst = NULL;
    3. if( CryptBinaryToString( bSrc, nLen, CRYPT_STRING_BASE64, NULL, &dwDst ) ){
    4.        pstrDst = new TCHAR[ dwDst + 1 ];
    5.        if( CryptBinaryToString( bSrc, nLen, CRYPT_STRING_BASE64, pstrDst, &dwDst ) ){
    6.                //pstrDst:Base64文字列
    7.        }
    8. }
    9. delete pstrDst;
  • Base64文字列をバイト列に変換する
    1. DWORD dwDst = 0;
    2. BYTE* pbDist = NULL;
    3. if( CryptStringToBinary( bSrc, nLen, CRYPT_STRING_BASE64, NULL, &dwDst, NULL, NULL ) ){
    4.        pbDist = new BYTE[ dwDst ];
    5.        if( CryptStringToBinary( bSrc, nLen, CRYPT_STRING_BASE64, pbDist, &dwDst, NULL, NULL ) ){
    6.                //pbDist:バイト列
    7.        }
    8. }
    9. delete pbDist;

C++でUTF-8をShift-JISに変換する

水曜日, 6月 25th, 2008 Posted in C++ | 1 Comment »

前回、Shift-JISをUTF-8に変換する方法を書きました。今回は逆のUTF-8をShift-JISに変換する方法です。

この変換も、

UTF-8→UTF-16→Shift-JIS

の順番で変換する必要があります。

以下が変換のコードです。エラー処理は省略しています。

  1. BOOL ConvUtf8toSJis( BYTE* pSource, BYTE* pDist, int* pSize )
  2. {
  3.    *pSize = 0;
  4.  
  5.    //UTF-8からUTF-16へ変換
  6.    const int nSize = ::MultiByteToWideChar( CP_UTF8, 0, (LPCSTR)pSource, -1, NULL, 0 );
  7.  
  8.    BYTE* buffUtf16 = new BYTE[ nSize * 2 + 2 ];
  9.    ::MultiByteToWideChar( CP_UTF8, 0, (LPCSTR)pSource, -1, (LPWSTR)buffUtf16, nSize );
  10.  
  11.    //UTF-16からShift-JISへ変換
  12.    const int nSizeSJis = ::WideCharToMultiByte( CP_ACP, 0, (LPCWSTR)buffUtf16, -1, NULL, 0, NULL, NULL );
  13.    if( !pDist ){
  14.        *pSize = nSizeSJis;
  15.        delete buffUtf16;
  16.        return TRUE;
  17.    }
  18.  
  19.    BYTE* buffSJis = new BYTE[ nSizeSJis * 2 ];
  20.    ZeroMemory( buffSJis, nSizeSJis * 2 );
  21.    ::WideCharToMultiByte( CP_ACP, 0, (LPCWSTR)buffUtf16, -1, (LPSTR)buffSJis, nSizeSJis, NULL, NULL );
  22.  
  23.    *pSize = lstrlen( (char*)buffSJis );
  24.    memcpy( pDist, buffSJis, *pSize );
  25.  
  26.    delete buffUtf16;
  27.    delete buffSJis;
  28.  
  29.    return TRUE;
  30. }

この関数の使用方法は、まずはじめにpDistにNULLを渡して変換後のサイズを取得します。変換後のサイズでpDistのメモリを確保したあと、もう一度この関数を呼びます。

次のような感じ。

  1. int nSize = 0;
  2. ConvSJisToUtf8( pSource, NULL, &nSize );
  3. BYTE* pDist = new BYTE[ nSize + 1 ];
  4. ZeroMemory( pDist, nSize + 1 );
  5. ConvSJisToUtf8( pSource, pDist, &nSize );

次回はBase64変換について書きます。

C++でShift-JISをUTF-8に変換する

水曜日, 6月 11th, 2008 Posted in C++ | 1 Comment »

Windowsアプリケーションでもネットワークにつながって、サーバアプリケーションと連携することが多くなってきてますねぇ。ネットワークといえば文字コードはUTF-8なイメージがあるワタクシですが、C++ではShift-JISなアプリばっかり作ってきたので、UTF-8に変換するにはどうしたらいいの?ってことでまとめます。

C++でShift-JISをUTF-8に変換するには、

Shift-JIS→UTF-16→UTF-8

の順番で変換する必要があります。

そもそも、UTF-16とUTF-8はどう違うのか。UTF-16は文字に割り当てられる番号(コードポイント)が2バイトで格納されます。一方UTF-8は、0~127のコードポイントは1バイトで格納され、128から上のコードポイントは、2~6バイトで格納されます。0~127のコードポイントでは、ASCIIと同じ文字が定義されているので、英語のテキストを扱うときには、ASCIIとUTF-8でまったく同じになります。

このあたりのことは、「Joel on Software」にわかりやすく解説してあります。

「Joel on Software」には、Unicodeに関する項目を「すべてのソフトウェア
開発者が絶対確実に知っていなければならないUnicodeとキャラクタセットに
関する最低限のこと(言い訳なし!)」として解説してあり、この項目を読むだけでもこの本を買う価値はあると思います。もちろん他の項目もすごくためになります。なかりおすすめ。

実際の変換はコードを見ればわかると思います。エラー処理は省略しています。

  1. BOOL ConvSJistoUtf8( BYTE* pSource, BYTE* pDist, int* pSize )
  2. {
  3.    *pSize = 0;
  4.  
  5.    //ShiftJISからUTF-16へ変換
  6.    const int nSize = ::MultiByteToWideChar( CP_ACP, 0, (LPCSTR)
  7. pSource, -1, NULL, 0 );
  8.  
  9.    BYTE* buffUtf16 = new BYTE[ nSize * 2 + 2 ];
  10.    ::MultiByteToWideChar( CP_ACP, 0, (LPCSTR)pSource, -1, (LPWSTR)
  11. buffUtf16, nSize );
  12.  
  13.    //UTF-16からShift-JISへ変換
  14.    const int nSizeUtf8 = ::WideCharToMultiByte( CP_UTF8, 0, (LPCWSTR)
  15. buffUtf16, -1, NULL, 0, NULL, NULL );
  16.    if( !pDist ){
  17.        *pSize = nSizeUtf8;
  18.        delete buffUtf16;
  19.        return TRUE;
  20.    }
  21.  
  22.    BYTE* buffUtf8 = new BYTE[ nSizeUtf8 * 2 ];
  23.    ZeroMemory( buffUtf8, nSizeUtf8 * 2 );
  24.    ::WideCharToMultiByte( CP_UTF8, 0, (LPCWSTR)buffUtf16, -1, (LPSTR)
  25. buffUtf8, nSizeUtf8, NULL, NULL );
  26.  
  27.    *pSize = lstrlen( (char*)buffUtf8 );
  28.    memcpy( pDist, buffUtf8, *pSize );
  29.  
  30.    delete buffUtf16;
  31.    delete buffUtf8;
  32.  
  33.    return TRUE;
  34. }

この関数の使用方法は、まずはじめにpDistにNULLを渡して変換後のサイズを
取得します。変換後のサイズでpDistのメモリを確保したあと、もう一度この関数を呼びます。

次のような感じ。

  1. int nSize = 0;
  2. ConvSJisToUtf8( pSource, NULL, &nSize );
  3. BYTE* pDist = new BYTE[ nSize + 1 ];
  4. ZeroMemory( pDist, nSize + 1 );
  5. ConvSJisToUtf8( pSource, pDist, &nSize );

次回はUFT-8をShift-JISに変換する方法です。
今回の逆なだけです。

ユニットテストの実践: CppUnitの導入(実装編)

木曜日, 5月 1st, 2008 Posted in C++, CppUnitの導入 | 1 Comment »

前回、CppUnitを導入しました。今回は実装編です。

本来はテストファーストで、CppUnitでテストケースを実装したあとにメインの実装を行うものなんでしょうけど、今回はすでに実装が終わったプロジェクトのユニットテストを行いました。今回作成したテストアプリはGUI版です。

まずはVCでプロジェクトを新規作成します。MFC AppWizard(exe)でダイアログベースで作成します。

[プロジェクト]-[設定]から[プロジェクトの設定]ダイアログを起動し、[C/C++]タブのカテゴリ[コード生成]で使用するランタイム ライブラリで[マルチスレッド(DLL、デバッグ)]を選択します。

同じく[C/C++]タブのカテゴリ[プロセッサ]インクルードファイルのパスに、メインのプロジェクトのパスを入力します。

[リンク]タブのカテゴリ[一般]オブジェクト・ライブラリ モジュールに、設定の対象がDebugの場合、

  1. cppunitd.lib testrunnerd.lib

Releaseの場合、

  1. cppunit.lib testrunner.lib

と入力します。

ここで、メインアプリのプロジェクトをワークスペースに追加しときます。そして[プロジェクト]-[依存関係]で、テストプロジェクトがメインプロジェクトに依存するように設定します。

テストアプリのStdAfx.hに次のincludeを記載します。

  1. #include <cppunit/ui/mfc/TestRunner.h>
  2. #include <cppunit/ui/text/TestRunner.h>
  3. #include <cppunit/extensions/TestFactoryRegistry.h>
  4. #include <cppunit/extensions/HelperMacros.h>
  5. #include <cppunit/CompilerOutputter.h>

C++アプリケーションの効率的なテスト手法(CppUnit編)なんかには、StdAfx.hは削除するとの記載がありますが、ボクの環境では削除しなくても問題ないです。てか削除するとダイアログベースのアプリなんでビルドできませんので。コンソールアプリの場合は必要ないでしょうね。

テストアプリのアプリクラス(CWinAppを継承しているクラス)のInitInstance()を次のように書き換えます。

  1. BOOL CTestApp::InitInstance()
  2. {
  3.     AfxEnableControlContainer();
  4.  
  5. #ifdef _AFXDLL
  6.     Enable3dControls();         // 共有 DLL 内で MFC を使う場合はここをコールしてください。
  7. #else
  8.     Enable3dControlsStatic();   // MFC と静的にリンクする場合はここをコールしてください。
  9. #endif
  10.  
  11.     CPPUNIT_NS::MfcUi::TestRunner runner;
  12.     runner.addTest( CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest() );
  13.     runner.run();
  14.  
  15.     return FALSE;
  16. }

メインプロジェクトのテスト対象となるクラスと対になるテスト用クラスを作成するのが流儀のようです。次のようなクラスを作成します。

  1. class CMainClass;
  2. class CTestClass : public CPPUNIT_NS::TestFixture  
  3. {
  4.     CPPUNIT_TEST_SUITE( CTestClass );
  5.     CPPUNIT_TEST( testFunc1 );
  6.     CPPUNIT_TEST( testFunc2 );
  7.     CPPUNIT_TEST_SUITE_END();
  8.  
  9. public:
  10.     CTestClass();
  11.     virtual ~CTestClass();
  12.  
  13.     void setUp();
  14.     void tearDown();
  15.  
  16.     void testFunc1();
  17.     void testFunc2();
  18.  
  19. private:
  20.     CMainClass* m_pApp;
  21. };

CPPUNIT_TEST_SUITEマクロとCPPUNIT_TEST_SUITE_ENDマクロの間にCPPUNIT_TESTマクロで、テスト対象となるテスト用の関数を登録します。setUp()はテスト用関数が実行される直前に呼ばれ、tearDown()はテスト終わったあとに呼ばれます。たとえばsetUp()でCMainClass* m_pAppをnewして、tearDown()でdeleteするような使い方になります。

では実装の方は、次のような感じ。

  1. CPPUNIT_TEST_SUITE_REGISTRATION( CTestClass );
  1. void CTestClass::setUp()
  2. {
  3.     m_pApp = new CMainClass();
  4. }
  5.  
  6. void CTestClass::tearDown()
  7. {
  8.     delete m_pApp;
  9. }
  10.  
  11. void CTestClass::testFunc1()
  12. {
  13.     CPPUNIT_ASSERT_EQUAL( TRUE, m_pApp->Func1( 0 ) );
  14.     CPPUNIT_ASSERT_EQUAL( FALSE, m_pApp->Func1( -1 ) );
  15. }
  16.  
  17. void CTestClass::testFunc2()
  18. {
  19.     CPPUNIT_ASSERT( m_pApp->Func2( 0 ) );
  20.     CPPUNIT_ASSERT( m_pApp->Func2( -1 ) );
  21. }

CPPUNIT_ASSERT_EQUALは、第一引数と第二引数が等しい場合にテスト成功。違ったらテスト失敗となります。CPPUNIT_ASSERTは、引数がTRUEだったらテスト成功。FALSEだったらテスト失敗となります。ほかにも、

  1. CPPUNIT_ASSERT
  2. CPPUNIT_ASSERT_MESSAGE
  3. CPPUNIT_FAIL
  4. CPPUNIT_ASSERT_EQUAL_MESSAGE
  5. CPPUNIT_ASSERT_DOUBLES_EQUAL

などあります。ここでは詳しくは省略。

さて、ここでビルドするとリンクエラーが発生します。メインプロジェクトのCMainClass::Func1()やCMainClass::Func2()にリンクできないと。ここでしばらく悩みましたが、どうやらメインプロジェクトのCMainClassのCPPファイルを、テストプロジェクトに追加しないといけないようです。なんかこれ気持ち悪いんですけど。しょうがないんですかね。

これでちゃんとビルドが通ります。

実行するとダイアログが起動され、Browseからテストケースが確認できます。通常はALL Testsを選んでおいて、Autorun at startupにチェックを入れておけば、起動と同時にすべてのテストを実行してくれるので楽です。

【関連エントリ】
ユニットテストの実践: CppUnitの導入(インストール編)

ユニットテストの実践: CppUnitの導入(インストール編)

金曜日, 4月 25th, 2008 Posted in C++, CppUnitの導入 | 3 Comments »

恥ずかしながら今までユニットテストツールを使ったことがなかったんですが、soraさんおすすめの、アジャイルプラクティス 達人プログラマに学ぶ現場開発者の習慣を読んだら、いても立ってもいられなくなり、会社の環境にCppUnitを導入しました。

CppUnitとは、C++のユニットテストを自動化してくれるステキなツールです。

導入編

導入に関しては、それほど難しくないです。ボクが今回入れた環境は、Visual C++ 6.0です。手順としては以下の通り。

CppUnitをダウンロード。実は去年の夏くらいにダウンロードしてましたが、当時は導入をあきらめてました。今回はこのときにダウンロードしたCppUnitを使いました。(cppunit-1.12.0.tar.gz)

ダウンロードしたファイルを解凍します。解凍先を\CppUnit\とします。

次のプロジェクトをVC6で開いてビルドします。

  1. \CppUnit\examples\msvc6\CppUnitTestApp\CppUnitTestApp.dsp

\CppUnit\lib以下にいろいろなDLLやらLIBやらが作成されます。ちなみにCppUnitTestAppはCppUnit自体のユニットテストです。実行するとGUIが表示されるのでいろいろいじってみましょう。

VCのメニュー[ツール]-[オプション]からオプションダイアログを開き、[ディレクトリ]タブの表示するディレクトリ[インクルードファイル]に

  1. \CppUnit\include

を、表示するディレクトリ[ライブラリファイル]に、

  1. \CppUnit\lib

を追加します。ここに設定したディレクトリはすべてのプロジェクトに適応されます。プロジェクトごとに設定したい場合は[プロジェクトの設定]ダイアログでやってください。また、次のファイルを\windows\system32の中へコピーしてください。

  1. testrunnerd.dll
  2. testrunner.dll

次回は実践編です。

【関連エントリ】
ユニットテストの実践: CppUnitの導入(実装編)