メンバー関数をスレッド関数に

スレッドを起こす古いプログラムをC++化しようとして、ちょっと躓いてしまいました。備忘録のメモです。

Windowsで、スレッドを起こすAPIは CreateThread、もしくは通常はCRTライブラリの_beginthreadexを利用しますが・・・、これらのAPIに渡す、スレッド関数(スレッドの開始アドレス)の呼び出し規約は、__cdeclもしくは__stdcallです。要はグローバル関数(自由関数)なわけですが、C++化する上で、できるだけグローバル関数を減らしたいと思いました。

プログラム自体をC++化する上で、単純にクラス内に静的な(staticな)メンバを追加してこれをスレッド関数にして、新しいスレッドに渡すパラメータにオブジェクトのインスタンスポインタを渡せば、最小の変更でうまく行きます。

が、もっと簡単に、、オブジェクト・インスタンスのメンバ関数をスレッド関数にしたい場合は、ちょっと工夫がいるようです。
というわけで、上記のようなプロキシ的な静的メンバ関数を設置して、インスタンスメンバー関数をスレッドに渡すようにしたコードを書いてみました。

/*
  SimpleThreading.hpp
  簡単なスレッドクラスです。継承させて使用します。
 
  追記(2010/4/10):致命的な欠陥があるので実際には使わないように。参考程度に見てください。
       メンバー関数ポインタとパラメータを CSimpleThreadingのメンバ変数にいったん保存して
       スレッド関数(ThreadStartProxyから参照している部分がスレッドアウトになってます(笑)
	
  class CMyObject : public CSimpleThreading<CMyObject>
   {
	   ...
   };
*/
#include <windows.h>
#include <process.h>

template<typename T>
class CSimpleThreading
{
  typedef UINT_PTR (T::*MEM_FUN)(LPVOID);

  LPVOID m_pvParameter;
  MEM_FUN m_ThreadStart;

  static UINT_PTR __stdcall ThreadStartProxy(LPVOID pvSimpleThreading)
    {
      CSimpleThreading *pSimpleThreading = (CSimpleThreading*)pvSimpleThreading;

     //修正しました。(10/4/16)
      return ((T*)pSimpleThreading->*(pSimpleThreading->m_ThreadStart))(pSimpleThreading->m_pvParameter);
    }

protected:
  //引数:メンバ関数ポインタ、スレッド処理に渡すパラメータ、サスペンド
  HANDLE BeginThreadEx(MEM_FUN ThreadStart,LPVOID pvParam,BOOL bSuspended = FALSE)
    {
      if(!ThreadStart)
        return NULL;

      m_pvParameter = pvParam;
      m_ThreadStart = ThreadStart;

      return (HANDLE)_beginthreadex(NULL,
                                    0,
                                    CSimpleThreading::ThreadStartProxy,
                                    (LPVOID)this,
                                    bSuspended ? CREATE_SUSPENDED : 0,
                                    NULL);
    }
};

このコードの要旨は、静的メンバ関数を_beginthreadex()APIに渡し、その中で実際にスレッドに処理させたいメンバー関数をコールする、というごく一般的なものです。
CSimpleThread::BeginThreadExで、メンバ関数ポインタとパラメータをプロパティとして保存しています。あとは、静的メンバー関数内で、そのメンバー関数ポインタをパラメータを引数としてコールしています。
メンバー関数ポインタをコールするには、どのインスタンスのメンバー関数をコールするのか、といった情報が必要なので、必ずpObject->*や、Object.*といったものが必要です。メンバー関数内でコールする場合も、this->*は省略できないようです。

実際には、上記クラスを継承させて使用します。

/*
  ThreadTest.cpp
  使用例
*/
#include "SimpleThreading.hpp"

class CMain : public CSimpleThreading<CMain>
{
public:
  
  UINT_PTR ThreadStart(LPVOID pvParam)
    {
      //スレッド処理...
      return 0;
    }
  
	DWORD Run()
    {
      DWORD dwWait = 0;
      HANDLE hThread = BeginThreadEx(&CMain::ThreadStart,NULL);

      if(hThread == NULL)
        return 0;

      return WaitForSingleObject(hThread,INFINITE);
    }
};

