APIを使ってみる(フォントを作る)


 皆さんは、ウインドウや印刷時に凝ったフォントを使ってみたいと考えたことはありませんか?
 私は印刷物に90度回転した文字列を出力したくて、色々と試行錯誤しました。
 え、「フォント名の先頭に@(アットマーク)を付ければいいじゃないか」って?
 確かに「@MS明朝」という具合に「@+フォント名」を指定すれば縦書き用の回転文字が使えますが、それだと半角文字は使えませんね。
 それに、単純な90度回転ではなく、斜め文字や180度回転した文字はどうでしょう。
 このように、VBが提供する標準の機能以外を実現する時にAPIが威力を発揮します。
 APIとは、Application Program Interface の略で、言葉通りに解釈すればアプリケーションプログラムのためのインターフェイスということですが、多くの場合はWindowsAPIといって、Windows が提供する様々なサービス(機能)をアプリケーションプログラムから利用する手続きのことを指します。
 ちなみにAPIとは、厳密にはWindowsAPIだけではなく各種DLLとのインターフェイスという広い意味を持ちますが、ここではWindowsAPIの事を単にAPIと呼びます。
 今回は、APIの使い方とVBが扱う文字型について取り上げます。

 今回の講座のサンプルは、 ここを押すとダウンロード できます。



 フォーム上をマウスでクリックすると、その位置に「回転文字」という文字列を90度回転させて表示します。


Private Declare Function CreateFontIndirect Lib "gdi32" Alias "CreateFontIndirectA" (lpLogFont As LOGFONT) As Long
Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long

プログラムで使うAPIを宣言している部分です。宣言が長いので見づらいですが、このプログラムで使うAPIは、
 CreateFontIndirect,SelectObject,DeleteObject
の3つです。これらの関数は"GDI32.DLL"に入っているものです。"GDI32.DLL"は\Windows\System ディレクトリにあります。


Private Const LF_FACESIZE = 32
Private Const FW_NORMAL = 400
Private Type LOGFONT
 lfHeight As Long
 lfWidth As Long
 lfEscapement As Long
 lfOrientation As Long
 lfWeight As Long
 lfItalic As Byte
 lfUnderline As Byte
 lfStrikeOut As Byte
 lfCharSet As Byte
 lfOutPrecision As Byte
 lfClipPrecision As Byte
 lfQuality As Byte
 lfPitchAndFamily As Byte
 lfFaceName(LF_FACESIZE) As Byte
End Type

Dim fnt As LOGFONT
Dim fname() As Byte
Dim font1 As Long, ret As Long

LOGFONT構造体は"CreateFontIndirect"で作るフォントを指定するためのパラメータになります。各パラメータの意味は名前から大体想像がつくと思います。


Private Sub Form_Load()
 With fnt
  .lfHeight = 20
  .lfWidth = 0
  .lfEscapement = 900   '角度=90度
  .lfOrientation = 0
  .lfWeight = FW_NORMAL
  fname() = StrConv("標準明朝", vbFromUnicode)
  For i = 0 To UBound(fname)
   .lfFaceName(i) = fname(i)
  Next
 End With
 font1 = CreateFontIndirect(fnt)
 ret = SelectObject(Form1.hdc, font1)
 ret = DeleteObject(font1)
End Sub

今回の例ではプログラムの開始時に1回だけフォントを作るようにしています。これはリソースを節約するためですが、詳細は後述します。
まず、フォントを作るためのパラメータの指定ですが注意するのはフォント名称"lfFaceName"です。
"CreateFontIndirect"に渡すパラメータはLOGFONT構造体になっていますが、その中でフォント名称を指定する"lfFaceName"の定義を見ると、
 lfFaceName(LF_FACESIZE) As Byte
となっています。
つまりフォント名称はバイト文字列に格納して渡す約束になっているのです。
では、VBの中での文字列はどう扱われているのでしょう?
実は、Unicodeといって半角英数も全角文字も全て2バイトで表すコード体系を取っているのです。しかし、VB以外の言語ではほとんどがASCIIという1バイト文字列が使われているので、VBから外部のサブルーチン(API )を呼び出して文字列を渡す場合は、Unicode→ASCIIという変換をしてやらなければいけないのです。
そして、その変換をする関数が"StrConv"です。
しかし、まだもう一つ注意すべき点があります。
"StrConv"が返してくるのは、Byte型配列ですがそのサイズは不定(StrConv関数が決める)ます。一方"lfFaceNam"は、LF_FACESIZE(32バイト)の大きさと決まっています。
しがって、1バイトずつ取り出して固定長のパラメータに格納してやる必要があります。
ここまで来て、やっとフォントを作る事ができます。しかし、今度はそのフォントを誰が使うのかを指定してやらねければいけません。
 ret = SelectObject(Form1.hdc, font1)
