[M1 Mac, Big Sur 11.6.5, no Xcode]
画面サイズの取得方法は以下の通りです。
int width = [[NSScreen mainScreen] frame].size.width;
int height = [[NSScreen mainScreen] frame].size.height;
[M1 Mac, Big Sur 11.6.5, no Xcode]
画面サイズの取得方法は以下の通りです。
int width = [[NSScreen mainScreen] frame].size.width;
int height = [[NSScreen mainScreen] frame].size.height;
[M1 Mac, Big Sur 11.6.5, no Xcode]
ボタンを配置しました。ボタンタイトルの色を設定できないので、Windowの背景色をやむなくデフォルトに戻しました。
設定の仕方が単純ではないというだけでできないわけではないと思います。一部機能ではFLTKに及ばないと考えるのが妥当でしょう。
<該当箇所のみ>
button1 = [[[NSButton alloc] initWithFrame:NSMakeRect(290, 265-30-10, 60, 30)] autorelease];
[button1 setFont:[NSFont fontWithName:@"Arial" size:12]];
[button1 setTitle:@"実行"];
[button1 setBezelStyle:NSBezelStyleRegularSquare];
[button1 setTarget:self];
[button1 setAction:@selector(OnButton1Click:)];
[button1 setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin];
[M1 Mac, Big Sur 11.6.5, no Xcode]
テキストラベルを配置しました。書き方自体はSwiftやFLTKと似ているため、角括弧への違和感が取れないながらも少しずつ慣れてきました。
Windowや図形のY座標は、ディスプレイやWindowの縦サイズから図形の高さと左上基準のY座標を引く必要がありややこしいです。
あとはテキストエリアとボタン類なのでGUIについてはスムーズに実装できそうです。
#include <Cocoa/Cocoa.h>
@interface ConvertorWindow : NSWindow {
NSTextField* label1;
NSTextField* label2;
}
- (instancetype)init;
- (BOOL)windowShouldClose:(id)sender;
@end
@implementation ConvertorWindow
- (instancetype)init {
NSColor *background = [NSColor colorWithCalibratedRed:(double)247/255 green:(double)252/255 blue:(double)254/255 alpha:1.0f];
NSColor *foreground = [NSColor colorWithCalibratedRed:(double)57/255 green:(double)63/255 blue:(double)76/255 alpha:1.0f];
label1 = [[[NSTextField alloc] initWithFrame:NSMakeRect(10, 265-16-15, 34, 16)] autorelease];
[label1 setFont:[NSFont fontWithName:@"Arial" size:14]];
[label1 setStringValue:@"FILE"];
[label1 setBezeled:NO];
[label1 setDrawsBackground:NO];
[label1 setEditable:NO];
[label1 setSelectable:NO];
[label1 setTextColor:foreground];
label2 = [[[NSTextField alloc] initWithFrame:NSMakeRect(10, 265-16-45, 34, 16)] autorelease];
[label2 setFont:[NSFont fontWithName:@"Arial" size:14]];
[label2 setStringValue:@"LIST"];
[label2 setBezeled:NO];
[label2 setDrawsBackground:NO];
[label2 setEditable:NO];
[label2 setSelectable:NO];
[label2 setTextColor:foreground];
[super initWithContentRect:NSMakeRect(100, 1080-265-100, 360, 265) styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable backing:NSBackingStoreBuffered defer:NO];
[self setTitle:@"Xlsx Convertor"];
[[self contentView] addSubview:label1];
[[self contentView] addSubview:label2];
[self setIsVisible:YES];
[self setBackgroundColor:background];
return self;
}
- (BOOL)windowShouldClose:(id)sender {
[NSApp terminate:sender];
return YES;
}
@end
int main(int argc, char* argv[]) {
[NSApplication sharedApplication];
[[[[ConvertorWindow alloc] init] autorelease] makeMainWindow];
[NSApp run];
}
[M1 Mac, Big Sur 11.6.5, no Xcode]
CMakeが作成したMakefileにアイコン設定を追記しました。
以下のようにallの箇所に追記して、make allコマンドを実行するとappファイル作成&アイコン設定できます。このコマンドにより、imagesディレクトリにあるicnsファイルをappファイル内にコピーし、info.plistのkeyとvalueを上書きします。
# The main all target
all: cmake_check_build_system
$(CMAKE_COMMAND) -E cmake_progress_start /code/ObjectiveC/projects/XlsxConvertor02/build/CMakeFiles /code/ObjectiveC/projects/XlsxConvertor02/build//CMakeFiles/progress.marks
$(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 all
$(CMAKE_COMMAND) -E cmake_progress_start /code/ObjectiveC/projects/XlsxConvertor02/build/CMakeFiles 0
# 以下3行を追記
mkdir XlsxConvertor.app/Contents/Resources
cp ../images/XlsxConvertor.icns XlsxConvertor.app/Contents/Resources
plutil -replace 'CFBundleIconFile' -string "XlsxConvertor.icns" XlsxConvertor.app/Contents/Info.plist
.PHONY : all
[M1 Mac, Big Sur 11.6.5, no Xcode]
まずはWindowを作成し、タイトルと背景色を設定しました。
何でもない内容ですが、手本となるサンプルコードを探すのに少々苦労しました。Xcodeを使わずに、エディタとターミナルだけでアプリを作成していきます。
Objective-Cの本を2冊購入しましたが、レガシー言語だからか状態の良い中古本で1冊800円程度でした。今のところパラパラと見た位で結局GitHubにある本とは関係のないサンプルコードを取っ掛かりの教材にしています。実践が先で理屈は後回しです。
Objective-CではMakefileがCMakeで簡単に作成できます。C++ではコンパイルやビルドのオプション探索に苦労していましたが、簡潔なCMakeLists.txtに丸投げで済みそうです。C++のCMakeはかなりとっつきにくい印象でした。
特徴的な点を以下に挙げておきます。
1.座標の原点が左上ではなく左下。左上基準の場合、ディスプレイの解像度がわからないとY座標が算出できない。
2.RGBはSwiftとは異なり、分数だけではNG。エラーにはならないが色がおかしくなる。(double)などで小数への変換が必要。
3.MakefileはCMakeに作らせても簡単。
#include <Cocoa/Cocoa.h>
int main(int argc, char* argv[]) {
NSColor* background = [NSColor colorWithCalibratedRed:(double)247/255 green:(double)252/255 blue:(double)254/255 alpha:1.0f];
NSWindow* window = [[[NSWindow alloc] initWithContentRect:NSMakeRect(100, 1080-220-100, 360, 220) styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable backing:NSBackingStoreBuffered defer:NO] autorelease];
[window setIsVisible:YES];
[window setTitle:@"Xlsx Convertor"];
[window setBackgroundColor:background];
[NSApplication sharedApplication];
[NSApp run];
}
cmake_minimum_required(VERSION 3.1)
# Project
Project(XlsxConvertor VERSION 1.0.0)
find_library(COCOA_LIBRARY Cocoa)
# Options
set(MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION})
set(MACOSX_BUNDLE_COPYRIGHT "Copyright (C) 2022")
set(MACOSX_BUNDLE_INFO_STRING "The name of this app is XlsxConvertor")
set(MACOSX_BUNDLE_GUI_IDENTIFIER "xxx.xxx.XlsxConvertor")
# XlsxConvertor
add_executable(${PROJECT_NAME} MACOSX_BUNDLE XlsxConvertor.m)
target_link_libraries(${PROJECT_NAME} ${COCOA_LIBRARY})
[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
[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
[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();
}
[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();
}
[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"