Struct.new2と構造体の使用の実践

Struct.newは引数に型名と値名を取る形式でした。そうでなければ、はじめの引数が数値の場合にアラインメント(#pragma packの引数)扱いしたり、値名の後に数値を置くことで配列を扱うという仕様に出来ないからでした。
問題は、そういう仕様である必要があったのかということです。旧言語のstruct関数の仕様を引きずっていただけでした。アラインメントを指定したい場合などめったになく、それならそれ用の関数(Struct.new2_with_alignment)を用意すればいいだけでした。配列についても、配列を扱うC_arrayオブジェクトがあるのだから、何も気にせずそれを使えばよかっただけの話でした。union(共用体)についても同じことです。(実は、Sturct.new実装時、unionのことを忘れていた。)
そしてもうひとつ、構造体は実際のところWinAPIを使うためのものです。ですから、WinAPIで使われるDWORDやLPCTSTR等の型についても構造体と同じぐらいの「用意」がなければStruct.newは使い物にならなかったわけです。

Struct.new2の単純な例

Giraffe+のScript\GetCaretPOINT.giraffeを解説します。

GUITHREADINFO: Struct.new2(
  Windows_DWORD :cbSize
  Windows_DWORD :flags
  Windows_HWND :hwndActive
  Windows_HWND :hwndFocus
  Windows_HWND :hwndCapture
  Windows_HWND :hwndMenuOwner
  Windows_HWND :hwndMoveSize
  Windows_HWND :hwndCaret
  Windows_RECT :rcCaret
)

gti: GUITHREADINFO.new
gti.cbSize.= (gti.@sizeof)
User32.GetGUIThreadInfo(0 gti.ref)

pt: Windows_POINT.clone
pt.x.= (gti.rcCaret.left)
pt.y.= (gti.rcCaret.top)
User32.ClientToScreen(gti.hwndCaret pt.ref)
pt

Struct.new2は引数を(オブジェクト 名前)の順番でとり、Struct_instanceの子供を返します。引数のオブジェクトがC言語における型として扱われます。Struct_instanceが構造体のインスタンスですので、この例では宣言的にGUITHREADINFOをdefしてますが、コレは分かりやすくするためにやってるだけで、必要な作業ではありません。

このGUITHREADINFO.newはGUITHREADINFO.cloneと全く同じです。Struct_instance.newは引数を受け取ることが出来、cloneしたオブジェクトのメンバに順番に引数を代入してから、そのオブジェクトを返します。この例ではnewに引数がないので同じになります。

gti.@sizeofはC言語のsizeof(gti)と、gti.refはC言語の&gtiとそれぞれ同じです。しかしこの場合は、refを使って明示的にポインタを与える必要はありません。DLLの関数は引数のC言語互換化のために引数のオブジェクトのto_C_argumentを呼びます。Struct.new(2)で作られたオブジェクトのto_C_argumentはrefと同じですので、自動的にポインタになります。

POINTはWindows_POINTとしてGiraffe+内で定義済みですのでそちらを使っています。こちらはnewではなくcloneをしています。Struct.new(2)で作られたオブジェクトとは別物ですので、こちらにはnewがありません。to_C_argumentもありませんので、ポインタとしてDLLの関数に渡すには、明示的なrefが必要です。

Struct.new(2)で作られた構造体とGiraffe+内部で作られたWindows_POINT等の構造体の仕様の違いが少し気になるところですが、別物であると認識しておいてください。変に仕様の統一化を図るのも混乱の元になってよくないような気もします。

union(共用体)

LARGE_INTEGERを例にC_unionの使い方を解説します。

LARGE_INTEGER: C_union.new(
  Struct.new2(
    Windows_DWORD :LowPart
    Windows_LONG :HighPart
  ) :u
  Windows_LONGLONG :QuadPart
)

li: LARGE_INTEGER.clone
li.u.LowPart.= 1
li.u.HighPart.= 2
li.QuadPart.flush //8589934593

Struct.new2とC_union.newはほとんど同じです。ただ、C_union.newの返すオブジェクトはC_unionの子供であるため、そのnewはC_union.newと同じです。ほぼ全ての型と同じく、作った共用体オブジェクトを型のように使う場合は、それをcloneします。

ひとつC言語と違う大きな制限があります。構造体や共用体を無名で使えません。これはStruct.new(2)とC_union.new共通の制限です。この例では、Struct.new2の戻り値をuと名づけています。C言語ではこのuがなければLowPartとHighPartがQuadPartと並列に置かれます。

配列

C_array.new(型 数)で作ります。いい実例が無かったので下記を例にします。

struct{
  int n;
  char a[10];
} test;
test.a[0] = 'a';
test: Struct.new2(
  Int :n
  C_array.new(Char 10) :a
)
test.a.at(0).= \a

constやポインタ

LPCTSTR等はWindows_LPCTSTR等がそのまま使えますが、int const*などは定義済みではないのでそのつど作る必要があります。

struct{
  double* pdbl;
  int const* pcint;
} test;
int const n = 10;
test.pcint = &n;
std::cout << *test.pcint << std::flush;
test: Struct.new2(
  Double.ref :pdbl
  Int.constize.ref :pcint
)
n: 10
test.pcint.= (n.ref)
test.pcint.deref.flush