で、Form1のデバイスコンテキストがフォントを使うことを指定します。そして、
 ret = DeleteObject(font1)
で、作ったオブジェクト(ここではフォント)を解放してやります。


Private Sub Form_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
 Form1.CurrentX = X
 Form1.CurrentY = Y
 Form1.Print "回転文字"
End Sub

Form上でマウスクリックが起きたときに文字を描画する処理です。
APIをご存じの人なら「あれ?」と首をひねるかも知れません。
普通、CreateObject(この例では"CreateFontIndirect")で作ったオブジェクトは、
SelectObject → 実際に使用 → DeleteObject という順序で処理することになっています。
しかし、今回の例では、Create → Select → Delete → 実際に使用、と順番がおかしくなっていますね。これには"CreateFontIndirect"特有の2つの理由があります。
まず第1に、CreteObjectで作ったオブジェクトのリソースは、実はDeleteObjectでは解放されないのです。(これは"CreateFontIndirect"特有の現象だと思うのですが)
試しに、From_Loadイベント内のフォント作成ルーチンをForm_MouseDownイベントの前半に持ってきて、マウスダウンの度にフォントが作られるようにし、次にWindows付属の「リソースメータ」を起動してから、このプログラムを動かしてみて下さい。GDIリソースの残量を見ながらマウスダウンのイベントを発生させるとどうなるでしょうか?
1回2回では変化はありませんが、何十回とマウスボタンを押してフォントを作っていくとGDIリソースがどんどん減っていくのが分かるはずです。
リソースが残り僅かになるとプログラムは異常終了します。また、このリソースはプログラムを終了するまで(VBの中で動かしている場合はVBを終了するまで)解放されることはないのです。
第2の理由として、"CreateFontIndirect"で作ったフォントはパラメータで渡したプロパティを変えない限り有効であるということがあります。
試しにサンプルのプログラムを「マウスボタンが押される度にフォントのサイズを変える」という風に改造してみて下さい。
結果はどうでしょう? 2回目以降の描画では、文字列が水平に戻ると思います。
では、「マウスボタンが押される度にフォントの色を変える」とするとどうなるでしょうか?(Form.ForeColor = xx で色が変わります)
文字が直角のまま色だけが変わるはずです。
つまり、フォントのプロパティを変えない限りは"CreateFontIndirect"で作ったフォントのまま文字列が描画され続ける訳です。

従って例題プログラムでは"CreateFontIndirect"の「リソースを解放しない」という短所を「プロパティを変えない限り使える」という特性でカバーするために、Form_Load時だけフォント作るという処理をしているのです。
実際のプログラミングでは、もっと複雑な処理が必要になると思いますが、"CreateFontIndirect" で作るフォントは必要最小限にとどめるということに注意して下さい。


最後に
 VBでプログラミングをしていると(すればするほど)、VBの機能不足に悩まされる事があります。今回取り上げた回転文字もその一つですが、こういったVBの役不足を大きく補ってくれるのがAPIの存在です。
 しかも、VBでは宣言の煩雑さはあるものの、APIはVBの関数を呼び出すのと同じくらい非常に扱いやすくなっています。
 APIを使えば「Windowsの壁紙を変える」とか、「スクリーンセーバーを起動する」といったことも可能でし、VBで実現できる機能も、同じことをAPI呼び出しを行えば実行速度も速くなります。
 しかし、便利だからといってAPIを使いすぎるのも却ってVBらしさを損ない、見通しの悪い、メンテナンスのしにくいプログラムになってしまいます。
 また、API特有の決まり事もあり、(例えばCreate→Select→Deleteというステップ)扱いを誤れば実行時エラー等も引き起こします。
 従って、APIを使う場合は山椒のようにピリリと利く、効果的な使い方が望まれます。

目次に戻る