[C++] 356 ChatGPTアプリの製作 その41 FLTKマルチスレッド化 promise, future

[Mac M2 Pro 12CPU, Sonoma 14.3.1, clang++ 15.0.0, FLTK 1.3.8]

ChatGPTアプリのアップデートは久々です。

cURL通信を中断する機能を実装するため、マルチスレッド対応に着手しました。

まずはリクエストの送信を別スレッドにしてデタッチ(切り離し)するようにしました。

これまでは受信があるまでGUIが固まっていたのが、他のボタンを押したりできるようになります。

ただし別スレッドの動作が終わってもその信号をGUIが受け取ることはできないため、システム音で知らせるようにしました。システム音が聞こえると自分でカーソルを少し動かして、メインスレッドに戻します。メインスレッドに戻るとリクエストの結果がGUIに表示されます。

次は中断ボタンを押すとコールバック関数で受信をabortするようにします。

#include <thread>
#include <future>

void sendCB(Fl_Widget*, void* data, std::promise<void>&& p) {

    <中略>

    p.set_value();
}

void sendCBWrapper(Fl_Widget* w, void* v) {
    // promiseとfutureを作成
    std::promise<void> p;
    std::future<void> f = p.get_future();
    
    // sendCBを新しいスレッドで実行し、promiseを渡す
    std::thread([w, v, p = std::move(p)]() mutable {
        sendCB(w, v, std::move(p));
    }).detach();
    
    // 別のスレッドでfutureの結果を待機
    std::thread([f = std::move(f)]() mutable {
        f.wait(); // sendCBの終了を待機
        std::system("afplay /System/Library/Sounds/Submarine.aiff"); // システム音を鳴らす
        cout << "sendCB終了" << endl;
    }).detach();
}

[C++] 355 ChatGPTアプリの製作 その40 GPT-4 Turbo with visionへの対応

[Mac M2 Pro 12CPU, MacOS Ventura 13.6, clang 15.0.0]

自製ChatGPTアプリをGPT-4 Turbo with visionに対応させました。

画像のURLを送ると内容を読み取り、プロンプトに対応します。

漢字、ひらがな、カタカナなど非ラテン文字は読めません。これらが画像に含まれているとレスポンスしなくなるので、モザイクを掛けるなり前処理が必要です。

プログラミングでUIについて質問する際に画像を使うとやりやすいです。特に見てほしいところを色線で囲むなど、工夫を入れるとより深い分析をしてくれます。ここまでできるとプログラミングスクールにとどまらず教育業界全体が相当な危機でしょう。

GPT-4までは心強いパートナーという感じでしたが、with visionになって凄まじい眼力を身に付け、いささか脅威を覚えるようになりました。

今のAIがどのような状況になっているのか、もっと周知しないとヤバい気がします。

[C++] 354 ChatGPTアプリの製作 その39 GPT-4 Turboへの対応

[Mac M2 Pro 12CPU, MacOS Ventura 13.6, clang 15.0.0]

自製ChatGPTアプリを本日11/7未明よりサービス開始となったGPT-4 Turboに対応させました。

たまに英語で回答が返ってくるため、最初の役割設定(role : sys)で日本語回答を必須にしました。

トレーニングデータは23年4月までですから、AppleのvisionOSに関する情報はカバーしていません。

トークンの上限がGPT-4の8,192から128,000になりました。ChatGPTでのやり取りが途切れることも大分減りそうです。また料金はGPT-4の3分の1なので、ある程度気にせず利用できます。

[C++] 353 Pythonスクリプトの実行(再) / Rustの現状

[Mac M2 Pro 12CPU, MacOS Ventura 13.6, clang 15.0.0]

以前にも扱いましたが、おさらいしておきます。

Rustでも簡単にPythonスクリプトを実行できます。ただRustは安全性が担保されない限りコーディングがなかなか先に進まないので、趣味としては楽しめないです。Javaのぬるぽ地獄に似たような窮屈さを感じます。

昨年2022年あたりがピークだったのか、今現在Rustブームは落ち着いているようです。プロダクト責任者に近い上級プログラマだけが熱を上げている印象です。Pythonのようなお手軽さが全くない言語ですから、ユーザーの新規参入が先細りにならないか心配です。

