[Obj-C] 01 コンソールアプリのMakefile

[M1 Mac, Big Sur 11.6.5, no Xcode]

Objective-CでのmacOSアプリ製作に着手しました。知識ゼロなので一から学んでいきます。GUIのXcodeは極力使わないようにします。

まずはコンソールアプリのMakefileを作成してみました。C++のテンプレを流用しています。今のところソースコードの内容についてはあまり理解していません。

次はWindowのあるアプリを作成します。

# コンパイラ
COMPILER = clang
DEBUG = -g

# オプション
CFLAGS =
LDFLAGS = -framework Cocoa

# includeパス(-I)
INCLUDE = -I.

# ソースファイル
SRCDIR = .
SRCS = $(SRCDIR)/test.m

# オブジェクトファイル
OBJDIR = .
OBJS = $(OBJDIR)/test.o

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

# コンパイル
$(OBJDIR)/test.o: $(SRCDIR)/test.m
	$(COMPILER) $(INCLUDE) $(DEBUG) -o $@ -c $<

# ビルド
$(TARGET):$(OBJS)
	$(COMPILER) -o $(TARGETDIR)/$@ $(OBJS) $(LDFLAGS)

# ファイル削除&ビルド
.PHONY:all
all: clean $(TARGET)

# ファイル削除
.PHONY:clean
clean:
	rm -rf $(OBJS) $(TARGETDIR)/$(TARGET)
#import "test.h"
 
@implementation AppDelegate
- (id) init {
    [super init];
    return self;
}
- (void) applicationDidFinishLaunching:(NSNotification *)aNotification{
    // アクティブ化
    [NSApp activateIgnoringOtherApps:YES];
}
@end
 
@implementation AppMenu
- (id) init {
    [super init];
    id item_app = [[NSMenuItem new] autorelease];
    [self addItem:item_app];
    id menu_app = [[NSMenu new] autorelease];
    [item_app setSubmenu:menu_app];
    id item_quit = [[NSMenuItem new] autorelease];
    [item_quit initWithTitle:@"Quit App" action:@selector(terminate:) keyEquivalent:@"q"];
    [menu_app addItem:item_quit];
    return self;
}
@end
 
