Turbo HAMLOGのデータをPythonで操作する(2)

前回はTurbo HAMLOGのライブラリHAMLOG50.DLLをPythonから操作して、HAMLOGデータベースに保存されているデータを読み込むところまで実装してみました。

ji1jdi.hatenablog.com

今回は実装していたときに気になったところを調べてみます。

交信データの読み書きに使用するデータ構造は次のようになっていました。

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


  1. Turbo HAMLOG/Win取扱説明書, 「データ項目の幅変更」, https://hamlog.sakura.ne.jp/html/HID00035.html