実際にはプロキシ的に静的メンバ関数をかましているので、メンバー関数をスレッド関数にしているわけではありませんが、ちょこっとしたプログラムにはこれで十分です(^^

エアロのガラス効果

Windows Vistaに全く興味がなかったわけでもないですが、メインのPCをWindows7にしたことだし・・・ということで、ほとんど何の知識もないWindows Aeroですが、ちょっと勉強がてらいじってみました。

Aeroの最大の特徴は、やはり、ウィンドウフレームにも適用されているガラス効果でしょう。各ウィンドウとガラス効果などのAero効果をデスクトップ上のウィンドウとして表示できるように合成・管理しているのが、DWM(Desktop Window Manager)ということだそうです。

クライアント向けのWindowsで、NT4からXPまでのウィンドウマネージャなどの大部分はカーネルモードで動作していたと思います。これは純粋にパフォーマンス向上のためと思いますが、Vista以降ではドライバモデルが変更され、iその一部がユーザーモードで動作するようになりました。本来のNTの思想に戻ったといえます。ビデオカードをはじめとするハードウェア自体の性能向上のおかげでしょうね・・・。

このDWM、タスクマネージャで確認するとVistaと7では、明らかにメモリ使用量が違っています。詳しくは知りませんが、なんらかの改良が施されているんだと思います。

で、DWMに関連するAPIでネットを検索しても、SDKプログラマの興味がないのか、関心がないのか、C/C++ WindowsSDKで利用できるまとまった情報(コード)が少ないです。使ってみました、という記事はいっぱい出るんですが、詳細な記事が見つからず。MSDNのAPI説明見てもいまいち分からないし・・・。

まぁ、だけど利用できるAPIは限られていて、DWMに関連するAPIで最も簡単に利用できるのが、DwmExtendFrameIntoClientAreaというAPIです。
これは、ウィンドウフレームに適用されているガラス効果をクライアントエリアに拡張するAPIなんですが、使い方は至って簡単です。ガラス効果をどれだけクライアント側に食い込ませるかをMARGINS構造体にセットして、それをウィンドウハンドルとともに渡せばOKです。

//{左,右,上,下}という風になります。
//下記例だと、上部20ピクセルが拡張されます。
MARGINS margins = {0,0,20,0};
DwmExtendFrameIntoClientArea(hWnd,&margins);

//ちなみに、以下のように-1をセットすると、クライアント領域すべてがガラス効果の影響受けます。
MARGINS margins = {-1};

※dwmapi.hのインクルードと、dwmapi.libのリンクが必要です。

MARGINS margins={-1};としたときのガラス効果

このようにAPI自体はカンタンに利用できる反面、C/C++とWindowsSDKのみで組んで行くには相当めんどくさい処理が必要になります。

  1. 単純にボタンやエディットなどのコントロールを配置すると、悲惨なことになる。
  2. ガラス効果上に黒い文字を描画すると、黒い文字が透明になっちゃう(^^;;;
  3. ガラス効果にしたクライアント領域部分は、別途常に黒く塗りつぶさないといけない。
    (要するに、拡張した部分の背景色の再描画処理を別途記述しなければならないかと・・・)
  4. DWMのデスクトップコンポジションが有効か無効かを判別してそれぞれに対する処理を行う必要がある。

などなど・・・

ガラス効果上に配置した黒く塗りつぶしたGDIアイテムは、全部透明になってしまいます(テキストとかメニューの文字とか)。要するに、色値が0x00000000(要するに黒色)の部分がガラス効果として透明なるっちゅーわけだから、当たり前と言えば当たり前ですが・・・。COLORREF型の先頭1バイトを255にすればいいのか?よくわかんないです。
これの解決方法としては、コントロールをオーナードローで描画するか、GDI+ライブラリを使用する、という選択肢がMSDNで紹介されていました。が、子コントロールの色を全部修正するなんて、メンドーだし、他の解決方法があるのかもしれません。
そのうちガラス効果関連処理をラップしたC/C++のクラスライブラリがそのうち出るかもしれないし、でないかもしれないし、すでに出ているのかも・・・。C#の情報は結構あるんですけどね・・・。

ダイアログをリソースファイルで定義してDialogBox() APIで表示させているようなプログラムの場合、最低限以下のようなウィンドウメッセージを捕まえて背景を別途処理しないといけない。

・WM_INITDIALOG
デスクトップコンポジションが有効なら、DwmExtendFrameIntoClientAreaを実行。

・WM_ERASEBKGND
ガラス効果を適用しても、ダイアログの背景用描画ブラシで上書きされてしまうので、ガラス効果を拡張した部分は黒色で、それ以外はダイアログの背景色で背景を塗りつぶす。

・WM_DWMCOMPOSITIONCHANGED
デスクトップコンポジションが有効になったり、無効になったりすると、飛んでくるので、これを捕まえて適切な処理を行う。
注意しなければならないのは、デスクトップコンポジションが有効になったらその都度、DwmExtendFrameIntoClientAreaをコールする必要があること。

INT_PTR CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
  //上部20ピクセルを拡張
  MARGINS margins = {0,0,20,0};
  RECT rect = {0};
  BOOL fDwmEnabled = FALSE;
  HBRUSH hBrush = NULL;
  LONG oldBottom = 0;

  switch(message)
  {
  case WM_INITDIALOG:
    DwmIsCompositionEnabled(&fDwmEnabled);
    if(fDwmEnabled)
      DwmExtendFrameIntoClientArea(hDlg,&margins);
    return (INT_PTR)TRUE;

  case WM_ERASEBKGND:
    DwmIsCompositionEnabled(&fDwmEnabled);
    GetClipBox((HDC)wParam,&rect);

    //デスクトップコンポジションの有効・無効によって背景に使用するブラシを決定
    hBrush = fDwmEnabled ? (HBRUSH)GetStockObject(BLACK_BRUSH) : GetSysColorBrush(COLOR_BTNFACE);

    //クライアント領域の上部20ピクセル(ガラス効果)の背景を描画
    oldBottom = rect.bottom;
    rect.bottom = rect.top + 20;
    FillRect((HDC)wParam,&rect,hBrush);
    
    //クライアント領域の残りの下部の背景を描画
    hBrush = GetSysColorBrush(COLOR_BTNFACE);
    rect.top += 20;
    rect.bottom = oldBottom;
    FillRect((HDC)wParam,&rect,hBrush);

    return (INT_PTR)TRUE;

  case WM_DWMCOMPOSITIONCHANGED:
    DwmIsCompositionEnabled(&fDwmEnabled);
    if(fDwmEnabled)
      DwmExtendFrameIntoClientArea(hDlg,&margins);
    return (INT_PTR)0;

  case WM_COMMAND:
      //ダイアログ上のコントロールへの応答処理など
      if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
          EndDialog(hDlg, LOWORD(wParam));
          return (INT_PTR)TRUE;
        }
      break;
  }
  return (INT_PTR)FALSE;
}