私としては、もしビジネスが絡んだら仕方なく使うというスタンスです。

#define PY_SSIZE_T_CLEAN
#include </Library/Frameworks/Python.framework/Versions/3.10/include/python3.10/Python.h>

void sheetNaming() {
    Py_Initialize();

    // pyファイルの指定(OutingCosts.py)
    PyObject* myModuleString = PyUnicode_FromString((char*)"OutingCosts");

    // pyファイルのモジュール化
    PyObject* myModule = PyImport_Import(myModuleString);

    // pyファイル内の関数を指定
    PyObject* myFunction = PyObject_GetAttrString(myModule,(char*)"outingcosts");

    // 関数の引数を設定
    PyObject* args = NULL;
    // 引数2つの場合
    // PyObject* args = PyTuple_Pack(2,PyUnicode_FromString(path),PyUnicode_FromString(path2)))

    // 関数を実行
    PyObject* myResult = PyObject_CallObject(myFunction, args);

    Py_Finalize();

    return ;

}

int main() {
    sheetNaming();

    return 0;
}
# コンパイラ設定
COMPILER = clang++
DEBUG = -g

# フラグ設定
CPPFLAGS = -std=c++20
LDFLAGS = -lc++

# includeパス(-I)
INCLUDE = -I./include \
-I/Library/Frameworks/Python.framework/Versions/3.10/include/python3.10

# ライブラリ(-l)
LIBRARY_l = -lpython3.10

# ライブラリパス(-L)
LIBRARY_L = -L/usr/local/lib -L/Library/Frameworks/Python.framework/Versions/3.10/lib

# ソースファイル
SRCDIR = ./src
SRCS = $(shell find $(SRCDIR) -type f)

# オブジェクトファイル
OBJDIR = ./obj
OBJS = $(addprefix $(OBJDIR), $(patsubst ./src/%.cpp,/%.o,$(SRCS)))

# 実行ファイル
TARGETDIR = ./bin
TARGET = test

# cppファイルからoファイル作成 $<:依存ファイル
$(OBJDIR)/%.o: $(SRCDIR)/%.cpp
	$(COMPILER) $(CPPFLAGS) $(INCLUDE) $(DEBUG) -o $@ -c $<

# oファイルから実行ファイル作成
$(TARGET):$(OBJS)
	$(COMPILER) -o $(TARGETDIR)/$@ $(OBJS) $(LIBRARY_l) $(LIBRARY_L) $(LDFLAGS)

# コンパイル&ビルド
.PHONY:all
all: clean $(OBJS) $(TARGET)

# oファイル・実行ファイル削除
.PHONY:clean
clean:
	rm -rf $(OBJS) $(TARGETDIR)/$(TARGET) $(TARGET).app
# 導入するPythonスクリプトのあるディレクトリ設定
export PYTHONPATH="/code/Python/library/python_module"

# importする自製ライブラリのあるディレクトリ設定
export PYTHONPATH="/code/Python/library"
RustでのPythonスクリプト実行例
Objective-CでのPythonスクリプト実行例

[C++] 351 BBS閲覧アプリの製作 その32 掲示板クリック時に落ちる / クラッシュレポートの閲覧

[Mac M2 Pro 12CPU, MacOS Ventura 13.6, clang 15.0.0]

昨晩、自製BBS閲覧アプリで特定の掲示板をクリックするとアプリが落ちるようになってしまいました。

クラッシュレポートからXPath式を評価する際にエラーになることが判明しました。エラーになってもアプリが落ちないようにexecuteXpath関数の戻り値をNULL以外に変えるなど対処しましたが、結局うまくいきませんでした。

今朝になって正常動作するようになったものの、再発が懸念されます。クラッシュレポートは後からでもmacOSのコンソールアプリから閲覧できます。一応、エラーが発生しそうな箇所を修正して予防対策しておきました。戻り値云々は見当違いのようです。

当時の他の類似アプリでの挙動は確認できていませんが、ちょっとしたトラブルでもアプリが絶対に落ちないよう堅牢性を持たせたいものです。

