割とたいそうな題名を付けたつもりです。でも、そんな大それたことを言うつもりはないです。

先週土曜の某問い詰める会でも少し話したのですが、現行のC++でこういう風にガベージコレクション(GC)を使うことを試してみたいと前々から考えています。

void* operator new(std::size_t x)
{
    // 面倒なのでset_new_handler非対応
    if (void* p = GC_malloc(x)) // Boehm GCなど
    {
        return p;
    }
    else
    {
        throw std::bad_alloc();
    }
}

void operator delete(void* p)
{
} // 何もしない

// operator new[]とoperator delete[]も同様に定義する

メモリ管理だけGCを用い、デストラクタなどそれ以外のセマンティクスは今までの通りです。今までのC++コードがそのまま使える(導入が容易)という特長の一方、shared_ptrなどGCに頼るなら要らないはずのコードがそのままオーバーヘッドになってしまう(メモリ以外のリソース管理がある場合を除く)ことが大きな欠点になると考えています。

もちろん保守的GCしか使えないと思います。この案においては、世代別GCなどオブジェクトの移動を伴うGCの利用を考えていません。

そのうち、適当なプログラムで試してみたいところです。

今日はこんな話です。

  1. boost::in_placeを使ってみようとした。
  2. エラーになる例に遭遇。
  3. Boost側を直せばいい(そのためには、こんなの誰が知っているんだという::new構文の出番)

この前、Gdiplusを使っていたプログラムでちょうどin_placeとoptinalの組み合わせが使えそうだと気付きました。ちょうど信仰「ワンフェーズコンストラクティズム」Togetter - まとめ「N3059」が世に公表されるなどという時流だったわけです。

ところが、ダメでした。謎のエラーになるのです。そのときは原因追及を諦めてboost::scoped_ptrで我慢しました。こんなコードで同じエラーを見られます。

#include <new>
#include <memory>
#include <boost/optional.hpp>
#include <boost/utility/in_place_factory.hpp>
#include <windows.h>
#include <gdiplus.h>

#if 1
// wchar_t constとfloatを引数に取るコンストラクタを持っている。
using Gdiplus::Font;
#else
struct Font
{
	Font(wchar_t const*, float) {}
};
#endif

int main()
{
	std::unique_ptr<Font> pf;
	boost::optional<Font> of;
	pf.reset((new Font(L"Arial", 16.f)));
	of = boost::in_place(L"Arial", 16.f);
}

余談ですが、この引数で呼ばれるGdiplus::Fontコンストラクタにはデフォルト引数があるので、実際の引数の個数はもっと多いです。

さて#ifの条件の値を0にする、すなわち自作Fontくらすにするとエラー無くin_placeが使用可能です。なぜ、この自作Fontクラスはin_placeが利用可能で、Gdiplus::Fontはだめだったのでしょうか?

その答えはnew演算子の多重定義です。

Gdiplus::Fontの基底クラスを辿ると、GdiplusBaseクラスが親にあります。

class GdiplusBase
{
public:
    void* (operator new)(size_t in_size)
    {
       return DllExports::GdipAlloc(in_size);
    }
// ほか色々省略
};

これがboost::in_place内部のplacement newの邪魔をしてしまうのです。in_place内部では、インスタンスの作成のために<new>にあるnewの多重定義の1つを呼びます。Boost 1.43.0ではboost/utility/in_place_factory.hppの50行目からその処理があります。

template<class T>
void* apply(void* address
    BOOST_APPEND_EXPLICIT_TEMPLATE_TYPE(T)) const
{
  return new(address) T( BOOST_PP_ENUM_PARAMS(N, m_a) );
}

(BOOST_PP_ENUM_PARAMSはここでは無視しましょう)このnewは<new>で定義されている先ほど紹介した多重定義の1つを呼ぶことを意図しているはずです。
にも関わらず、GdiplusBaseがメンバにoperator newを持っているため、Gdiplusとその派生クラスにおいてはこのapply内のnewについてもクラス内からの捜索を始めてしまい、
<new>にあるin_placeが用いるつもりだった先ほど紹介したグローバルなoperator newが見つかりません。このため、引数の適合するoperator newが存在しないことを原因とするエラーが生成されるのです。

解決策はapplyに修正を加えることです(GdiplusBaseをいじる方法もありますが割愛します)。

return ::new(address) T( BOOST_PP_ENUM_PARAMS(N, m_a) );

たったこれだけです。これでGdiplus::Fontでもなんでも大丈夫です。

C++標準には、newやdelete演算子に::を前置する構文があります。これは、グローバル名前空間にあるoperator newやdeleteを呼ぶ意味になります。普段使う::なしのnewやdeleteだと、newするクラスを起点として名前探索が行われます。なお、hoge::newのような任意の名前空間を指定する構文はありません。

あとは、こう修正してくれと働きかけるだけです。……、すみませんまだやっていません。

Visual Studioにも、マクロが搭載されているのは当然というべきでしょう。MS Officeでも、ほぼすべてと言ってよいほどの操作がマクロで実行できたのですから、ましてやそれ以上にプログラマが扱うソフトウェアたるVisual Studioでできないわけがありません。というわけで、ちょっと試してみました。それにしてもGoogleの検索しても日本語の情報が(MSDNライブラリを除いて)まったくといって良いほど見当たらないのが意外です。Excelなんか山ほどあるというのにです。

とりあえず、何かやろうと思い、ビルドする度にTwitterへ投稿を一言行うという、読む側からすればはた迷惑そうなものを作りました。迷惑そうと言っても、気に入らなければスルーもしくはリムーブしてくださいというのがTwitterというものですし、問題ないでしょうという考えで作りました。

(more…)

気が付いたら1週間経ってしまいました。今日は後半、画面表示部分です。ここを綺麗に作ってあれば、コメント取得部分を差し替えるだけで済んだのでしょうが、こっちも残念な品質です。ほぼ1から作り直したいです。元のコードはTVTest 0.6.4のVMR9 Renderlessモードでの描画部分です。ここで取り上げているコードは、改造後のDirectShowFilter/VMR9Renderless.cppのCVMR9Allocator::PresentHelper関数(442行目以降)より一部抜粋となります。

(more…)

ニコニコ実況SDKがリリースされたそうです。

実は、以前にニコニコ実況のコメントを取得して表示するプログラムを書いたのですが、これでおそらくお蔵入りになると思います。SDKをダウンロードしておらず、中身は詳しく知りませんがおそらくコメントの取得も可能でしょう。そのため、せめてここに記録を残しておこうと思います。

さかのぼること3–4ヶ月前、高専カンファ in 長野へとりあえず発表申込みしていたところ、ニコニコ実況が発表されました。とうとう出てきたかという思いましたが、残念な点はコメント表示とテレビ映像の表示は別ウィンドウ・別アプリケーションという仕様、これを一体化させたものを作って発表に持ち込もうと考えました。改造元はTVTestです。当時の最新版の0.6.4を使っていたと思います。


ニコニコ実況でコメントを取得するには次の手順が必要でした。

  1. ログイン
  2. コメントサーバの情報を取得
  3. コメントを取得

ググっていて、どこかでニコ生と同じという話を見たので、主にニコ生についてググってそれに従いました。
異なる点は、動画ID(ニコニコ動画で言うところのsmXXXX)に相当するところです。


以下、コードをぺたぺた貼っていきます。2週間ほどの突貫工事で作ったため非常に汚い残念なソースコードになっています。それをそのまま載せていてごめんなさい。

CCritSec MsgQueueLock;
std::queue<std::wstring> MsgQueue;

