[C++] 68 動的ライブラリdylibの作成 初回 split

[M1 Mac, Big Sur 11.6.7, clang 13.0.0, no IDE]

前回[C++] 67で作成した関数をsplitクラスにまとめて動的ライブラリdylibにしました。このファイルはプロジェクトとは別に外部クラス用のmylibディレクトリに保管します。dylibファイルはオブジェクトファイルと同等と考えると理解しやすいです。

ライブラリのヘッダファイルをincludeして、クラスをオブジェクト化すると、クラスのメンバ関数として使用することができます。

これで他のプログラムからもsplitクラスが利用可能になりました。

clang++ -dynamiclib -o split.dylib split.cpp -I/mylib/include
#ifndef SPLIT_H
#define SPLIT_H

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <vector>

using std::string; using std::vector;

class split{
    
public:
// 文字列を文字列delで分割しリスト化する関数
vector<string> splits(string str, char del);

// 分割リストのstart番目からend番目までの要素を結合する関数
string splitjoin(string str, char del, int start, int end);

// 作業ディレクトリを変更する関数
int chCWD(string str, char del, int start, int end);
};

#endif
#include "split.h"

using std::string; using std::vector;

// 文字列を文字列delで分割しリスト化する関数
vector<string> split::splits(string str, char del) {
    int first = 0;
    int last = str.find_first_of(del);
 
    vector<string> result;
 
    while (first < str.size()) {
        string subStr(str, first, last - first);
 
        result.push_back(subStr);
 
        first = last + 1;
        last = str.find_first_of(del, first);
 
        if (last == string::npos) {
            last = str.size();
        }
    }
    return result;
}
// 分割リストのstart番目からend番目までの要素を結合する関数
string split::splitjoin(string str, char del, int start, int end){
    string result2;
    vector<string> list = split::splits(str, del);

    vector<string> list2;
    for (int i = 0; i < list.size(); i++){
        if (end < 0){
            end += list.size();
        }
        if (i >= start && i <= end) {
            list2.push_back(list[i]);
        }
    }

    for (int i = 0; i< list2.size(); i++){
        result2.append(list[i] + del);
    }
    return result2;
}

// 作業ディレクトリを変更する関数
int split::chCWD(string str, char del, int start, int end){
    string new_str = split::splitjoin(str, del, start, end);

    int rtn = chdir(new_str.c_str());
    return rtn;
}
# includeパス(-I)
INCLUDE = -I./include  -I/usr/local/include/FL -I/mylib/include // 実際は絶対パス

# ライブラリ直接パス(-l)
LIBRARY = -lpng16 -lz -ljpeg /mylib/lib/split.dylib
<該当箇所のみ>

#include "split.h"

class split spt;