// XPath式で要素を取得
xmlNodeSetPtr executeXpath(xmlDocPtr &doc, xmlChar *xpath_expr) {
    xmlXPathContextPtr xpath_context;
    xmlXPathObjectPtr  xpath_obj;

    xpath_context = xmlXPathNewContext(doc);
    if (xpath_context == NULL) {
        cerr << "Error: unable to create new XPath context" << endl;
        xmlFreeDoc(doc);
        return NULL;
    }
    xmlNodePtr node = xmlDocGetRootElement(doc);

    // XPath式を評価
    // 23/10/16 エラー発生, 23/10/17 回復
    xpath_obj = xmlXPathEvalExpression(xpath_expr, xpath_context);
    if (xmlXPathNodeSetIsEmpty(xpath_obj->nodesetval)) {
        cerr << "Error: unable to evaluate xpath expression" << endl;
        xmlXPathFreeContext(xpath_context);
        xmlFreeDoc(doc);
        return NULL;
    }

    return xpath_obj->nodesetval;
}
    xmlDoc* doc = htmlReadMemory(htmlBuffer.c_str(), htmlBuffer.size(), nullptr, nullptr, HTML_PARSE_RECOVER);
    if (doc) {
        xmlNodeSetPtr tradObj = executeXpath(doc, (xmlChar *)"//*[name()='small']/*[name()='a']");

        if (tradObj) {
            for (int i = 0; i < tradObj->nodeNr; i++) {
                xmlNodePtr node = tradObj->nodeTab[i];
                xmlChar* id0 = xmlGetProp(node, (xmlChar*)"href");
                xmlChar* title0 = xmlNodeGetContent(node);

                string id = convertToString(id0);
                id.erase(id.size() - 4);
                string title = convertToString(title0);
                // cout << "title: " << title << endl;

                idTitle.push_back(std::make_pair(id, title));
                
                xmlFree(id0);
                xmlFree(title0);
            }
        } else {
            return {}; // else部分がなかったので追記 23/10/17
        }
        xmlFreeDoc(doc);
    } else {
        return {}; // else部分がなかったので追記 23/10/17
    }
クラッシュレポートは後からでもコンソールアプリで確認可能
(保存期間は不明)

[C++] 350 ChatGPTアプリの製作 その37 libcrypto.libが見つからず起動しない

[Mac M2 Pro 12CPU, MacOS Ventura 13.6, clang 14.0.3]

今朝、自製ChatGPTアプリを起動しようとすると途中で落ちてしまうようになりました。

Homebrewのopensslライブラリを更新すると直りました。エラーレポートにあるlibcrypto.libは暗号化に関連するlibファイルです。

余談ですが、新macOS Sonomaは2023/10/11現在まだ初期バージョン14.0のままなので次のバージョンになったらサブ機に入れてみる予定です。

brew update & brew upgrade & brew install openssl
自製ChatGPTアプリの最新UI

参考サイト
Apple Developer Forums

[C++] 349 FLTK : ChatGPTアプリの製作 その36 jsonファイルをフォルダに振り分ける

[Mac M2 Pro 12CPU, MacOS Ventura 13.5, clang 14.0.3]

自製ChatGPTアプリに関する記事は3月以来5ヶ月ぶりです。記事にはしていませんが、これまで何度も更新を重ねグレードアップしています。

ChatGPTアプリでのやりとりはjsonファイルとして保存しています。大分ファイルがたまってきたため、年月フォルダ(今月の場合は”2308″)を自動作成して振り分けるMovJSボタンを実装しました。

ChatGPTにコードを書いてもらいました。一応コードはチェックして問題なさそうなのでビルド&実行したところ一発で成功しました。

実装完了までものの10分です。AIアシストでプログラミングすると生産性爆上がりです。

#include <iostream>
#include <filesystem>
#include <fstream>

namespace fs = std::filesystem;

