_bstr_t は何気に便利だな、とか。

昔書いてたメモ帳から転記。今更感がマックスの、備忘録メモ。

ちょこっとCOMクライアントとかのテストコード書く際に、ワイド文字列(主にWindowsで言うところのUNICODE)と、マルチバイト文字列の相互変換が煩わしいときが多々ある。

まぁ、変換用に関数一個作っとけばいい話なんですが・・・。正直、Win32 APIのMultiByteToWideChar/WideCharToMultibyte は使いたくない。理由はただ一つ。コイツらの使い勝手の悪さときたらどうしようもない。ってとこ。引数が5個も6個もいるし、何より、最低限2回のAPIコールが必要。↓のような感じ。

/*
  マルチバイト文字列 ==> ワイド文字列
*/

#include <windows.h>

//ヒープを使用。
PWSTR AllocString(PCSTR& pStr)
{
  PWSTR pWStr = NULL;
  SIZE_T cchWStr = 0;

  if(pStr == NULL)
    goto cleanup;
  
  cchWStr = MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,pStr,-1,NULL,0);

  if(cchWStr <= 0)
    goto cleanup;

  if((pWStr = (PWSTR)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,cchWStr*sizeof(WCHAR))) == NULL)
    goto cleanup;

  if(!MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,pStr,-1,pWStr,cchWStr))
    FreeString(pWStr);

cleanup:
  return pWStr;
}

//ヒープ解放
BOOL FreeString(PWSTR& pWStr)
{
  BOOL bRet = FALSE;

  if(pWStr && (bRet = HeapFree(GetProcessHeap(),0,reinterpret_cast<PVOID>(pWStr))))
    pWStr = NULL;

  return bRet;
}

でも、こんな感じだと、明らかにテストしたいコードより遙かに長いコード行・・・何か腹立つ。
お金の掛からないVC++ Express Edition を使っていて贅沢は言えませんが・・・なんかで代用できないものか・・・。

で、最近MSDNのドキュメント見てて気付いた。
_bstr_t クラスって便利やないかい!、と。

もともとは、COMでの文字列表現、BSTR型のラップするスマートポインタみたいなクラス。BSTR型はちょっと特殊で簡単に言えば文字列の長さとバッファを合わせたような型。BSTR型をPWSTR型と混同しているケースを極たまに見ます。まぁ、そんなことはどうでもよく。

_bstr_t クラスは、PCWSTR,PCSTRの両方をコンストラクタで受け付けてくれます。しかも、キャスト演算子をオーバーロードしてくれてるので、引数にPCWSTRもしくはPCSTRが必要なAPIや関数にそのまま放り込めます。

※注意点
これらのキャスト演算子は内部バッファへのナマのポインタを返すので、やたらめったら使用すると、おそらくどこかでクラッシュするバグを抱えるでせう。

#pragma comment(lib,"user32.lib")
#pragma comment(lib,"comsuppw.lib")

#include <windows.h>
#include <comutil.h>
#include <tchar.h>

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
  _bstr_t bStr("テストでげす");
  
  //ANSI版
  MessageBoxA(NULL,bStr,"ANSI版",MB_OK);

  //UNICODE版
  MessageBoxW(NULL,bStr,L"UNICODE版",MB_OK);
  
  return 0;
}

/*
ANSIバージョンのAPIは、内部でマルチバイト文字<==>ワイド文字の変換を行って
UNICODE版APIをコールしているだけのようなので、このコード自体無意味。
*/

スコープを抜けると自動的にSysFreeStringをコールしてくれるみたいなので、解放処理も必要なし。

万歳!

インスタント検索を完全に無効にしたいんだけど・・・

普段PCのブラウザは、Google Chromeを使ってます。でも、バージョンアップするのが速すぎのは別に良いとして、ユーザビリティーに関して細かな変更をチョコチョコ行うので、あれ?と思うことが多々ある。

それとは別に・・・

最近になってようやく気づいたんですが、Google検索のインスタント検索がいつのまにか有効になってしまってます。一旦クッキーとか全部履歴を削除して、検索設定を下記のようにしても、設定を無視してインスタント検索されてしまいます。

普段、検索結果の表示件数を100件にしていますが、インスタント検索が有効になってしまうと、10件になってしまい非常に使い勝手が悪くなってしまう。

これって、どうやればインスタント検索が無効になるのかなぁ・・・???