int main(int argc, char **argv) {

    vector<string> l = spt.splits(string(argv[0]), '/');

    // 作業ディレクトリを変更
    int rtn = spt.chCWD(argv[0], '/', 0, -2);
    cout << rtn << endl ;

[C++] 67 アプリ画像ファイルの有効パス その2 : 作業ディレクトリ変更

[M1 Mac, Big Sur 11.6.7, clang 13.0.0, no IDE]

実行ファイルのあるところに作業ディレクトリを変更して相対パスが使えないか検証しました。

実行ファイルのディレクトリの文字列を作成するために文字列分割関数と文字列分割/再結合関数を作りました。これらは後日ライブラリにする予定です。

検証の方は成功しました。手間は掛かりますが、作業ディレクトリを実行ファイルのディレクトリに変更することで相対パスでの画像ファイルの使用は実行ファイル、appファイル共に可能になります。

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_PNG_Image.H>
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <vector>

using std::string; using std::vector;
using std::cout; using std::endl;

Fl_Window* window;
Fl_Box* picturebox;
Fl_PNG_Image* pngimage;  

// 文字列を文字列delで分割しリスト化する関数
vector<string> split(string str, char del) {
    int first = 0;
    int last = str.find_first_of(del);
    vector<string> result;
 
    while (first < str.size()) {
        string subStr(str, first, last - first);
        result.push_back(subStr);
 
        first = last + 1;
        last = str.find_first_of(del, first);
 
        if (last == string::npos) {
            last = str.size();
        }
    }
    return result;
}
// 分割リストのstart番目からend番目までの要素を再結合する関数
string splitjoin(string str, char del, int start, int end){
    string result2;
    vector<string> list = split(str, del);

    vector<string> list2;
    for (int i = 0; i < list.size(); i++){
        if (end < 0){
            end += list.size();
        }
        if (i >= start && i <= end) {
            list2.push_back(list[i]);
        }
    }
    for (int i = 0; i< list2.size(); i++){
        result2.append(list[i] + del);
    }
    return result2;
}

int main(int argc, char **argv) {
    // 変更前の作業ディレクトリ確認
    char tmp[256];
    getcwd(tmp, 256);
    cout << "Current working directory: " << tmp << endl;

    // 実行ファイルのパスを出力
    cout << argv[0] << endl;

    // パスをスラッシュで分割しリストにする
    vector<string> l = split(argv[0], '/');

    // リストの内容確認
    int i = 0;
    for (const auto &item : l) {
        cout << i << " " << item << "; ";
        i++;
    }
    cout << endl;

    // リストの0番目から-2番目(最後から2番目)までの文字列を結合して新ディレクトリ作成
    string new_path = splitjoin(argv[0], '/', 0, -2);
    cout << new_path << endl;
    
    // 作業ディレクトリを変更
    int rtn = chdir(new_path.c_str());
    cout << rtn << endl ;

    // 変更後の作業ディレクトリ確認
    getcwd(tmp, 256);
    cout << "Current working directory: " << tmp << endl;

    Fl::set_font(FL_HELVETICA, "Osaka");
    window = new Fl_Window(100,100,360,220,"PLAIN");
    window->color(fl_rgb_color(131,204,210));

    pngimage = new Fl_PNG_Image("star.png");
    picturebox = new Fl_Box(20, 20, 120, 120);
    picturebox->box(FL_UP_BOX);
    picturebox->image(pngimage); 
    picturebox->redraw();  
    window->add(picturebox);

    window->end();
    window->show(argc, argv);
    
    return Fl::run();
}
Current working directory: /Users/[ユーザID]
/Volumes/DATA_m1/code/cpp/projects/plainPNG/bin/plain
0 ; 1 Volumes; 2 DATA_m1; 3 code; 4 cpp; 5 projects; 6 plainPNG; 7 bin; 8 plain; 
/Volumes/DATA_m1/code/cpp/projects/plainPNG/bin/
0
Current working directory: /Volumes/DATA_m1/code/cpp/projects/plainPNG/bin

[Obj-C++] 01 Objective-C++への移行

[M1 Mac, Big Sur 11.6.5, clang 13.0.0, no Xcode]

C++の機能も使えるようにするため、Objective-CからObjective-C++へ移行します。

mファイルの拡張子をmmに変えるだけでOKです。coutで動作確認しました。

#include <Cocoa/Cocoa.h>
#include <iostream>

using std::cout; using std::endl;

int num = 1;

// Objective-C++
NSLog(@"Objective-C++ num %d",num);

// C++
cout << "C++ num " << num << endl;

--------------------------------------------------
出力
--------------------------------------------------
Objective-C++ num 1
C++ num 1

[C++] 65 FLTK : xlsx変換アプリ / MakefileからCMakeLists.txtへの移植

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]

自製Makefileの生成するコマンドを見ながらCMakeLists.txtへ移植しました。

CMakeが作成したMakefileが生成するコマンドについては以下の行を追記して内容を確認できます。

set(CMAKE_VERBOSE_MAKEFILE ON)

Objective-Cのappファイルと同様、こちらもボタンを押下してもクラッシュしませんでした。自製Makefileではできない補完をしているのでしょう。

C++でappファイルに問題がないとなるとObjective-Cに戻って開発を進める必要がありません。

Cocoaアプリの統一感のある洗練された外観も捨てがたいので何か別のアプリで再チャレンジしたいところです。

cmake_minimum_required(VERSION 3.1)

# Project
Project(XlsxConvertor VERSION 1.0.0)
FIND_PACKAGE(FLTK REQUIRED)
FIND_PACKAGE(OpenGL REQUIRED)
find_library(COCOA Cocoa)

set(CMAKE_CXX_STANDARD 11)

# make時の生成コマンドをターミナルに表示する(デフォルトでは非表示)
set(CMAKE_VERBOSE_MAKEFILE ON)

# Info.plist
set(MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION})
set(MACOSX_BUNDLE_COPYRIGHT "Copyright (C) 2022")
set(MACOSX_BUNDLE_INFO_STRING "Convert tool for XLSX")

