前回はTurbo HAMLOGのライブラリHAMLOG50.DLLをPythonから操作して、HAMLOGデータベースに保存されているデータを読み込むところまで実装してみました。
今回は実装していたときに気になったところを調べてみます。
交信データの読み書きに使用するデータ構造は次のようになっていました。
typedef struct { char Calls[21], Date[9], // 04/08/20 Time[7], // 10:20J Code[7], Glid[7], Qsl[4]; // Qsl, Send, Rcv ここまでで57バイト WORD Flag1; char Hiss[764]; // 13 char *Myrs, // 13 *Freq, // 17 *Mode, // 17 *Name, // 65 *Qth, // 129 *Rmk1, // 255 *Rmk2; // 255 BYTE HissLen, MyrsLen, FreqLen, ModeLen, NameLen, QthLen, Rmk1Len, Rmk2Len; } TQsoBuff;
相手のシグナルレポートを保持するメンバー Hiss が char [764] 型になっていて非常に大きな領域を確保しています。そして自局のシグナルレポートを保持するメンバー Myrs から Rmk2 までが char * 型になっています。仕様書を見てもこのあたりの説明はなさそうでした。
(2024-12-09 追記)仕様書の715行目に次の記述がありました。
char Hiss[764]; // His-RST 及びその他の項目のバッファ char *Myrs, *Freq, *Mode, *Name, *Qth, *Rmk1, *Rmk2; // バッファへのポインタ
Myrs以下のメンバーの値を保持する領域はどこに確保されるのでしょうか。HissからRmk2までのメンバーの右側にはコメントとして数値が書かれています。これらをすべて足すと764となり、Hissの配列サイズと同じになります。おそらくMyrs以下のメンバーはHissの配列内の位置を指すポインターなのかもしれません。それでは、C/C++側から見たアドレス(ポインター)の値を見てみることにします。
まずは、Visual Studioでコンソールプロジェクトを作ります。ターゲットのプラットフォームをx86、文字セットをマルチバイト文字セットにして、次のようなコードを作成します。
#include <iostream> #include <Windows.h> #include "HAMLOG50.H" typedef int (WINAPI* PHAMLOGOPEN)(HDC, TThLog*, const char*, const int); typedef void (WINAPI* PHAMLOGCLOSE)(TThLog*, const int); int main() { HMODULE h = ::LoadLibrary("c:\\path\\to\\Hamlog50.dll"); PHAMLOGOPEN hamlogOpen = (PHAMLOGOPEN)::GetProcAddress(h, "HamlogOpen"); PHAMLOGCLOSE hamlogClose = (PHAMLOGCLOSE)::GetProcAddress(h, "HamlogClose"); TThLog log; hamlogOpen(NULL, &log, "c:\\path\\to\\HAMLOG.hdb", 0); printf("Hiss %p %d\n", log.Qso.Hiss, log.Qso.Myrs - log.Qso.Hiss); printf("Myrs %p %d\n", log.Qso.Myrs, log.Qso.Freq - log.Qso.Myrs); printf("Freq %p %d\n", log.Qso.Freq, log.Qso.Mode - log.Qso.Freq); printf("Mode %p %d\n", log.Qso.Mode, log.Qso.Name - log.Qso.Mode); printf("Name %p %d\n", log.Qso.Name, log.Qso.Qth - log.Qso.Name); printf("Qth %p %d\n", log.Qso.Qth, log.Qso.Rmk1 - log.Qso.Qth); printf("Rmk1 %p %d\n", log.Qso.Rmk1, log.Qso.Rmk2 - log.Qso.Rmk1); printf("Rmk2 %p\n", log.Qso.Rmk2); printf("HissLen %d\n", log.Qso.HissLen); printf("MyrsLen %d\n", log.Qso.MyrsLen); printf("FreqLen %d\n", log.Qso.FreqLen); printf("ModeLen %d\n", log.Qso.ModeLen); printf("NameLen %d\n", log.Qso.NameLen); printf("QthLen %d\n", log.Qso.QthLen); printf("Rmk1Len %d\n", log.Qso.Rmk1Len); printf("Rmk2Len %d\n", log.Qso.Rmk2Len); hamlogClose(&log, 0); }
実行すると次のように表示されました。Myrs以下のメンバーは、char [764]型のHissの領域内を指していることがわかります。
Hiss 001AE899 4 Myrs 001AE89D 4 Freq 001AE8A1 8 Mode 001AE8A9 5 Name 001AE8AE 13 Qth 001AE8BB 29 Rmk1 001AE8D8 55 Rmk2 001AE90F HissLen 3 MyrsLen 3 FreqLen 7 ModeLen 4 NameLen 12 QthLen 28 Rmk1Len 54 Rmk2Len 54
Turbo HAMLOGのマニュアル1には次のように記載されていて、各領域はヌル終端文字の分も含まれているようです。
変更可能な幅は、次のとおりです。 His 初期値3バイト最大12 My 初期値3バイト最大12 Freq 初期値7バイト最大16 Mode 初期値4バイト最大16 Name 初期値12バイト最大64 QTH 初期値28バイト最大128 Remarks1 初期値54バイト最大254 Remarks2 初期値54バイト最大254
このようなデータ構造にしたのは、Hiss以下のデータ項目のサイズを変更するための工夫のようです。
C/C++側からポインターの値を見ようと思ったのは、Python側からc_char_p型のメンバーのアドレス値が見られなかったからなのですが、c_void_p型にすればアドレス値を取得できることがわかりました。
class TQsoBuff(ctypes.Structure): _pack_ = 1 _fields_ = [ ("Calls", c_ubyte * 21), ("Date", c_ubyte * 9), ("Time", c_ubyte * 7), ("Code", c_ubyte * 7), ("Glid", c_ubyte * 7), ("Qsl", c_ubyte * 4), ("Flag1", WORD), ("Hiss", c_ubyte * 764), ("Myrs", c_void_p), # c_char_p から c_void_p へ変更。以下 Rmk2 まで同じ。 ("Freq", c_void_p), ("Mode", c_void_p) ("Name", c_void_p), ("Qth", c_void_p), ("Rmk1", c_void_p), ("Rmk2", c_void_p), ("HissLen", BYTE), ("MyrsLen", BYTE), ("FreqLen", BYTE), ("ModeLen", BYTE), ("NameLen", BYTE), ("QthLen", BYTE), ("Rmk1Len", BYTE), ("Rmk2Len", BYTE) ]
Python側でアドレス値を表示してみます。配列型のメンバーは addressof でアドレス値を取得しています。
print(addressof(log.Qso.Calls), addressof(log.Qso.Date) - addressof(log.Qso.Calls)) print(addressof(log.Qso.Date), addressof(log.Qso.Time) - addressof(log.Qso.Date)) print(addressof(log.Qso.Time), addressof(log.Qso.Code) - addressof(log.Qso.Time)) print(addressof(log.Qso.Code), addressof(log.Qso.Glid) - addressof(log.Qso.Code)) print(addressof(log.Qso.Glid), addressof(log.Qso.Qsl) - addressof(log.Qso.Glid)) print(addressof(log.Qso.Qsl), addressof(log.Qso.Hiss) - addressof(log.Qso.Qsl)) print(addressof(log.Qso.Hiss), log.Qso.Myrs - addressof(log.Qso.Hiss)) print(log.Qso.Myrs, log.Qso.Freq - log.Qso.Myrs) print(log.Qso.Freq, log.Qso.Mode - log.Qso.Freq) print(log.Qso.Mode, log.Qso.Name - log.Qso.Mode) print(log.Qso.Name, log.Qso.Qth - log.Qso.Name) print(log.Qso.Qth, log.Qso.Rmk1 - log.Qso.Qth) print(log.Qso.Rmk1, log.Qso.Rmk2 - log.Qso.Rmk1) print(log.Qso.Rmk2)
実行すると、次のように表示されました。「6」と表示されているところはDWORD型のFlag1のサイズ 2 バイトを含みます。DWORD型のアドレス値の取得ができなさそうでしたので飛ばしました。
13821960 21 13821981 9 13821990 7 13821997 7 13822004 7 13822011 6 # DWORD型のFlag1のサイズ 2 バイトを含む 13822017 4 13822021 4 13822025 8 13822033 5 13822038 13 13822051 29 13822080 55 13822135
Python側からもMyrsメンバー以下のアドレス値が取れるようになりました。各メンバーには ctypes.memset で値を設定すればTurbo HAMLOGデータベースに登録ができそうです。
次回はTurbo HAMLOGデータベースへの交信データの登録を試したいと思います。
JI1JDI
ゆるく楽しくアマチュア無線とプログラミングを楽しんでいます。 scrapbox.io
- Turbo HAMLOG/Win取扱説明書, 「データ項目の幅変更」, https://hamlog.sakura.ne.jp/html/HID00035.html↩