[Delphi] Chet の紹介

# Chet とは

OSS で公開されている C ヘッダを Object Pascal のコードに変換するツールです。

https://github.com/neslib/Chet

実行ファイルはこちらからダウンロードできます。
https://github.com/neslib/Chet/tree/master/Bin

Chet が他の変換ツールと違うのは Clang を使って実際にコンパイルし解析する所です。
そのため、他の変換ツールだと間違う場面でも、正しく変換できる事が多いようです。

2020 年のエンバカデロ・テクノロジーズのブログにも紹介されています。
https://blogs.embarcadero.com/c-header-translator-tool-for-delphi-lets-you-parse-c-header-files-using-the-clang-compiler/

# Chet で GameInput.h を変換する

今回「[クソアプリ Advent Calendar 2024](https://qiita.com/pik/items/6d96d79c659359fed0e4)」用に必要になった（最終的には使いませんでしたが…）GameInput.h を変換してみます。

変換に必要な最低限の設定を紹介します。

まずは、以下に紹介している項目を埋めます。

## Project

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12977/14461b34-a0c7-d561-5739-76829c628f9d.png)

|項目|内容|
|---|---|
|Directory with C Header files|C のヘッダファイルがある **フォルダ** |
|Target Pascal file|出力される Object Pascal のファイル名|

フォルダにあるヘッダファイルを全て１つの Object Pascal のファイルとして出力します。

## Platforms

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12977/2c9bcf39-24cb-4819-e931-77d56a967428.png)

|項目|内容|
|---|---|
|Library constant|DLL や Lib をインポートする時に使う 定数の名前|
|Platform|必要なプラットフォームにチェックを入れます|
|Platform.Library name|リンクする Lib や DLL を指定します|

## Translate

あとはデフォルトのままで大丈夫です。
 [Run Header Translator] を押すと…

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12977/1dd8526a-e3f6-3c77-a880-0ede0ec9b5b9.png)

メッチャエラーが出ます。
C のソースを変換するツールなので C23 未満では定義されていない ```bool``` 型に対するエラーが出ています。

```Conversion Options``` の項にある ```Custom types map``` に Key Value で定義しておくと置き換えてくれるみたいですが上手く動きませんでした。

なので、GameInput.h 側の bool を全て char に置き換えました。

これで、改めて実行すると

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12977/19ddb155-3b35-f265-9b99-60faab217954.png)

上手くいきました。
弊害として、bool になるところが UTF8Char になります…

これで、GameInput.pas が出力されました。

# 手直しする

Chet が高性能とはいえそのままビルドできるソースができるのはマレです。
出力された GameInput.pas そのままだと、↓これだけエラーが出ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12977/0e951aed-fe88-5836-af97-db9dec36d09c.png)

このエラーを潰していきます。

## 1. Winapi.Windows を追加

```delphi
interface

uses
  Winapi.Windows;
```

これを追加するだけでエラーが激減します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12977/d5742538-c303-3528-fe60-8305a1927979.png)


## 2. エラーの中で使われていない物を排除

```delphi
  INTERFACE = IGameInput;
```

と書いてあるところが予約語の Interface と被っていてエラーが出ています。
他で使われていないので削除します。

もう一つ ```_HRESULT_TYPEDEF_``` が見つからないというエラーがでているので、コレも削除します。
```_HRESULT_TYPEDEF_``` は引数の定数を HRESULT の型として定義するマクロなので、そのまま定数として宣言して問題ありません。

```delphi:元
  GAMEINPUT_E_DEVICE_DISCONNECTED = _HRESULT_TYPEDEF_($838A0001);
```

```delphi:修正後
  GAMEINPUT_E_DEVICE_DISCONNECTED = $838A0001;
```

## 3. 未定義の構造体の定義 

APP_LOCAL_DEVICE_ID 構造体定義が無いので追加します。

```delphi
const
  APP_LOCAL_DEVICE_ID_SIZE = 32;

type
  PAPP_LOCAL_DEVICE_ID = ^APP_LOCAL_DEVICE_ID;
  APP_LOCAL_DEVICE_ID = record
    value: array [0.. APP_LOCAL_DEVICE_ID_SIZE - 1] of Byte;
  end;
```