void movJSONCB(Fl_Widget*, void*){
    fs::path srcDir = "/ChatGPT";

    // srcDirにあるjsonファイルのpathをリスト化する
    std::vector<fs::path> jsonFiles;
    for (const auto& entry : fs::directory_iterator(srcDir)) {
        if (entry.path().extension() == ".json") {
            jsonFiles.push_back(entry.path());
        }
    }
    
    // ファイル名の先頭4文字のフォルダを作成する
    for (const auto& jsonFile : jsonFiles) {
        std::string folderName = jsonFile.filename().stem().string().substr(0, 4);
        fs::path folderPath = srcDir / folderName;
        
        // フォルダがすでに存在する場合は上書き作成しない
        if (!fs::exists(folderPath)) {
            fs::create_directory(folderPath);
        }
        
        // ファイルをそれぞれのフォルダに振り分ける
        fs::path destinationPath = folderPath / jsonFile.filename();
        fs::rename(jsonFile, destinationPath);
    }
}

[C++] 348 size_tの大きさ

[Mac M2 Pro 12CPU, MacOS Ventura 13.3.1, clang 14.0.3]

size_tはサイズを表す符号なし整数型データです。

符号なしの整数型であればunsigned intでいいのではないかと考えることもできますが、size_tはポインタを格納する場合もあるため4バイトのunsigned intでは足りないです。8バイト必要になります(64ビットプラットフォームの場合)。

コードで出力しようとするとintと同じように扱えるので何のために存在しているのか疑問に思っていましたが、ようやく理解できました。

[C++] 347 BBS閲覧アプリの製作 その31 無効なシーケンス混入による文字コード変換失敗 iconv / CP932

[Mac M2 Pro 12CPU, MacOS Ventura 13.3.1, clang 14.0.3]
(今回からM2 Pro 12コアCPUを使用)

とあるdatファイルをCP932からUTF-8へ変換しようとしたところエラーになりました。

どうやら無効なシーケンスが混ざっているようです。

以下のようにConvertShiftJISToUTF8関数を修正しました。

string ConvertShiftJISToUTF8(const string& input) {
    string output;

    // iconv_t cd = iconv_open("UTF-8", "SHIFT_JISX0213");
    iconv_t cd = iconv_open("UTF-8", "CP932");
    if (cd == (iconv_t)-1) {
        std::cerr << "Error: Failed to open iconv" << std::endl;
        return output;
    }

    size_t inBytes = input.size();
    size_t outBytes = inBytes * 4;
    char* inBuf = const_cast<char*>(input.c_str());
    char* outBuf = new char[outBytes];
    char* outPtr = outBuf;

    while (iconv(cd, &inBuf, &inBytes, &outPtr, &outBytes) == (size_t)-1) {
        if (errno == EILSEQ) {
            std::cerr << "EILSEQ:入力バッファに無効なシーケンスが含まれている" << std::endl;
            inBuf += 1; // 無効なシーケンスの分だけ入力バッファを進める
            inBytes -= 1; // 無効なシーケンスの分だけ入力バッファのサイズを減らす

        } else if (errno == EINVAL) {
            std::cerr << "EINVAL:入力バッファの終端が不完全" << std::endl;
            delete[] outBuf;
            iconv_close(cd);

            return "";
        } else if (errno == E2BIG) {
            std::cerr << "E2BIG:出力バッファが不足している" << std::endl;
            delete[] outBuf;
            iconv_close(cd);

            return "";
        } else {
            std::cerr << "その他のエラー" << std::endl;
            delete[] outBuf;
            iconv_close(cd);

            return "";
        }
    }
    output.assign(outBuf, outPtr - outBuf);

    delete[] outBuf;
    iconv_close(cd);

    return output;
}

ChatGPTに有益なヒントをもらいましたが、例示するコードが実にいい加減で少し振り回されました。追及しても虚偽説明が積み上がっていくばかりで無意味なやりとりになります。ChatGPTはネット情報から引用するだけで、検証する能力は皆無ですね。まあそんなもんです。

datファイルがCP932ではなく他のシフトJISを採用している可能性も考えましたが、今回は当てはまりませんでした。

ちなみにCP932(Windows-31J)と他のシフトJISとの文字集合の関係は以下のオイラー図のようになります。CP932は黄色です。

User:Hissakun~commonswiki, CC 表示-継承 3.0, https://commons.wikimedia.org/w/index.php?curid=82138426による