# XlsxConvertor
add_executable(${PROJECT_NAME}
    MACOSX_BUNDLE
    ./src/XlsxConvertor.cpp
    ./src/process.cpp
    ./src/modalDialog.cpp
    ./include/process.h
    ./include/modalDialog.h
    )

set(INCDIR /include)
set(INCDIR ${INCDIR} /opt/homebrew/Cellar/libpng/1.6.37/include)
set(INCDIR ${INCDIR} /opt/homebrew/Cellar/fltk/1.3.8/include)
set(INCDIR ${INCDIR} /opt/homebrew/Cellar/jpeg/9e/include)
set(INCDIR ${INCDIR} /Library/Frameworks/Python.framework/Versions/3.10/include/python3.10)
include_directories(${INCDIR})

set(LIBS dl)
set(LIBS ${LIBS} z pthread c++)
set(LIBS ${LIBS} /opt/homebrew/Cellar/fltk/1.3.8/lib/libfltk_forms.dylib)
set(LIBS ${LIBS} /opt/homebrew/Cellar/fltk/1.3.8/lib/libfltk_gl.dylib)
set(LIBS ${LIBS} /opt/homebrew/Cellar/fltk/1.3.8/lib/libfltk_images.dylib)
set(LIBS ${LIBS} /opt/homebrew/Cellar/jpeg/9e/lib/libjpeg.dylib)
set(LIBS ${LIBS} /opt/homebrew/Cellar/libpng/1.6.37/lib/libpng.dylib)
set(LIBS ${LIBS} /opt/homebrew/Cellar/fltk/1.3.8/lib/libfltk.dylib)
set(LIBS ${LIBS} /Library/Frameworks/Python.framework/Versions/3.10/lib/libpython3.10.dylib)

set(LIBDIR /opt/homebrew/Cellar/jpeg/9e/lib)
set(LIBDIR ${LIBDIR} /opt/homebrew/Cellar/libpng/1.6.37/lib)
set(LIBDIR ${LIBDIR} /opt/homebrew/Cellar/fltk/1.3.8/lib)
set(LIBDIR ${LIBDIR} /usr/local/lib /Library/Frameworks/Python.framework/Versions/3.10/lib)
link_directories(${LIBDIR})

target_link_libraries(${PROJECT_NAME}
    ${FLTK}
    ${OpenGL}
    ${LIBS}
    ${COCOA})

[C++] 64 FLTK : ドラッグ&ドロップ

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]

Objective-Cのドラッグ&ドロップが今の私にはあまりにも難解なので、FLTKではどうやって実装していたか振り返ります。かなり簡単に実装できます。

FL_Boxでファイルをドロップする領域を指定し、ドロップされたファイルの情報をFL_Boxの引数で指定したFL_Inputに渡します。単純明快です。

Objective-Cのドラッグ&ドロップについては必須ではなく努力目標にして、FLTKアプリのCMakeLists.txt作成に優先して取り組むことにします。

同じ内容の実装でもC++(FLTK)とObjective-Cでこれだけ難易度が違うと難しい方を学ぼうとする気力がなかなかわきません。

Xcodeで書けばいいのですが、xibファイルやStoryboardを扱いたくないのでモチベが上がらないです。

<該当箇所のみ>

class Box : Fl_Box {
    Fl_Input* input_line2;

    public:
        Box(int, int, int, int, Fl_Input*);
    private:
        auto handle(int) -> int override;
};

Box::Box(int x, int y, int width_input, int height_input, Fl_Input* input) : 
    Fl_Box(FL_NO_BOX, x, y, width_input, height_input, "") {
    this->input_line2 = input;
    }

auto Box::handle(int event) -> int {
    switch (event) {
        case FL_DND_DRAG:
        case FL_DND_ENTER:
        case FL_DND_RELEASE:
            return 1;

        case FL_PASTE:
            input_line2->value(Fl::event_text());
            input_line2->textsize(12);
            input_line2->textfont(FL_HELVETICA);

            return 1;

        default:
            return Fl_Box::handle(event);
    }
}

int main(int argc, char **argv) {
    Box *box = new Box(0, 0, 360,220, input_line);
}

[C++] 63 FLTK : xlsx変換アプリ / appファイルの不具合3

[M1 Mac, Big Sur 11.6.5, Python 3.10.4]

デバッグ用コードを走らせ、実行内容をチェックしました。