同様に PIID, PIUnknown, HANDLE が未定義なのでこれも定義します。

```delphi
  PIID = PGUID;
  PIUnknown = ^IUnknown;
  HANDLE = THandle;
```

ここまで作業が終わるとエラーが全て消えます。

## 4. bool の後始末
最後に、bool を char に変換したことで UTF8Char になった部分を ByteBool に修正します。

# 修正結果

全文は長い（1000行超え）ため、修正した部分が含まれるコードを貼っておきます。

```delphi
unit GameInput;
{ This unit is automatically generated by Chet:
  https://github.com/neslib/Chet }

{$MINENUMSIZE 4}

interface

// Winapi.Windows を追加
uses
  Winapi.Windows;

const
  {$IF Defined(WIN64)}
  // Library constant で指定した定数に dll が定義されています
  LIB_GAMEINPUT = 'GameInput.dll';
  _PU = '';
  {$ELSE}
    {$MESSAGE Error 'Unsupported platform'}
  {$ENDIF}

const
  GAMEINPUT_CURRENT_CALLBACK_TOKEN_VALUE = $FFFFFFFFFFFFFFFF;
  GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE = $0000000000000000;

  FACILITY_GAMEINPUT = 906;

  // _HRESULT_TYPEDEF_ を削除してコメントを外したもの
  GAMEINPUT_E_DEVICE_DISCONNECTED = $838A0001;
  GAMEINPUT_E_DEVICE_NOT_FOUND = $838A0002;
  GAMEINPUT_E_READING_NOT_FOUND = $838A0003;
  GAMEINPUT_E_REFERENCE_READING_TOO_OLD = $838A0004;
  GAMEINPUT_E_TIMESTAMP_OUT_OF_RANGE = $838A0005;
  GAMEINPUT_E_INSUFFICIENT_FORCE_FEEDBACK_RESOURCES = $838A0006;

  // APP_LOCAL_DEVICE_ID のサイズ
  APP_LOCAL_DEVICE_ID_SIZE = 32;

type
  // APP_LOCAL_DEVICE_ID の定義
  PAPP_LOCAL_DEVICE_ID = ^APP_LOCAL_DEVICE_ID;
  APP_LOCAL_DEVICE_ID = record
    value: array [0.. APP_LOCAL_DEVICE_ID_SIZE - 1] of Byte;
  end;

  // 未定義の型を定義
  PIID = PGUID;
  PIUnknown = ^IUnknown;
  HANDLE = THandle;
```

## COM Interface の変換
今回変換した GameInput.h は、[COM ライトと呼ばれる COM Interface](https://learn.microsoft.com/ja-jp/gaming/gdk/_content/gc/input/overviews/input-fundamentals) を公開するヘッダです。
ですが、これもきちんと変換できたので Clang の威力を感じました。

```delphi:COM Interface の変換結果抜粋
  IGameInputDispatcher = record
    lpVtbl: PIGameInputDispatcherVtbl;
  end;

  IGameInputDispatcherVtbl = record
    QueryInterface: function(This: PIGameInputDispatcher; const riid: PIID; ppvObj: PPointer): HRESULT; stdcall;
    AddRef: function(This: PIGameInputDispatcher): ULONG; stdcall;
    Release: function(This: PIGameInputDispatcher): ULONG; stdcall;
    Dispatch: function(This: PIGameInputDispatcher; quotaInMicroseconds: UInt64): UTF8Char; stdcall;
    OpenWaitHandle: function(This: PIGameInputDispatcher; waitHandle: PHANDLE): HRESULT; stdcall;
  end;
```

ちなみに、このタイプに変換されたものは下記の様に VTable に直接アクセすることで呼び出せます。

```delphi:実際に呼ぶ例
var Intf: IGameInputDispatcher;
Intf.lpVtbl^.AddRef(Intf);
```

# まとめ
C のライブラリを使う場合などヘッダファイルの移行を大分手助けしてくれます。
修正が必要とはいえ、自分で書く事に比べて大分時間が節約できます。

```Post Process``` の項では、スクリプトも組めるようです。
今回、手作業で修正しましたがもしかするとスクリプトで修正できるかもしれません。

使って見たい C のライブラリがあるけどヘッダの移植が面倒くさいな…っていう時、試して見てください。