DWORD WINAPI CommentReceivingThreadEntry(void*)
{
  HINTERNET hinet = InternetOpen(TEXT("NicoChanJikkyo"),
    INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
  if (!hinet)
    return 1;
  tstring s = Login(hinet);
  CommentServerInfo info = GetCommentServerInfo(hinet, s);
  InternetCloseHandle(hinet);

MsgQueueは取得したコメントを放り込むキュー(描画するスレッドから読み出す)、MsgQueueLockはその排他制御用のクリティカルセクションです。CCritSecはDirectShowのBaseClassesライブラリで定義されていたと思います。今だったら、Intel TBBでも使って排他制御を別途行う必要のないスレッドセーフなキューを使うでしょう。

このCommentReceivingThreadEntry関数は、WinMain関数でCreateThreadされることで実行されます。ここに載せた部分はログイン(Login関数)して、コメントサーバの情報を取得(GetCommentServerInfo関数)するところです。それぞれの関数の詳細はこの後に示します。ところで、User Agentの指定のNicoChanJikkyo、仮称です。それにしてもInternetCloseHandleで手動で閉じているのがまず信じられないです。

この関数の続きを載せる前に、LoginとGetCommentServerInfoの2つの関数の中身を載せていきます。

tstring Login(HINTERNET hinet)
{
  typedef boost::xpressive::basic_regex<PCTSTR> tcregex;
  typedef boost::xpressive::match_results<PCTSTR> tcmatch;
  HINTERNET hinConnect = InternetConnect(hinet, TEXT("secure.nicovideo.jp"),
    INTERNET_DEFAULT_HTTPS_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
  static PCTSTR acceptType[] = {TEXT("*/*"), NULL};
  HINTERNET hinReq = HttpOpenRequest(hinConnect, TEXT("POST"),
    TEXT("secure/login?site=niconico"), NULL, NULL, acceptType,
    INTERNET_FLAG_SECURE | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_AUTO_REDIRECT, 0);

  static const TCHAR ContentType[] =
    TEXT("Content-Type: application/x-www-form-urlencoded");
  BOOL ret = HttpAddRequestHeaders(hinReq, ContentType, ARRAYSIZE(ContentType) - 1,
    HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE);

  string sendData = GetSendData("ユーザ名", "パスワード");
  tstring contentLength = boost::str(boost::basic_format<TCHAR>(
    TEXT("Content-Length: %1%")) % sendData.length());
  ret = HttpAddRequestHeaders(hinReq, contentLength.c_str(), contentLength.length(),
    HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE);
  ret = HttpSendRequest(hinReq, NULL, 0, &sendData[0], sendData.length());

  TCHAR buf[4096];
  DWORD index = 0;
  static const tcregex r = tcregex::compile(TEXT("user_session=(user_session\w+);.*"));
  do
  {
    DWORD bufSize = ARRAYSIZE(buf);
    ret = HttpQueryInfo(hinReq,
      HTTP_QUERY_SET_COOKIE, buf, &bufSize, &index);
    const TCHAR userSession[] = TEXT("user_session");
    tcmatch m;
    if (regex_match(buf, buf + bufSize, m, r))
    {
      return m.str(1);
    }
  } while (index != ERROR_HTTP_HEADER_NOT_FOUND && ret != FALSE);
  return tstring();
}

何よりも戻り値を殆ど無視しているのが大変いけないコードとなっています。それはさておき、ログインするにはhttps://secure.nicovideo.jp/secure/login?site=niconicoに「mail=メールアドレス&password=パスワード」という文字列をPOSTします。そして、レスポンス中のCookieからuser_sessionを探し出します。その値がログインの証であり、この関数の戻り値です。user_sessionの抽出も本当はSpiritで綺麗にやりたいなあと思いつつ、Xpressive(しかも静的ですらない)で適当にやっつけています。ここではやっていませんが、user_sessionの有効期限も検出して保存する仕様にするのがよいと思います。

CommentServerInfo GetCommentServerInfo(HINTERNET hinet, tstring const& session)
{
  using namespace boost::xpressive;
  tstring cookie = TEXT("Cookie: ") + session;
  HINTERNET hinetFile = InternetOpenUrl(hinet,
    TEXT("http://jk.nicovideo.jp/api/getflv?v=jk1"), cookie.c_str(),
    boost::numeric_cast(cookie.length()), INTERNET_FLAG_NO_COOKIES, 0);
  char buf[16384];
  DWORD read;
  BOOL ret = InternetReadFile(hinetFile, buf, sizeof buf, &read);
  CommentServerInfo info;
  cmatch m;
  static const cregex messageServerExp = cregex::compile(”ms=((\w|\.)+)”);
  if (regex_search(buf, buf + read, m, messageServerExp))
  {
    info.MessageServer = m.str(1);
  }
  static const cregex messageServerPortExp = cregex::compile(”ms_port=(\d*)”);
  if (regex_search(buf, buf + read, m, messageServerPortExp))
  {
    info.MessageServerPort = m.str(1);
  }
  static const cregex threadIdExp = cregex::compile(”thread_id=(\d*)”);
  if (regex_search(buf, buf + read, m, threadIdExp))
  {
    info.ThreadId = m.str(1);
  }
  InternetCloseHandle(hinetFile);
  return info;
}

http://jk.nicovideo.jp/api/getflv?v=jk1にアクセスするとNHK総合についての情報が返ってきます。最後の数字はチャンネル番号に対応して、jk1, jk2, jk4–jk9があります。得られる情報のうち、コメントサーバへの接続に必要なサーバ名・ポート番号・スレッドIDの3つだけさくっと探してこの関数の戻り値としています(ここもSpirit使いたかった……)。

では、CommentReceivingThreadEntry(void*)の続きです。

using namespace boost::asio;
using ip::tcp;
io_service ios;

tcp::resolver resolver(ios);
tcp::resolver::query query(
  info.MessageServer.c_str(), info.MessageServerPort.c_str());
boost::system::error_code error = error::host_not_found;
tcp::socket socket(ios);
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
tcp::resolver::iterator end;
while (endpoint_iterator != end)
{
  socket.close();
  socket.connect(*endpoint_iterator, error);
  if (!error)
    break;
  endpoint_iterator++;
}
if (error)
  return 1;

int lastComment = -10;

string req = boost::str(boost::format("<thread res_from=\"%1%\" version=\"20061206\" thread=\"%2%\" />") % lastComment % info.ThreadId);
socket.send(const_buffers_1((const void*)req.c_str(), req.length() + 1));

std::stringstream tmp;
streambuf buf;
for (;;)
{
  tmp.str("");
  std::size_t read;
  read = read_until(socket, buf, '', error);
  if (error)
    break;
  std::istream is(&buf);
  for (int i = 0; i < read; ++i)
  {
    char c;
    is.get(c);
    if (c == '')
      break;
    tmp.put(c);
  }
  OutputDebugStringA(tmp.str().c_str());
  OutputDebugStringA("rn");
  tmp.seekg(0, std::ios_base::beg);
  using boost::property_tree::ptree;
  ptree pt;
  read_xml(tmp, pt);
  if (boost::optional<string> msg = pt.get_optional<std::string>("chat"))
  {
    std::vector<wchar_t> buf(msg->size());
    int len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
      msg->data(), msg->size(), &buf[0], buf.size());
    if (len > 0)
    {
      std::wstring wmsg(&buf[0], len);
      CAutoLock lock(&MsgQueueLock);
      MsgQueue.push(wmsg);
    }
  }
}

急にBoost.Asioになりました。コメントサーバとのやりとりにHTTPを使わずTCP/IP上で直接XMLのやり取りとなるためです。さっき取得したサーバ・ポート番号へ接続して、変数reqのようなXMLを送りつけるとコメントが取得できるようになります。lastComment = -10で最新10件をいますぐ取得する意味になりますが、ソケットを切断しない限り、放っておけば新しいコメントも次から次へとやってきます。ここら辺もニコ生と同じだと思います。受信する個々のコメントの情報は、ナル文字(’\0′)区切りのXMLになっています。それをBoost.PropertyTreeに放り込んでコメント本文(chat要素)を取得(get_optinalメンバ関数)。それをUTF-8からUTF-16LEへ変換し、MsgQueueに送り込んでいます。最初に書いたように、これは画面描画を担当する別のスレッドで読み取られます。それは次の記事に分けます。

TVTestはGPLを採用していますので、次の記事あたりで差分まとめてzipにします。


2010年3月4日追記: コメントサーバへの接続後、reqのようなXMLを送信すると書いてありますが、コメントサーバとのやり取りは受信データ同様、送信データでもナル文字を最後に付けることが必要です。上記ソースコードではそのことが明確ではないと思い、ここに追記します。あのコードでは、c_str()がナル文字終端の文字配列(の先頭要素を指すポインタ)を指すことを利用し、そのナル文字の部分まで含めて送信するようになっています。const_buffers_1((const void*)req.c_str(), req.length() + 1)ではそのために+1しているわけです。こういうことはきちんとソース中にコメントとして残しましょう( to 自分)。

元ネタはクロージャによるクラスの代替 - Inside Closure - nihmaの日記です。それC++でもできるよ!というわけでやってみました。Visual C++ 2010 β2で試しました。

#include <memory>
#include <functional>
#include <vector>
#include <iostream>

template<typename T>
struct Stack
{
    std::function<void (T)> push;
    std::function<T ()> pop;

    static Stack create()
    {
        auto arr = std::make_shared<std::vector<T>>();
        Stack s;
        s.push = [arr](T n) {
            arr->push_back(std::move(n));
        };
        s.pop = [arr]() -> T {
            auto ret = std::move(arr->back());
            arr->pop_back();
            return ret;
        };
        return s;
    }
};

int main()
{
    using std::cout;
    using std::endl;
    auto a = Stack<int>::create();
    auto b = Stack<int>::create();

    a.push(0); b.push(5);
    a.push(1); b.push(6);
    a.push(2); b.push(7);
    a.push(3); b.push(8);
    a.push(4); b.push(9);

    cout << "a:" << a.pop() << " b:" << b.pop() << endl; // a:4 b:9
    cout << "a:" << a.pop() << " b:" << b.pop() << endl; // a:3 b:8
    cout << "a:" << a.pop() << " b:" << b.pop() << endl; // a:2 b:7
    cout << "a:" << a.pop() << " b:" << b.pop() << endl; // a:1 b:6
    cout << "a:" << a.pop() << " b:" << b.pop() << endl; // a:0 b:5
}

ちなみに、ABだとラムダ(無名関数)がないのが厳しいですが、デリゲートがあるのでやはりやってやれないことはないという結論になります。

C++という言葉に惹かれてきた方へごめんなさい。今日はVisual C++ 2010附属のAsynchronous Agent Libraryです。これの紹介を書きたいなあと思いつつもいいサンプルが思い浮かばないと放置していたのですが、今は並列とか並行プログラミングとかがはやりなので、ウェブをあさればネタには困らないのでした。

というわけで、Go言語 (Go lang)の並列プログラミングは超かんたん。 - 医者を志す妻を応援する夫の日記の中から、Goなんて1ミリも知りませんが雰囲気でAsynchronous Agent Libraryを使って書き写してみました。

まずはHello, worldっぽいのです。

package main

var ch = make(chan string)

func g(str string) {
      println(str);
      ch <- "printed";
}

func main() {
      go g("hello, gorutine!");
      <- ch;
}

さっそくVC++で書き直します。

#include <agents.h>
#include <string>
#include <iostream>
#include <boost/thread.hpp>

Concurrency::unbounded_buffer<std::string> ch;

void g(std::string const& str)
{
	std::cout << str << std::endl;
	Concurrency::asend(ch, std::string("printed"));
}

int main()
{
	boost::thread t(g, "hello, Agent library!");
	Concurrency::receive(ch);
}

実行すると次のような出力が得られます。

hello, Agent library!

スレッド作成にBoostを使ってごめんなさい。std::threadを持っていないVC++ 2010がいけないのです。もっとも、Asynchronous Agent Libraryにはスレッド生成の仕組みとしてagentクラスがあります。ただ、これを使うには派生してクラスを作ることになり、行数が膨らむので使いませんでした。今回はスレッドを作る以上の機能は使いませんし。

チャネルというやつ(変数chのやつ)に相当するのはたぶんunbounded_bufferだろうと思いました。unbounded_bufferはキューです。値が来たら値を蓄え、取り出す要求が来たら値を放出します。asendは値を送り、receiveで値を受け取ります。もちろん、中はスレッド安全なので、ロックなどは考えなくて平気です。

次のコードへ行きましょう。素数をひたすら表示するというコードだそうです。

package main

// Send the sequence 2, 3, 4, ... to channel 'ch'.
func Generate(ch chan<- int) {
    for i := 2; ; i++ {
        ch <- i  // Send 'i' to channel 'ch'.
    }
}

// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func Filter(in <-chan int, out chan<- int, prime int) {
    for {
        i := <-in;  // Receive value of new variable 'i' from 'in'.
        if i % prime != 0 {
            out <- i  // Send 'i' to channel 'out'.
        }
    }
}

// The prime sieve: Daisy-chain Filter processes together.
func Sieve() {
    ch := make(chan int);  // Create a new channel.
    go Generate(ch);  // Start Generate() as a subprocess.
    for {
        prime := <-ch;
        print(prime, "n");
        ch1 := make(chan int);
        go Filter(ch, ch1, prime);
        ch = ch1
    }
}

func main() {
    Sieve()
}

骨幹は最初の例と同じで、チャネルというものをunbounded_bufferに置き換えただけです。

#include <agents.h>
#include <string>
#include <iostream>
#include <memory>
#include <boost/thread.hpp>

using Concurrency::unbounded_buffer;
using Concurrency::ISource;
using Concurrency::ITarget;
using Concurrency::asend;
using Concurrency::receive;
using std::shared_ptr;
using std::make_shared;
using boost::thread;

// Send the sequence 2, 3, 4, ... to channel 'ch'.
void Generate(shared_ptr<ITarget<int>> ch)
{
    for (int i = 2; ; i++) {
        asend(*ch, i);  // Send 'i' to channel 'ch'.
    }
}

// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
void Filter(shared_ptr<ISource<int>> in,
	shared_ptr<ITarget<int>> out, int prime)
{
    for (;;) {
        int i = receive(*in);  // Receive value of new variable 'i' from 'in'.
        if (i % prime != 0) {
            asend(*out, i);  // Send 'i' to channel 'out'.
        }
    }
}

// The prime sieve: Daisy-chain Filter processes together.
void Sieve()
{
    auto ch = make_shared<unbounded_buffer<int>>();  // Create a new channel.
    new thread(Generate, ch);  // Start Generate() as a subprocess.
    for (;;) {
        int prime = receive(*ch);
        std::cout << prime << std::endl;
        auto ch1 = make_shared<unbounded_buffer<int>>();
        new thread(Filter, ch, ch1, prime);
        ch = std::move(ch1);
    }
}

int main()
{
    Sieve();
}

永遠に終わらないということで、threadはnewしたまま放置しています、ごめんなさい。あと、unbounded_bufferの基底クラスにISource/ITargetというのがあるので、Generate/Filter関数ではそっちを引数の型としました。出力はこんな感じです。

2
3
5
7
11
13
17
⋮

以下補足2点です。1点目、今回登場しませんでしたが、asend/receiveにはそれぞれ兄弟がいます。

  • send関数: asendと違い、誰かが値を取り出すまで待機する。
  • try_receive関数: receiveと違い、待機せず即座に戻る。値そのものは参照型引数に格納し、取得したかを表すbool値を返す。

2点目、send/asendとreceive/try_receiveとでやりとりできる対象はunbounded_buffer以外にいくつか種類があります。非同期メッセージのブロックにおそらくすべて載っています。気が向いたらそのうち取り上げるかもしれません。

一昨日はBoost.勉強会お疲れ様でした。

開始時刻である午前10時の時点での人の少なさに「さすがプログラマは朝に弱いなあ」と思いました。かく言う自分も開場時刻である9:30到着を目指したものの、9:50近くの到着だったのでしたが。午後はもう少し増えましたね。

当日、午前3時過ぎまで起きていたことに対し、こりゃだめだと思いましたが、そのとおりgdgdな発表になってしまったと思っています。ごめんなさい。

今回、Boost.Graphは単なる関数の集まりでしかないから怖くないよと訴えることだけを目的とし、タイトルをBoost.Graph入門の入門としました。次があったらおそらくBoost.Graph入門というタイトルです。プロパティマップ使ってグラフアルゴリズム関数使って、これで今日から君もグラフ使いだ、という構想はあるんですけどね。というわけで、ほかにもアレやってとか言われた気がしますが、それはさらに次にするしかないです。しかしみなさん、私より公式のドキュメントやexampleをあてにしていますよね?

最後に、資料へのリンクを置いておきます。

今年も高専プロコンが終わりました。というわけで、プロコンのプログラムから、自分が担当した部分で最も重要な存在であるD3DImageの部分を抜き出してサンプルプログラムにしようとしたのですが、結局1から書き直したも同然となってしまいました。特に、D3DImage.IsFrontBufferAvailableChangedの扱いは、プロコン前だと完全に無視していましたし、後でプロコンのプログラムへフィードバックするつもりです。D3DImage/VMR9 Renderlessのサンプルと比較用に普通のウィンドウへ出力するサンプルをここに置きます。あくまで比較用なのでさらにいいかげんです。DShowWPFはVC++ Expressでもコンパイルできるようにしているつもり(試してはいない)ですが、DShowWin32はWTLを使っています。ご容赦ください。

  1. DShowWPF.cpp : VMR9 Rnderless/WPF サンプル
  2. DShowWin32.cpp : VMR9 Rnderless/Win32 (WTL) サンプル

ちょっと今までここに載せてきたサンプルより規模が大きいので、ライセンスを明記することにしました。といっても、NYSL、利用にどんな制約を設ける意図も全くありません。

このサンプルプログラムの主役のD3DImageは、.NET Framework 3.5 SP1で追加されたクラスです。これは何かというと、Direct3D 9での描画した結果をWPFの描画システムへ取り込めるというものです。今回、DirectShowの映像をWPFへ送り込むために用いました。具体的には、DirectShowからVMR9 RenderlessモードによってDirect3D 9 サーフェースの形で映像を取得し、それをD3DImageへ送り込むという構成になっています。数ヶ月前までWPF、Direct3D 9/9Ex、VMR9すべて未体験、DirectShowもちょびっとかじっただけという状態でしたが、なんとかなりました。

通常、WPFで動画を再生するにはMediaElementやVideoDrawingを用いればよいのですが、今回はそれらで対応できず、DirectShowを使う羽目になりました。また、DirectShowの出力を描画するだけなら、HwndHostを使って子ウィンドウを作りそこへ描画させればよいのですが、それでは映像に加工ができず困るという点が問題でした。

D3DImageの基本的な使い方はSetBackBufferでバックバッファを指定後、Lock→バックバッファへ描画→AddDirtyRect→Unlockです。そして、VMR9 Renderlessの基本的な使い方は、PresentImageで描画すべきイメージがサーフェースとして渡されるので、PresentImage内でBeginScene→描画→EndScene→Presentです。この2つを組み合わせると……、頭が爆発します。

単純に考えて、PresentImage内でLock–Unlockしたいのですが、D3DImageはDispatcherによりスレッドに結びつけられているので不可能です(PresentImageは独自のスレッドで呼ばれます)。そのため、このサンプルではDispatcher.BeginInvokeを使ってメインスレッド内でLock–AddDirtyRect–Unlockしています。ただ、これだとバックバッファへの描画がLockの外側で行われることが気になります。川西さんのMedia Foundationと組み合わせる例(川西 裕幸のブログ : Media Foundation ⑥ WebCam + WPF XAMLとC#の実装)もそうやっているし、プログラムが落っこちるなどといった問題も生じていないので気にしないことにします。ここら辺、プロコンのプログラムではまた少し違った形になっています。

もう1つの難関がIsFrontBufferAvailableChangedでした。今週はこいつをどうするかに大半を費やしました。IsFrontBufferAvailableがtrueになったときには必ずSetBackBufferを呼ばなければならないのですが、その前に自分の管理しているDirect3Dオブジェクトも駄目になっていたら作り直さないといけません。そこら辺はデバイスロストした場合の話を参考にしています(ただし、IsFrontBufferAvailableChangedが呼ばれたとしてもロストしていない場合もあるので注意です)。デバイスに関連するオブジェクトを全部リリースしてResetすればいいという話も見つかるのですが、どうもうまくいきません。Windows SDKのサンプルVMR9Allocatorでも、デバイスまで破棄して作り直しているのでそうしました。この点、Direct3D 9Exは楽です、破棄せずResetExで動くんですから。

Vista/7以上ではD3DImageにおいてDirect3D 9ExのほうがD3DImageの性能がよいとMSDNライブラリに書いてある(Direct3D9 および WPF の相互運用性のパフォーマンスに関する考慮事項)のでそれに従っています。これ、さっき書いたようにデバイスロスト時の対処が楽になったのが良いと思いました。というより、逆にデバイスロストの面倒くささの一片を味わった気分です。

前回の続きです。あの後、Windows API Code Pack (.NET Frameworkアプリ用の新APIのラッパー集)にもこれ関連のものがあることに気づき、ソースを読んでみましたが、やはりダミーのウィンドウを作っているようでした。

ところで、なんでこれを取り上げたかというと、Project EditorがMDIアプリケーションなので対応させたかったからです。その結果、こうなりました。

ビフォー
タスクバーのabdev.exe(対応コードを組み込む前のもの)のアイコンをポイントした様子。ウィンドウ全体が1項目としてサムネイル表示されている
アフター
タスクバーのabdev.exe(対応コードを組み込んだ後のもの)のアイコンをポイントした様子。abdev.exe内で開いている3つのウィンドウがそれぞれサムネイル表示されている

Windows 7のタスクバーのアイコンをポイントすると、こういう風にプレビューが表示されます。これ自体はVistaからありましたが、アフターのようなことができるようになったのは7からです。

前回は文章ばかりだったので、今回はあっさりとさせました。プログラムの解説は次回以降に持ち越しということで。

すっかり間があきました。Windows 7もRTMが出て、MSDNやTechNetなどではダウンロード可能となったようです。また、Windows 7 SDKの正式版も出ました。というわけで、Windows 7の機能を使ってみたいと思います。今回取り上げるのは次の2つの機能です。

  • Windows 7のIE 8にはタッチ機能 - ITmedia Newsに書かれている、タスクバー上でのタブ切り替え:Internet Explorerのほか、エクスプローラのフォルダ表示、Safari 4やExcel 2010もこれに対応しています(Excel 2000から2007までも実現できていますが、旧来のAPIを使っているため、次のエアロプレビューに対応していません)。
  • エアロプレビュー(旧称エアロピーク):Vistaからさらに進化したWindows 7の新GUI - @ITの「グラフィックス機能を駆使したアプリケーションの切り替え表示」にあるようにタスクバーのアイコンやライブサムネイル上をマウスでポイントするだけで対応するウィンドウが一時的に手前に表示される機能です。

ようするに、1番目、タブやMDIの中身をタスクバーボタンで選べるようにすることをAB5のProjectEditorでもやりたかったのです。

ソースコードはこちらです: MdiTaskbarTest.cpp

。なお、Windows 7 RCとWindows 7 SDK (正式版)で動作を確かめました。

WTLを使った何の変哲もないMDIアプリケーションです。メニューバー上の「子ウィンドウ作成(M)」をクリックすると、エディットボックスが載ったMDI子ウィンドウが作られます。なお、MDIフレームウィンドウ・クライアントウィンドウはMainFrameクラス、MDI子ウィンドウはChildFrameクラスが対応します。さて、このエディットボックスが載ったMDI子ウィンドウをIEのタブのようにタスクバー上で選べるようにしよう、そしてこれをエアロプレビュー対応させようというのが今回の趣旨です。

通常、MDI子ウィンドウに対してタスクバー上のボタンが作られることはありません。そこで、ITaskbarList3です。これのRegisterTabとUnregisterTabで任意のウィンドウをタスクバーのボタンに登録・解除できるようになります。以前のものよりMDIでは使いやすくなったと思います。

RegisterTabで登録しただけでは残念ながらライブサムネイルもエアロプレビューもできません。しかし、それらを表示させることは簡単です。DwmSetIconicThumbnailとDwmSetIconicLivePreviewBitmapという関数でビットマップを渡してやるだけでよいのです。ただし、ライブサムネイル用のビットマップはキャッシュされるので、MDI子ウィンドウの内容が書き換わったときにはDwmInvalidateIconicBitmapsで通知しないといけません。

ここから、問題に行き着くまで、しばらく逆方向に芋づるを辿って話を進めます。DwmSetIconicThumbnailとDwmSetIconicLivePreviewBitmapは、それぞれWM_DWMSENDICONICTHUMBNAILとWM_DWMSENDICONICLIVEPREVIEWBITMAPのメッセージを受け取ったときに呼ぶのが良いとされています。この2つのメッセージを受け取るには、DwmSetWindowAttributeでそれぞれDWMWA_FORCE_ICONIC_REPRESENTATIONとDWMWA_HAS_ICONIC_BITMAPのフラグを有効にしてやる必要があります。ところが、WS_CHILD属性を持つウィンドウにDwmSetWindowAttributeを使うとE_HANDLEのエラーを返してきます。つまり、MDI子ウィンドウにDwmSetWindowAttributeは使えないのです。

そこでどうするのかというと、WM_DWMナントカを受け取るためだけのダミーのウィンドウを作ることにしました。きちんと確かめたわけではありませんが、Spy++で見る限り、Safari 4もExcel 2010もそういているようなのです。このプログラムでも、そのようなウィンドウであるDummyWindowForTaskbarButtonクラスのtaskbarButtonというメンバがChildFrameに設け、ChildFrameとDummyWindowForTaskbarButtonは1対1に対応するようにしています。これに伴い、ITaskbarList3::RegisterTabもこのダミーウィンドウを登録するようにしています(RegisterTabは子ウィンドウでも成功するのですが)。あと、タスクバーでライブサムネイルがクリックされたときも(おそらくRegisterTabで登録したため)ダミーウィンドウへWM_ACTIVATEがやってくるので、本来のMDI子ウィンドウに飛ばすという処理も行っています (DummyWindowForTaskbarButton::OnActivate)。

ちなみに、DwmSetIconicThumbnail用のビットマップを作るとき (DummyWindowForTaskbarButton::SetIconicThumbnail)、ライブサムネイルの大きさとして指定された大きさ目いっぱいになるように単純にStretchBltで拡大・縮小していますが、実際には元画像の縦横比を崩さないように拡大・縮小するほうがきれいになると思います。ビットマップはα付きビットマップなので、ライブサムネイルの縦横比とあわない分は透明にしてしまえばよいのです。

私は、APIを使ってプログラムする立場から、このようなダミーのウィンドウを作らなくて済む仕組みにしたほうが簡単だったのにと思っています。もう少しなんとかならなかったのでしょうかねえ。

もう数回、このプログラムの解説を書きたいと思っています。そのため、(1)としました。

春先のことだったのですが、タイプライブラリから読み取ってラッパークラスを作るプログラムの生成しようと調べ回っていました。それから数ヶ月、放置していたのですが、昨日、VBScript.RegExp(WSH付属の正規表現)を使えるところまで進みました。この調子で、IEやOffice (Word/Excelなど)の操作ができるところに持って行きたいと思っています。VBみたいになるかどうかは分かりませんが、プロジェクトエディタ上で参照設定できるようにしたいです。

現在、こんなコードが動きます。RegExpクラスは、VBScript.RegExp用のラッパークラスです。

#require <com/index.ab>
#require "out.ab" 'タイプライブラリから生成させたクラスが入っているファイル

#console

Imports ActiveBasic
Imports ActiveBasic.Windows
Imports System

Try
    ThrowIfFailed(CoInitialize(0))

    Using re = New RegExp
        With re
            .Pattern = ".*@.*" ' メールアドレス「もどき」
            .Global = True
            Do
                Dim s = Console.ReadLine()
                If IsNothing(s) Then Exit Do
                If .Test(s) Then
                    Print "Match!"
                Else
                    Print "No match"
                End If
            Loop
        End With
    End Using

Catch e As Exception
    Console.Error.WriteLine(e)
Finally
    CoUninitialize()
End Try

End

久々なので、大げさな題を付けました。

新しいWindowsでしか実行できない関数を使うときの1つの手段として、GetProcAdressがあります。C++0xのdecltypeは、そのやり方を大きく変えます。

HMODULE hmodKernel =
    GetModuleHandle(TEXT("kernel32"));

typedef decltype(CancelSynchronousIo)* PCancelSynchronousIo;
PCancelSynchronousIo pCancelSynchronousIo =
    reinterpret_cast<PCancelSynchronousIo>(
        GetProcAddress(hmodKernel, "CancelSynchronousIo"));

今までだったら、MSDNライブラリあるいはヘッダファイルを直接見るなどして、typedef BOOL (WINAPI *PCancelSynchronousIo)(HANDLE hThread);とtypedefを書く必要がありました。ところが、decltypeを使うと、このとおり関数名から直接、型を取り出せます。

つまり、<windows.h>以下の膨大な関数の宣言がそのままGetProcAddress利用時にも使い回せるようになったことを意味します。使える状況がやや限られますが、autoと組み合わせれば、typedefを使わないことすら可能です。私は、「あり」だと思いますが、(C# 3.0のvarのときの様子からすれば、特にautoについて)受け入れがたいと感じる人もある程度いると思います。

auto pCancelSynchronousIo =
    reinterpret_cast<decltype (CancelSynchronousIo)*>(
        GetProcAddress(
            hmodKernel, "CancelSynchronousIo"));

あと、result_type (__stdcall *fn_ptr)(…)のようなstdcall関数へのポインタの書き方も、よく忘れるので毎度調べる羽目になります。よその掲示板でも時々この書き方についての質問を見るという印象を持っています。これから解放されるのも嬉しい点です。

autoはともかく、decltypeは、Win32APIを使う人たちの中で、普段クラスやテンプレートを使わない人の間にも是非広まって欲しい機能だと思っています。

前回のVisual Studio 2010 CTPではautoがあるのに、decltypeがなくて心底がっかりしました。あと、昔から類似品の__typeofのあったgccはうらやましかったです。なお、今はg++でもdecltypeが使えます。

ご覧の通り、activebasic.comが復活したようです。心配をおかけしてすみませんでした。と言う立場なのでしょうが、内心自分もひやひやしていました。今日はその間の積もる話でも。

まずは、Twitter始めました。ysk-noh (egtra) on Twitterです。フォロー等ご自由にどうぞ。

復活そうそうアレなんですが、休止宣言します。いや、毎週木曜の更新をやめ、不定期にします。当分、ブログなんて書いている場合ではない(はず)なので、予防線、免罪符でも張っておこうというわけです。いつ言い出そうと考えていたところ、今回の長期不通が来ましたが。

忙しい以外の言い訳を書いておきましょうか。そもそもこれは、3日坊主にならないよう週一更新を自分に課す目的でしたが、無事、飽きずに3年も続き、もうこの枷がなくても平気だろうと考えています。ネタのストックもかなり積み上がっていますし、当然これから貯まる分もあるわけですし。

先週のプログラムでは、標準のエディットボックスを使っているということもあって、GDIを使わざるを得ませんでした。バッファを用意して、GDIでアルファ値以外の描画→アルファ値の調整→表のウィンドウへの転送という手順を踏むのは面倒です。

そういうわけで、急速にAB5でGDI+の対応させようという考えへ至るわけです。GDI+ならアルファ値付きで描画できるからです。前回のプログラムも、はじめはGDI+で描画しようと考えていました。それだと、別途クリップボードへのコピーに対応させなければならないと気付いて、結局エディットボックスを使ったのですが。

現在、Brush/PenやFont、Image/Bitmap/Metafileなどのクラスが順調に追加されています。

AB5では、コンパイルを高速化するため、basic.sbp以下を事前にコンパイルする仕組みが導入されます。それがデバッグ・リリースの区別などで、現在8種類です。ところで、先週書いたように、私のPC、OSからはCPUが8個あると認識されています。ちょうど数がぴったりというわけで、コンパイラのプロセスを8個起動するだけのプログラムを書きました。コンパイラの出力メッセージをコピーできるようにエディットボックスを8個並べています。
サンプルアプリParallelBuild実行中の図

そう、調子に乗って、Aeroで全体を半透明になんてことやってみました。いや、ちょうど素晴らしいお手本を見つけたのでつい: Windows Theme API 2-コントロールのテキストを不透明にする- — while( c++ );

自分の書いたコードはこちらです: ParallelBuild.ab。AB5です。コンパイルできるもんならしてみやがれという状態で本当にすみません。

そこでは、Vistaからの新API、BufferedPaint関数群を使っていますが、これは本当にDIB + メモリDCをラップしただけのようです。前に、32-bit DIBからBitBlt (SRCCOPY)を使いGDIでガラスウィンドウ上にアルファ値付きで描画するということを紹介しましたが(紹介しただけで試してなかった!)、やっていることは同じだと思います。いや、ボタン・エディットというコントロールを扱っているという点は異なりますが。

ともあれ、その方法を応用して、エディットボックスに適用したわけです。IE・エクスプローラのアドレスバーのように、エディットボックスの部分も透けています。ARGBなカラー値は前乗算済み (pre-multiplied) にしておくのがコツみたいです。

あと、ソースコード中にコメントしてあるように、タイトルバーの文字列・アイコンがないのもわざとです。どこかで方法を見つけたので試してみました。

本格的にx86-64環境へ移っている最中なのです。デバイスドライバは問題ありませんでしたが、シェル拡張でちょっと手間取りました。エクスプローラは64ビットしかないので、インプロセスCOMのシェル拡張も64ビットDLLに移らないといけないわけです。IEだって32ビット版を使わせるのだから、エクスプローラだって32ビットでもいいのに、と思ってしまいます。

TortoiseSVN、FireFileCopyなどは64ビット版も用意されていましたが、mp3infpにはありませんでした。幸い、ソース公開 (LGPL)だったので、ビルドすればいいだろうと考えるわけです。こんな流れになりました。

  1. Get/SetWindowLongでエラーになるのを直す。
  2. liboggやlibvorbisの64ビット版がないことによるリンカエラーが出たので、liboggやlibvorbisのビルドに周る。
  3. mp3infp.dllのregsvr32(64ビット版)で読み込みに失敗するというので調べてみると、manifestで”x86″と指定している箇所があったので、”*”に変更

インストーラとかよく分からなかったし、regsvr32で登録できるようだったので、直接regsvr32です。32と連呼していますが64ビット版です。ハードリンクでいいから、regsvr64も用意しましょうよ……。

コントロールパネルの項目であるmp3infp.cplの64ビット版もビルドしてレジストリに買い込みましたが、まだ表示されていません。再起動しないとだめなのかなと思っています。

いろいろ不満が積み重なって、新しいPCを導入しました。3台目にして、初の自作です。

Core i7 920なんて選んだら、後で友人にそんな金あったらIntelのSSDでも買えアホそのほうがずっといいと言われました。いや全くその通りだと思います。それにしても、Core 2やPhenom IIだとマザーボード・メモリが格段に安くなりますね。しかし、4コアのハイパースレッディングで、タスクマネージャに8つCPU使用率のグラフが並ぶのはなかなかの圧巻です。

OSは相変わらずWindows Server 2008。いや、今回は64ビット版です。グラフィックもAeroが使える程度になりました。

それにしても、さすがにPentium 4から乗り換えると速く感じます。プログラムのビルドなど、かかる時間が半分以下になっているような気がします。並列処理を全く行わなかったとしてです。8スレッドで処理すれば1/8とそう都合よくはいきませんが、それなりには時間短縮される感じがします。そういえば、ABコンパイラもそこらへん何とかしたいです。

とりあえず、夏を越せることを祈って終わります。

完全に狂っています、体内時計が。

とうとう見付けました、以前(IMEのドキュメントURLメモ)は謎の存在だったimeshare.dllの使い方を。How to get IME settings such as converted text color ‐ Mozilla for Windows x64 platformにばっちり書いてあります

というわけで、これはMS-IME限定でした。さて、Office 2000 の新機能 WHITE PAPER - May 1999の中に、IMEShareという言葉が出てくることから、個人的にはMS-IME 2000から存在するのではないかと勘ぐっています。またOffice 2007を入れてあるVistaには存在しないことから、Vista付属MS-IMEとOffice 2007には存在しないのではないかと考えています。この2つのバージョンは、すべてTSFで処理するということで合点が行きます。

ようするにこういうことです。IMMには、確定前の文字列のスタイルを取得する統一したAPIが存在しませんでした。それ故、MS-IMEもATOKも独自にそれを取得するAPIを公開しており、MS-IMEではimmshare.dllがそれを行うモジュールだったのです。ただし、TSFだけ世代のMS-IMEでは、TSFのAPI経由でこの情報を取得できるため、imeshare.dllは搭載されていないという結論です。

そういえばというわけで、AB5のプロンプト画面でも書くわけです。もしかしたら、この調子で、ABでGDIとかGDI+とかDirect2Dとかやるかもしれません。

(more…)

前回のTSFのサンプルをActiveBasicへ移植してみました。半分以上は、インタフェースなどの宣言を用意することだった気がします。

それだけでは物足りないので、描画していなかった未確定文字列の下線を書こうと決心。表示属性の使用 (MSDNライブラリ)がずばりそれだろうと期待して取りかかりました。

結論は、ビンゴ。このように今度はきちんとした線が表示されました。しかし、面倒でした。もう少し簡単に使えるようにならないのでしょうか。
表示属性を使用して、確定していない文字列に下線が引かれた様子

表示属性は、最終的にTF_DISPLAYATTRIBUTE構造体 (MSDNライブラリ英語版)の形で表現されるのですが、その中で線の種類を表すのがTF_DA_LINESTYLE列挙体です。わざわざ新しく作らなくても、線の種類(実線・破線・点線など)にGDIのCreatePen系の定数など、既存のものを使えばいいのに、と思いました。いっそ、LOGPEN構造体をメンバにしてくれれば楽なんですが。また、例えGDI+などGDI以外で描画するとしても、独自に作られるよりは覚えることが減っていいと思うんですけどね……。

この線の種類は、不思議なことに、なんと、日本語MS-IMEで設定可能なTSFでディザ細線・ディザ太線の意味の定数が存在しないのです。代わりにTF_LS_SQUIGGLE(ジグザグな線)という定数があり、MS-IMEもTSFではディザ細線のはずの状況で、代わりにこっちを指定してきます。Vista使っていればCUAS(IMMエミュレーション)の描画で頻繁に見かけるのですが、たしかにジグザグ線です。

Windows Server 2008のメモ帳でMS-IME 2007による未確定文字列の様子 Windows XPのメモ帳でMS-IME 2002による未確定文字列の様子
Windows Server 2008でMS-IME 2007 Windows XPでMS-IME 2002

ジグザグ線は、GDIの(Ext)CreatePenには対応する定数がないので面倒に思います。ディザ細線なら、NT系限定ながらExtCreatePenのPS_ALTERNATE指定でペン作れるのですが。

あと、このサンプルは不完全なようで、Social IMEとWinAnthyでは上手く動かない点があります(直接TSF対応しているはずのMS Wordではまともに動くんですけどね)。しかもSocial IMEのほうはソース非公開みたいですし。MS-IME(Vista系2002とOffice 2007)は問題なく動いてくれるのですが。あと、念を押して書いておきますが、ATOKは論外です、TSF非対応なので。残念です。

ソースコードは、待ってください。そのうちひっそり公開するつもりです。

ようやくText Services Framework (TSF) の使い方を理解するために、Windows SDK付属のサンプルプログラムからあれこれ削ったものを基に、最小限のプログラムを作ってみました。どれくらい削ったかというと、TSF以外の入力は受け付けないほど——WM_CHARなどのハンドラを書いていないため、MS-IMEの直接入力モードでは何も入力されないくらい——徹底しています。では、TSFInputTest.zipをダウンロード。Visual C++ 2008 SP1 + Windows SDK 6.0でビルドしています。

仮に参考にするなら、このプログラムだけでなく、基となったWindows SDKのサンプルtsfappも必ず見ておくべきです。これはこれでまだ実用的でない部分もありますが。例えば、どちらも確定前の文字列に下線が付いたり色が付いたりせず区別が付きません。どっちもそんなもの描画していないからです。TSFでは、未確定の文字列の描画もアプリケーションの責務です。従来のIMMでは任意でしたが。

TSFの入力を受け付けるために大事なのは、ITextStoreACPインタフェースを実装することです。中の人のブログ、TSF Awareのエントリでもこれだけが必須と書かれています。この中にInsertTextAtSelection(向こうからテキストを入れたいとき、未確定文字列の入力・変換候補の選択も含む)や、GetText(こっちから向こうへテキストを渡す、例えば再変換のときに呼ばれる)などといった、いかにもな感じのメソッドが存在します。これによって、選択範囲・ロックの概念を実装しなければなりません。このプログラムでは切り捨てましたが、属性というものの取り扱いもほとんどの場合において欠かせないだろうという感じました。ここでごちゃごちゃ言うより、ソースコード見たほうが早いと思います。

間が開きましたが、Direct2D版です。以下はコードの一部を抜粋して取り上げるので、完全なコードも併せて参照ください。ビルドにはMicrosoft Windows SDK for Windows 7 and .NET Framework 3.5 SP1: BETA、実行にはWindows 7 βが今のところ必要です。

HWND Create()
{
    if (FAILED(D2D1CreateFactory(
        D2D1_FACTORY_TYPE_SINGLE_THREADED, &pD2DFactory)))
    {
        return 0;
    }
    return CreateWindow(
        reinterpret_cast<LPCTSTR>(static_cast<UINT_PTR>(wncClassAtom)),
        TEXT("Direct2D Test"), WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        0, 0, 0, this);
}

ウィンドウを作るのと前後して、Direct2Dのファクトリを作る必要があります。
この例ではそれだけで済んでいますが、MSのサンプルでは、ほかのオブジェクトを作る操作を併せてCreateDeviceIndependentResourcesという名前の関数になっており、例えば、画像ファイルの読み込みなどが行われています。

HRESULT CreateDeviceResources()
{
    HRESULT hr = S_OK;
    if (prthw == 0)
    {
        RECT rc;
        GetClientRect(hwnd, &rc);
        D2D1_SIZE_U size = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top);
        hr = pD2DFactory->CreateHwndRenderTarget(
            D2D1::RenderTargetProperties(),
            D2D1::HwndRenderTargetProperties(hwnd, size),
            &prthw);
        if (FAILED(hr))
        {
            return hr;
        }
        hr = prthw->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &pscb);
        if (FAILED(hr))
        {
            return hr;
        }
    }
    return hr;
}

CreateDeviceIndependentResourcesと違って、CreateDeviceResourcesではデバイスに依存するオブジェクトの生成を行っています。ここでのデバイスとは、レンダーターゲット、ここではウィンドウということになります。もう少し読めば、CreateDeviceIndependentResourcesとCreateDeviceResourcesに分かれている実務上の理由が見えてきます。

なお、CreateWindowの引数hInstanceは、Windows 2000以上の条件を当然満たすためNULLで構いません。

void OnPaint(HWND hwnd)
{
    PAINTSTRUCT ps;
    BeginPaint(hwnd, &ps);
    OnRender();
    EndPaint(hwnd, &ps);
}

WM_PAINTが来たときの処理です。BeginPaint/EndPaintは呼んでいますが、それだけです。実際の描画処理は行いません。

void OnRender()
{
    HRESULT hr = CreateDeviceResources();
    if (prthw && !(prthw->CheckWindowState() & D2D1_WINDOW_STATE_OCCLUDED))
    {
        prthw->BeginDraw();
        prthw->Clear(D2D1::ColorF(D2D1::ColorF::White));
        Render(prthw);
        hr = prthw->EndDraw();
    }
    if (hr == D2DERR_RECREATE_TARGET)
    {
        DiscardDeviceResources();
    }
}

OnPaint内で呼んでいます。BeginDrawとEndDrawで挟んだ部分がGDIでのBeginPaintとEndPaintに相当し、つまりその中が実際の描画するコードです。prthwは、レンダーターゲット、およそデバイスコンテキストにあたるもので、CreateDeviceResourcesの中で作成しています。OnRenderが呼ばれる度にCreateDeviceResourcesも呼んでいますが、中でNULLチェックをしているので、毎回作り直されるわけではありません。

EndDrawの戻り値がD2DERR_RECREATE_TARGETだったときの処理、DiscardDeviceResourcesでは、CreateDeviceResourcesで作ったオブジェクトをすべて破棄しています。これらは次回のOnRender時に作り直されます。このために、CreateDeviceIndependentResourcesとCreateDeviceResourcesに分かれていると言った感じです。

void DiscardDeviceResources()
{
    pscb.Release();
    prthw.Release();
}

pscbとprthwは_com_ptr_tなので、pscb->Release()などとしてはいけないことに注意です。

void OnSize(HWND hwnd, UINT /*state*/, int cx, int cy)
{
    D2D1_SIZE_U size = {static_cast<UINT>(cx), static_cast<UINT>(cy)};
    if (prthw != 0)
    {
        HRESULT hr = prthw->Resize(size);
        if (FAILED(hr))
        {
            DiscardDeviceResources();
            InvalidateRect(hwnd, 0, FALSE);
        }
    }
}

もう1ヶ所、D2D関連の処理を行っているのがWM_SIZEのときです。サイズ変更を伝え、エラーだったらオブジェクトを破棄して、InvalidateRectしています。OnRender内で作り直させるつもりでしょう。

ほか、ここで取り上げなかった細かいことをいくつか。

  • WinMainの中でCoInitialize/CoUninitializeしています。
  • 私はWM_DESTROYでDiscardDeviceResourcesなどを呼ぶべきではないかと最初思いましたが、
    前述したとおり_com_ptr_tを使っているので、終了時は自動でReleaseされます。
  • ウィンドウクラスの登録時、WNDCLASS(EX)のwc.hbrBackgroundはNULLにしています。背景の塗り潰しもD2Dで行うため不要なのです。具体的には、BeginDrawの直後prthw->Clear(D2D1::ColorF(D2D1::ColorF::White));で背景を塗り潰しています。

次は、GDI+版です。Direct2Dを触ってみたのですが、ウィンドウメッセージをまたいで扱うデータが存在することから、結局C++クラスを導入することにしました。

最初に作ったC++クラスを使わない版も併せて公開しておきます。

(more…)

とりあえず、コッホ曲線を描いてみました、GDIで。

GDIによるコッホ曲線

ソースコードを載せておきます。C++です。

(more…)

書きかけを放置したら、気が付くと日付が2つも進んでしまいました。

いろいろあって、GDI+を使ってみたいなと思っていました。しかし、Windows 7ではDirect 2Dなるものが載るらしいというのは以前に書いたとおりです。Windows 7 βをインストールして、SDKもある。使ってみない手はないと思いつつ、まずは比較用のコードを書くわけです。続きは正規の日付で。

買ってから1ヶ月もしないで壊れました、Seagateのハードディスクが。学校のサークルでのことです。

電源を入れると怪しい異音が出て、BIOSで認識されずという具合でした。OSとアプリケーションをインストールしたくらいで、バックアップすべきデータがろくに無かったのが救いですね。せめて数ヶ月もってくれれば、4月を超すまで使えればと思っていたんですけどね。

そんなわけでデータ救出などは全く考えずにとりあえず買った所へ持っていけば、新品に交換してもらえるだろうという風に話がまとまりました。どうせ同じSeagateがやってくるだけでしょうけど。

WM_TIMERによるTimerクラスを作ったので、それのサンプルです。元ネタはWin32プログラミング講座 ~ Step32. アナログ時計を作る ~です。clockプログラムのキャプチャ

WM_TIMER(Timerイベント)の部分のソースコードを載せておきます。まあ、元と大して変りないですけどね。

Sub Timer_OnTick(sender As Object, e As Args)
    Dim wsec As Word
    wsec = st.wSecond
    GetLocalTime(st)
    '秒針を動かす必要があるときは再描画する
    If wsec <> st.wSecond Then Invalidate()
End Sub

ほかに、NoWestさんのTimerクラスもあって、これとは場合によって使い分けることになるのですが、その話はまた今度にします。

Windows Vistaと9xとでネットワーク共有を組もうとしても上手くいきません。

その原因の1つに認証方式の違いがあります。9xではLM認証というものを使いますが、Vistaでは、NTLMv2認証のみの使用が初期設定です。2000/XPは、LM/NTLM/NTLMv2を使い分けます。かなりいい加減なので、詳しくはLmCompatibilityLevelでググってください

そのLmCompatibilityLevelの設定を変えれば、Vistaでも2000/XPと同じ状態にできます。そうすれば、LM同士で9xとVistaでも繋げられるというわけです。安全性と引き換えですが。

しかし、今回の話は逆です。実はWindows 9xをNTLMv2に対応させることも可能だったのです。9xとVistaでのネットワーク共有について調べていたところ、NTLM 2 認証を有効にする方法(MSKB)を見つけました。

そこにあるリンクからActive Directory Client Extension をインストールする方法へ行き、Windows 98 DSClient パッケージをダウンロードします。これを実行するとインストーラが走ったかのような表示になりますが、実際には所定の場所にインストーラが展開されているだけです。それを実行し、再起動することで初めてインストールが完了します。あとは、NTLM 2 認証を有効にする方法の記事に従ってレジストリを変えればNTLMv2が有効になります。

もっとも、こうして互いに見えるようにしたところで、Windows 98 Second Edidtion のエクスプローラから Windows Vista の共有フォルダ内のファイルを開くことができないの言うとおり、9xからVistaの共有フォルダを見るのは無理です。あっという間にエクスプローラが固まり、なんとかしようとすると青画面で使い物にならず、目的は果たせませんでしたというオチでした。

ActiveBasic 5は、だいぶオブジェクト指向言語と呼べるだけの機能追加を行っています。そういうわけで、このブログでもオブジェクト指向プログラミングについても書くかもしれません、そんなに知識・経験の持ち合わせはありませんが。その前に、今日は自分のことについて書こうと思います

現在の自分のオブジェクト指向プログラミングに対する理解はここの影響を多分に受けています。

オブジェクト指向の概念の発明者は誰ですか? - sumim’s smalltalking-tos

ただし、実際のプログラミングではC++の影響が大きいです。ABに対しても、例えば静的型付け、パッケージやモジュールではなく名前空間の導入を主張したことなどがこれに由来していると考えています。

1つ、ここで言っておきます。少なくともABはJavaやC#のようなクラスだらけの言語にしたいとは思っていません。JavaやC#のstatic void main/Mainはクラスの限界が露呈しているように思えてなりません。静的メソッドだらけのクラス(両者共通のとこで言えばMath)なんかもそうですね。どうせやるなら100.sin()と書けるべきと思いますね(とか書くとC#は拡張メソッドで見かけ上そうできるはずなのが困りますね、話の都合上)。

そういう意味では、クラスは、絶対の存在ではなく、プロブラミングにおいて数ある選択肢の1つという見かたです(とか言いつつ、実際にプログラム書くとクラスだらけになりますが)。実際、頭の中で思い描くABに将来欲しい機能にも、クラスについてはあまり大きな要望がないです。

なんか話がそれてきました。おとなしく、冒頭の記事の紹介にとどめておけばいいものを、というとこですね。

Next Page »