sysPath 6個に対してGetPath 3個となっており、pathを追加できていないだけでなく数も合っていません。

つまりsys.pathとPython/C APIのmodule search pathは一致しないということが判明しました。集合で表現するとmodule search path ⊂ sys.pathになります。

module search pathの設定がPy_SetPath()やPYTHONPATH以外の方法でできないとなると、これ以上の追究は厳しくなります。

#define PY_SSIZE_T_CLEAN
#include "process.h"
#include </Library/Frameworks/Python.framework/Versions/3.10/include/python3.10/Python.h>
#include <iostream>
#include <string.h>
#include <stdlib.h>

#define PATH L"/Python/library/python_module:/Library/Frameworks/Python.framework/Versions/3.10/lib/python310.zip:/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10:/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/lib-dynload"

using std::string;

string XlsxToList(const char* path) {
    // Py_SetPath(PATH);

    Py_Initialize();
	
	// PyObject* sysPath = PySys_GetObject("path");
	PyObject* sys = PyImport_ImportModule( "sys" );
	PyObject* sysPath = PyObject_GetAttrString( sys, "path" );
	std::cout << "追加前path数 " << PyList_Size(sysPath) << std::endl;
	std::cout << "PyObject* sysPath通過" << std::endl;

	PyObject* module_path = PyUnicode_FromString("/Python/library/python_module");
	std::cout << "PyObject* module_path通過" << std::endl;
	
	int append_bool = PyList_Append(sysPath, module_path);
	std::cout << "PyList_Append通過" << std::endl;
	std::cout << append_bool << std::endl;

	std::cout << "追加後path数 " << PyList_Size(sysPath) << std::endl;

	// 	module search pathを出力し、パスの内容と個数を確認
	std::wcout << Py_GetPath() << std::endl;

    // pyファイルのモジュール化
    PyObject* myModule = PyImport_ImportModule((char*)"test");
	std::cout << "myModule通過" << std::endl;

	const char* function = "xlsx_to_list";
	int attr_bool = PyObject_HasAttrString(myModule,function);
	std::cout << "attr_bool通過" << std::endl;
	std::cout << attr_bool << std::endl;
    
    // pyファイル内の関数を指定 ここでエラー発生
    PyObject* myFunction = PyObject_GetAttrString(myModule,function);
    
    // 関数の引数を設定
    PyObject* args = PyTuple_Pack(1,PyUnicode_FromString(path));
    
    // 関数を実行し戻り値をPyObjectとして取得
    PyObject* myResult = PyObject_CallObject(myFunction,args);
    
    
    // PyObjectをconst char*に変換
    const char* result = PyUnicode_AsUTF8(myResult);

    std::cout << result << std::endl;

    return result;

    Py_FinalizeEx();
    
}
--------------------------------------------------
出力
--------------------------------------------------
追加前path数 5
PyObject* sysPath通過
PyObject* module_path通過
PyList_Append通過
0
追加後path数 6
/Library/Frameworks/Python.framework/Versions/3.10/lib/python310.zip:/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10:/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/lib-dynload
myModule通過
attr_bool通過
0
Segmentation fault: 11

[C++] 62 FLTK : xlsx変換アプリ / appファイルの不具合2

[M1 Mac, Big Sur 11.6.5, Python 3.10.4]

sys.pathをPy_SetPathで強制的に書き換えたところ、実行ファイルの方も動かなくなりました。力技が過ぎたようです。

sys.pathの文字列が全く同じでもメモリアドレスが変わったためにEXC_BAD_ACCESSになっているのでしょうか。

appファイルについてはこれで完全にお手上げとなりました。実行ファイルでアプリを完成させる目処は立っていますが、言語を変更してObjective-Cで同じアプリを作るかどうか迷っています。

#define PY_SSIZE_T_CLEAN
#include "process.h"
#include </Library/Frameworks/Python.framework/Versions/3.10/include/python3.10/Python.h>
#include <iostream>
#include <string.h>
#include <stdlib.h>

#define PATH L"/Python/library/python_module:/Library/Frameworks/Python.framework/Versions/3.10/lib/python310.zip:/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10:/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/lib-dynload"

using std::string;