int main(int argc, char *argv[]) {
    // メモリ管理
    [NSAutoreleasePool new];
    // NSApp作成
    [NSApplication sharedApplication];
    // setActivationPolicy
    [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
    // メニュー設定
    id main_menu = [[AppMenu new] autorelease];
    [NSApp setMainMenu:main_menu];
    // Delegate
    id delegate = [[AppDelegate new] autorelease];
    [NSApp setDelegate:delegate];
    // メインループ
    [NSApp run];

    return 0;
}
#import <Cocoa/Cocoa.h>
 
@interface AppMenu : NSMenu
@end
 
@interface AppDelegate : NSObject<NSApplicationDelegate>
@end

参考サイト

[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 リファレンスマニュアル

[C++] 58 FLTK : xlsx変換アプリ / Pythonの openpyxlライブラリ導入

[M1 Mac, Big Sur 11.6.5, Python 3.10.4]

Excelファイルを扱うFLTKアプリの作成に着手しました。GUIはよく使うガワの使い回しです。

まずは前々回から取り組んでいるExcelファイルから列データを取り出してリストにする機能を実装しました。A列の値とセル色をリストにします。色データはRGB16進数の頭にFFが付きます。このFFはいらないのでそのうち除去するようにします。

列数や取り出すデータの種類はいずれ選択できるようにしたいです。

#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;

int XlsxToList(const char* path) {

    Py_Initialize();

    PyRun_SimpleString("import openpyxl");

    string path_str = string(path);
    string wb_str = "wb = openpyxl.load_workbook('" + path_str +"')";
    PyRun_SimpleString(wb_str.c_str());
    PyRun_SimpleString("ws = wb.worksheets[0]");

    PyRun_SimpleString("color_name = []");
    PyRun_SimpleString("color_code = []");

    string for_str = "for cell in ws['A']: color_name.append(cell.value), color_code.append(cell.fill.fgColor.rgb)";
    PyRun_SimpleString(for_str.c_str());
        
    PyRun_SimpleString("print(color_name)");
    PyRun_SimpleString("print(color_code)");

    Py_Finalize();
    return 0;
}

[C++] 57 Pythonスクリプトを使ってExcelを操作する / for文

[M1 Mac, Big Sur 11.6.5, Python 3.10.4]

前回の続きです。

for文はワンライナーで書けば、PyRun_SimpleString()に使えます。バックスラッシュを入れてもOKです。

リスト内包表記のワンライナーは多用していましたが、if文やfor文の中身をカンマでつなぐとワンライナーになるというのは知りませんでした。

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

using std::string;

int main() {
    Py_Initialize();

    PyRun_SimpleString("import openpyxl");
    PyRun_SimpleString("wb = openpyxl.load_workbook('test.xlsx')");
    PyRun_SimpleString("ws = wb.worksheets[0]");

    PyRun_SimpleString("color_name = []");
    PyRun_SimpleString("color_code = []");

    string str = "for cell in ws['A']: color_name.append(cell.value), color_code.append(cell.fill.fgColor.rgb)";

    // バックスラッシュを使う場合
    // string str = "for cell in ws['A']: \
    //     color_name.append(cell.value), color_code.append(cell.fill.fgColor.rgb)";

    PyRun_SimpleString(str.c_str());
        
    PyRun_SimpleString("print(color_name)");
    PyRun_SimpleString("print(color_code)");

    Py_Finalize();
    return 0;
}

[C++] 56 Pythonスクリプトを使ってExcelを操作する

[M1 Mac, Big Sur 11.6.5, Python 3.10.4]

C++コードからPythonスクリプトを動かしてExcelを操作してみました。Pythonは公式サイトからダウンロード&インストールしたVer. 3.10.4を使っています。

以下のコードはExcelファイルのA列に入力された文字列とセル色をリストにして出力します。

モジュールではないのでデータの双方向なやりとりは直接できませんが、ファイルを介してなら可能でしょう。

C++でExcelを扱うにはLibXLのような3万円もする有償ライブラリが必要になるので、間接的とはいえopenpyxlで操作できるのであればこれで十分です。

FLTKのような軽量なGUIでopenpyxlやpandasが使えないかと思い、試してみた次第です。TkinterやPyQtといったウィジェットツールキットで重量級GUIにはしたくなかったものですから。

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

int main() {
    Py_Initialize();

    PyRun_SimpleString("import openpyxl");
    PyRun_SimpleString("wb = openpyxl.load_workbook('test.xlsx')");
    PyRun_SimpleString("ws = wb.worksheets[0]");
    PyRun_SimpleString("color_name = [cell.value for cell in ws['A']]");
    PyRun_SimpleString("color_code = [cell.fill.fgColor.rgb for cell in ws['A']]");
    PyRun_SimpleString("print(color_name)");
    PyRun_SimpleString("print(color_code)");

    Py_Finalize();
    return 0;
}
# コンパイラ
COMPILER = clang++
DEBUG = -g

# オプション
CPPFLAGS =

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

# ライブラリパス(-l)
LIBRARY0 = -lpython3.10

# 優先ライブラリパス(-L)
LIBRARY = -L/Library/Frameworks/Python.framework/Versions/3.10/lib

# ソースファイル
SRCDIR = ./src
SRCS = $(SRCDIR)/Openpyxl.cpp

# オブジェクトファイル
OBJDIR = ./obj
OBJS = $(OBJDIR)/Openpyxl.o

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

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

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

# ファイル削除&実行ファイル作成
.PHONY:all
all: clean $(TARGET)

# ファイル削除
.PHONY:clean
clean:
	rm -rf $(OBJS) $(TARGETDIR)/$(TARGET)

[Java]109 自製アプリのリファクタリング案

[M1 Mac, Big Sur 11.6.5, VSCode 1.67.1]

自製カラーアプリのリファクタリング案(改良案)を作成しました。

これまではTextFieldに保持されたカラーコードで選択色を管理していましたが、専用のクラスSelectColorを新設し変数(基本フォーマットは0x+hex)で管理することにしました。

これにより色フォーマットの変更に対応するコードが複雑にならずに済みそうです。

@startuml

package base {
    +class ColorSampleJP {
        +{static} gui : JFrame
        +{static} tabbedpane : JTabbedPaneEx
        +{static} tab1 : JPanel
        +{static} tab2 : JPanel
        +{static} tab3 : JPanel
        +{static} tab4 : JPanel
        +{static} tab5 : JPanel
        +{static} tab6 : JPanel
        +{static} layout : BorderLayout
        +{static} default_font : Font
        +{static} home : String
        -locale_lang : ResourceBundle

        -ColorSampleJP() : void
        +{static} main(String[] args) : void
        +{static} setLAF() : void
        +LAF_OS() : String[][]
        +{static} size(String key) : int
        +{static} makeDir() : void

    }
    
    '関連クラス(mainクラスで使用)
    +class PanelMake {
    }
    +class RegistTab {
    }
    +class SelectColor #00FFFF{
    }
    +class TabMake {
    }
}

package btnAction {
    +class ShowButtonAction #DDA0DD{
    }
    +class AdjustButtonAction {
    }
    +class AlphaButtonAction {
    }
    +class RadioButton1Action #DDA0DD{
    }
    +class RadioButton2Action #DDA0DD{
    }
    +class RadioButton3Action #DDA0DD{
    }
    +class RadioButton4Action #DDA0DD{
    }
    +class RegistButtonAction #DDA0DD{
    }
    +class TrushButtonAction {
    }
    +class FormatConverter {
    }

}

note left of SelectColor
    選択あるいは指定した色を管理する
end note

ColorSampleJP -- PanelMake
ColorSampleJP -left- RegistTab
ColorSampleJP -left- TabMake
TabMake -- SelectColor
SelectColor -- ShowButtonAction
SelectColor -- RadioButton1Action
SelectColor -- RadioButton2Action
SelectColor -- RadioButton3Action
SelectColor -- RadioButton4Action
SelectColor -- RegistButtonAction

left to right direction
PanelMake -- ShowButtonAction
PanelMake -- AdjustButtonAction
PanelMake -- AlphaButtonAction
PanelMake -- RadioButton1Action
PanelMake -- RadioButton2Action
PanelMake -- RadioButton3Action
PanelMake -- RadioButton4Action
RegistTab -- RegistButtonAction
RegistTab -- TrushButtonAction
AdjustButtonAction -- FormatConverter

@enduml