Google Chrome(17.0.963.46)自体の設定で、「オプション >基本設定 > インスタント検索を有効にして・・・」 のチェックを外すと・・・アドレスバーから検索する分にはインスタント検索をオフにすることができました。

が・・・ちょっと使っていると、またインスタント検索が有効になってたりする(`Д´)
フツーに検索結果さえ出してくれればいいんだけど。。。

一太郎は要らないがヒラギノフォントは欲しい!

ちょっと前にジャストシステムから「一太郎バージョンアップ」のDMが来てたんです。が、一太郎自体もう何年も使っていないので、DMをそこらへんに放ったらかしにしてました。

で、さっきたまたまそのDMを開いて見てたんですが・・・、プレミアムバージョンだと、なんと、「ヒラギノフォント」の基本6書体標準搭載って書いてあるじゃないですか!

これは欲しい。だって、普通に「ヒラギノフォント」を手に入れようとしたら、MACを一台買うか、もしくは、ン万円も出してフォント自体を買わないといけない。でも、今更一太郎は買うのも・・・どうせ使わないし・・・ATOKは2年前にバージョンアップして、今のところ不自由ない。

ムムム・・・・。

子供の頃、オマケが欲しくてお菓子を買った、あの感覚に似てるな・・・。

どうしよう。。。15,700円か・・・。ATOKとヒラギノフォント目当てで15,700円。。。。

ムムム・・・

今度の一太郎はEPUB形式で保存できるのか・・・別においら、物書きじゃないし・・・。

今週末リリースか・・・どーしょっかなー・・・。

iTunesで曲名の列挙

ちょっとメモ。

iTunesに登録した動画・音楽の一覧をプログラムかスクリプトから取得したくて・・・。
はじめに思いついたのは「ライブラリのエクスポート」で得られるXMLファイルから取得する方法。
これは単純にXMLをパースするだけなんで、C# (.NET Framework)で使えるようにラップするクラスを作った。
これを使って、下記のようなコードで曲名を列挙できた。・・・が、

/*
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Linq;
using System.Xml.Linq;
*/

public static void Main(string [] args)
{
  if(args.Length <= 0)
    {
      Console.WriteLine("XMLファイルを指定してください。");
      return;
    }

  string xmlfile = args[0];

  var albums = new Dictionary<string,Album>();

  var xDict = new XDict(xmlfile,"/dict");
  var Tracks = xDict["Tracks"] as XDict;

  foreach(var key in Tracks.Keys)
    {
      var xDictTrack = Tracks[key] as XDict;
      
      //アルバム名がない場合、アーティスト名で作成する。
      if(xDictTrack["Album"] == null)
        xDictTrack["Album"] = xDictTrack["Artist"] ?? "No Album";
      
      var tune = new Tune(xDictTrack);
      
      if(albums.ContainsKey(tune.Album))
        albums[tune.Album].Tunes.Add(tune);
      else
        albums[tune.Album] = new Album(tune);
    }

  foreach(var album in albums.Values)
    {
      Console.WriteLine("---{0}-",album.Name);
      album.Sort(SortBy.Track);
      album.Tunes.ForEach((tune) => Console.WriteLine("{0:D2}:{1}",tune.Track,tune.Name));
    }
}

これを書いてたとき、たまたまGoogleで検索してたら、iTunesアプリケーション自体がCOMオートメーションサーバーを実装していてドキュメントが公開されているのを今更発見しました(笑) なんだ、スクリプトから簡単に曲名からプレイリストの編集までできるんじゃないですか!(^^;;
さっそく https://developer.apple.com/downloads/ で無料のデベロッパー登録?して “iTunes COM for Windows SDK”をダウンロードしドキュメントをゲット。

/*
 wscriptで動かすと延々メッセージボックスがポップアップするので注意。
*/
var	iTunes = WScript.CreateObject("iTunes.Application");
var	tracks = iTunes.LibraryPlaylist.Tracks;
var	num = tracks.Count;
for(var i = 1;i <= num;i++)
 WScript.Echo(tracks.Item(i).Name);

簡単!

最初のXMLから取得する方法は無駄になった・・・けど、C#でXMLを操作する勉強をしたと思えば・・・ま、いいか(^^;

Perlで並列処理

複数のファイルをダウンロードするとき、wget を使っているのですが、複数のファイルをダウンロードするときは、URLのリストをファイルにして、-i でwgetに食わせてました。でもこれだと順番に一個ずつダウンロードしていくので効率が悪いです。サイズの小さなファイルだと気にならないんですが・・・。

で、Perl の system関数で、複数のwgetプロセスを作れば・・・と考えました。けど、ダウンロードするファイル数と同じ数のプロセスを作ると効率が悪いし、ダウンロード先のサーバーに迷惑をかけてしまいます。ので、同時に起動するwgetの数を制限するようにしたらいいんじゃないかと。

起動するプロセス数を制限するには、Parallel::ForkManagerモジュールが簡単で便利でいいんですが・・・Windows環境では不自然・・・ということで、threadsthreads::shared モジュールを選択。

下記のような感じのPerlスクリプトをnohup コマンドで起動すれば、あとはターミナルを切断しても勝手にやってくれる。終わったら携帯にメールを投げるようなコードを付け足せば・・・より便利かなと。

#!/usr/bin/perl

####################################################
# parallel-wget.pl urllist1.txt urllist2.txt ...
#  '-'(ハイフン) を指定すると標準入力から読み込む
#
# Perl 5.8.8 で確認
####################################################
use strict;
use warnings;
use threads;
use threads::shared;
use IO::File;
use File::Basename;

#何個のWGETを起動するか = スレッドの個数
my $NUM = 4;

#wgetコマンド。パスが通ってない場合はフルパスを。
my $WGET_COMMAND = 'wget';

#WGETオプション引数の指定
my @WGET_OPTION = qw(-nd -a Thread%02d.log --content-disposition);
# sprintf関数に渡されます。 %02d には スレッド番号が入ります。

#スレッド間で共有する配列。
my @WGETS : shared;

#Startup code
&{sub
{
  @WGETS = get_wget_commandline(@_);

  my @threads;
  $NUM = scalar(@WGETS) if(@WGETS < $NUM);
  
  #わざわざ $i_ なんて使わず、$_を使えばいいところだけど、
  # $_ を使うと "Scalars leaked: 1" なんてエラーが出るので・・・。
  # ガーベージコレクタのせい? 出たり、出なかったりする。なんで?
  foreach my $i_(1..$NUM)
    {
      push @threads,threads->create(\&ThreadStart,$i_);
    }
  
  $_->join foreach(@threads);
  
}}(@ARGV);

#ワーカースレッド関数
sub ThreadStart
{
  #引数はスレッドの番号
  my $num = shift;
  
  local $| = 1;

  #スレッド間で共有された配列が空になるまでスレッドを回す。
  while(@WGETS)
    {
      my $wget_command_line;
      {
        lock(@WGETS);
        $wget_command_line = shift @WGETS;
      }
      system(sprintf($wget_command_line,$num)) if($wget_command_line);
    }

  print STDERR "Thread($num) terminated...\n";
}

#URLが列挙されているファイルからwgetコマンドラインを組み立て、
#それらを配列に格納して返す。
sub get_wget_commandline
{
  my @retval;

  #code here
  foreach my $ifile_(@_)
    {
      next if($ifile_ ne '-' && !(-e $ifile_));

      my $fin = ($ifile_ eq '-') ? IO::File->new_from_fd(fileno(STDIN),'<') : IO::File->new($ifile_);

      die "can not detect input stream...\n" unless($fin);

      my ($name,$dir,$suffix) = fileparse($ifile_,qr/\.[^\.]*/) if($ifile_ ne '-');

      $name = "wget_$$" unless($name);
      $name = qq("$name") if($name =~ /\s/);

      my $wget_command_line = join(' ',($WGET_COMMAND,@WGET_OPTION,"-P $name",''));
  
      while(my $line = $fin->getline)
        {
          #改行文字、空行を削除
          $line =~ s/[\r\n]+//;
          next if($line =~ /^\s*$/);
          
          push @retval,$wget_command_line . $line;
        }
    }

  return @retval;
}
__END__

Perlでスレッドはなんか怪しい挙動。

Scalars leaked: 1

CentOS環境では上記エラー(警告か?)が出るけど、Windows上のActive Perl では出ない。
foreachとかforのループで、$_を渡すと出たり出なかった・・・?よく分からん。

プロセスの仕組みが根本的に違うWindowsだと並列処理はスレッドベースになって複雑にならざるを得ないのが残念すね。

スレッドプールなモジュールをインストールしようと思ったんですが、単純な問題だったので、そこまでやる必要なかった感じ。