string XlsxToList(const char* path) {
    
    // sys.pathの書き換え
    Py_SetPath(PATH);

    Py_Initialize();

    // sys.pathの確認
    std::wcout << Py_GetPath() << std::endl;

    // pyファイルのモジュール化
    PyObject* myModule = PyImport_ImportModule((char*)"test");
    
    // pyファイル内の関数を指定 今度は実行ファイルにてここでエラー発生
    const char* function = "xlsx_to_list";
    PyObject* myFunction = PyObject_GetAttrString(myModule,function);
    
    // 関数の引数を設定
    PyObject* args = PyTuple_Pack(1,PyUnicode_FromString(path));
    
    // 関数を実行し戻り値をPyObjectとして取得
    PyObject* myResult = PyObject_CallObject(myFunction,args);
    
    // PyObjectをconst char*に変換
    const char* result = PyUnicode_AsUTF8(myResult);

    std::cout << result << std::endl;

    return result;

    Py_FinalizeEx();
    
}

[C++] 61 FLTK : xlsx変換アプリ / appファイルの不具合

[M1 Mac, Big Sur 11.6.5, Python 3.10.4]

製作中のアプリですが、前回の記事でも書いたように実行ファイルでは正常に動作し、appファイルは起動はするもののボタンを押すと落ちてしまいます。

原因を探ったところ、どうやらPYTHONPATHを認識できないためにPyImport_ImportModuleが働かずモジュールが生成していないようでした。PYTHONPATHの__pycache__ディレクトリにpycファイルが生成していないことで判明しました。

appファイル内の/Contents/Resourcesにpyファイルを置き、info.plistで認識させようとするなどいろいろ試しましたが、うまくいきませんでした。

結局解決には至らず、やむなくこのまま次に進みます。Objective-Cであれば何らかの方法が見つかるかもしれません。

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

using std::string;

const char* XlsxToList(const char* path) {

    Py_Initialize();

    // sys.pathを確認 wchar_tなのでwcoutで出力
    std::wcout << Py_GetPath() << std::endl;

    // pyファイルのモジュール化 appファイルではモジュール化できない PYTHONPATHに到達できていない
    PyObject* myModule = PyImport_ImportModule("test");

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

    // 関数の引数を設定
    PyObject* args = PyTuple_Pack(1,PyUnicode_FromString(path));
    
    // 関数を実行し戻り値をPyObjectとして取得
    PyObject* myResult = PyObject_CallObject(myFunction,args);
    
    // PyObjectをconst char*に変換
    const char* result = PyUnicode_AsUTF8(myResult);

    std::cout << result << std::endl;

    return result;

    Py_FinalizeEx();
    
}

[C++] 60 FLTK : xlsx変換アプリ / Pythonスクリプトのモジュール化

[M1 Mac, Big Sur 11.6.5, Python 3.10.4]

前回まではPythonスクリプトの埋め込みについて調べていましたが、ついにモジュール化に成功しました。

埋め込みでは処理が一方通行だったのが、モジュール化により双方向でデータのやり取りができるようになります。Pythonスクリプトを1行ずつバラして埋め込む必要がなくなるのでだいぶ楽です。

今回のケースでは、Pythonスクリプトで作成したリストをC++コードが文字列として受け取り、Fl_Multiline_Outputに表示させています。

現段階では実行ファイルでしかできませんが、早くappファイルでもできるようにしたいところです。appファイルでは実行ボタンを押すとなぜか落ちてしまいます。

なお.bash_profileにてPYTHONPATHを設定しないとPythonスクリプトを読み込めないので要注意です。

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

using std::string;

string XlsxToList(const char* path) {
    Py_Initialize();

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

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

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

    // 関数の引数を設定
    PyObject* args = PyTuple_Pack(1,PyUnicode_FromString(path));

    // 関数を実行し戻り値をPyObjectとして取得
    PyObject* myResult = PyObject_CallObject(myFunction,args);

    // PyObjectをconst char*に変換
    const char* result = PyUnicode_AsUTF8(myResult);

    return string(result);

    Py_Finalize();
}
import openpyxl

def xlsx_to_list(path):
    wb = openpyxl.load_workbook(path)
    ws = wb.worksheets[0]

    color_name = []
    for cell in ws['A']:
        color_name.append(cell.value)
        
    return str(color_name)
<該当箇所のみ>
void xtol(){
    const char *path = input_line->value();
    string result = XlsxToList(path);
    output_line->insert(result.c_str());
}
# pyファイルのあるディレクトリを指定

export PYTHONPATH="/Python/library/python_module"

Python/C API リファレンスマニュアル