ということで、なんとかかんとか、思ったように表示できました・・・。

ガラス効果2

なんか、久しぶりにGUIなAPI触ると、何がどうだったか・・・忘却の彼方へ・・・(ーー;

64bit Project with Visual C++ 2008 Express Edition SP1

無事、Windows7 のインストールと環境移行も完全に済み、ようやく64bitデビューできました(^^;;;

64bit版だからといって、特に変わったこともなく、フツーに使えてます。ただ、メモリ食いますね・・・。32bit版だと、タスクマネージャで確認すると起動時で600~800MBぐらいのメモリ消費。64bit版だと、この倍の1.1~1.3GBぐらいになります。まぁ・・・64bitなんだからしょうがないね・・・と自分で自分を納得させています(笑)

で、次は、ツール類のビルド環境です。自作のツールがチョコチョコとあるので、これも一気に64bitネイティブバイナリにしたい!っていうのは当然の欲求じゃないでしょうか(^^;;;

しかしながら・・・無料バージョンのVisual C++ 2008 Express Edtionでは、64bitの開発はサポート外になっています。別途、Windows SDK (Windows7,Server2008,Server2003,XPに対応したv7バージョン)を追加インストールすると、コマンドラインからの64ビット開発はできるものの、やはりVisual StudioのIDEで楽をしたいというものです。が、実際、IDEからは64bitのプロジェクトは構成できません。これは、Standardエディションを買えと、いうことですかね・・・。まぁ、そりゃそうだ。

でも、実は・・・サポート外、というだけで、少しの修正で64bitのプロジェクトが作成できるようです。

Visual C++ 2008 Express Edition And 64-Bit Targets
http://jenshuebel.wordpress.com/2009/02/12/visual-c-2008-express-edition-and-64-bit-targets/

やはりすでにやられている方がいますね。上記アドレスから、VCE64BIT.zip をダウンロードして、バッチファイルを実行すると64bitを構成することが可能になります。ありがたい。。。
(WindowsSDKインストーラのバグに対応した、VCE64BIT_WIN7SDK.zip ってファイルに更新されていました。VCE64BIT.zipをダウンロードして実行しても適用されないようです。ご注意を。 追記:2010/3/27)

ただ、上記アドレスの内容を読むと、おそらくSP1以前の内容じゃないかと・・・。僕の環境とはちょっと違っていたので、ダウンロードしたファイルのバッチファイルを修正して使用させてもらいました。

具体的には、VCProjectAMD64Platform.dllとVCProjectIA64Platform.dllがC:Program Files (x86)Microsoft Visual Studio 9.0VCvcpackagesディレクトリにコピーされないようにしました。僕の環境では、すでに上記二つファイルは、32bit版のものがインストールされていたので・・・。

上記内容をよく読むと、VC++2008expressって、64bit Windows上でも64bitのコンパイラではなく、32bitのクロスコンパイラが使われるようですね・・・。

次期バージョンのVisual Studio 2010では、64bitに完全対応してくれるのかな???

ということで?、無事、IDEから64bit プロジェクトが構成されました(^^)

VC++ 2008 Express Edition

補足

新たにプロジェクトを作成しても、デフォルトではソリューションプラットフォームにx64(or Itanium)の選択肢は表示されません。
まぁ、当たり前だと思いますが・・・。プラットフォームにx64 or Itanium を追加するには、一手間かける必要があります。

構成マネージャのダイアログを表示させ、アクティブソリューションプラットフォームから”新規作成”を選択すると、Itaniumとx64が現れますので、x64を追加する必要があります。

新規作成

それと、当然ですが、Windows SDK v7をインストールする必要があります。言わずもがなですけど・・・。

モニターの電源を切れ

今日、iPod Touchの車載用FMトランスミッター兼充電器買ってきました。エンジンの始動とともに音楽が始まるところは便利です(^^

バッファローコクヨ・FMトランスミッター

雑音(ノイズ)も入るのかな~、と思ってましたが、そんなこともなく、なかなかです。明日のドライブに重宝しそうです(^^

で、本題です。

続きを読む

SQLite3

昨日、深夜にブラタモリの再放送やっててついつい観てしまいました。去年だったか一回だけ放送があって、帯でやってくれないかな~と期待してたんですが・・・10月から始まるようですね。「タモリ倶楽部」のスピンオフ的な番組のような気もしないでもないですが・・・さすがにゲストと番組のノリはNHKですね(^^;;;

それは、さておき・・・。

アプリケーションに組み込むタイプの軽量データベース・ライブラリ、SQLite3を使える環境をセットアップしようと思っていろいろファイルをダウンロードしました。

  • SQLite3 本体(コマンドライン版/DLL版/ソース)
  • System.Data.SQLite(C#から利用できるように)
  • GUIなDB管理マネージャ

適当なフォルダに突っ込んでパスを通してやれば使えるようになります。

ソースから静的リンクライブラリも一応作成して環境構築終了です。

で、ついでに、WSH( とくにJScript)で使用できるようにしたいな、と思い検索してみたんですが・・・探し方がバカなのか、見つからない。perlやpython,php,といったWEBアプリ系のスクリプトの記事はアホほどヒットするんだけど、JscriptやVBScriptなどから(要はCOMオブジェクトとして)使えるラッパーCOMサーバーのようなものをを見つけることができませんでした。COM自体もう時代遅れのトピックになりつつあって、多分に「イマサラ」感があるので需要が無いのかもしれませんね。JScriptはOS(正確にはIE?)に付属しているのでお手軽度的に言えばperlやphpやPythonやRubyや・・・・に勝っているとは思うんですけどね・・・。

・・・まぁ、データベースファイルをオープンして、SQLクエリを実行して、結果を得る、ということがなんとなくできれば・・・データベースファイルの管理はGUIで他のソフトに任せればいいんだし・・・と割り切って、昼休みの時間に作ってみようかなと。

C固有の型とJScriptで使用するVARIANT型の変換とか、SQLite3で定義されている構造体なんかをJscriptでどう表現すればいいのか? といった諸々を考えないといけないんですが・・・まぁなんとかなるでしょう。休み時間にコツコツと作ってみます(^^;;;