CocoaのFrameworkでSTLを使ってみる

最近Cocoaにはまっています。Windows用に作成したライブラリをMacへ移植してみたのですが意外とはまったのでメモ

環境

  • MacOX10.9
  • XCode 5

TestAppという単純なアプリを作成しその中からSTLで作成したFrameworkを呼ぶことにするサンプルです。

UtilsというなのFramework

まず、ライブラリの作成です。XCodeでOSX用のCocoaFrameworkを作成します。

作成した後にファイルの追加で以下のヘッダファイルとC++ファイルを追加します

  • Test.h
#ifndef Utils_Test_h
#define Utils_Test_h
#include 
// 値を保持するクラス
class Bean{
public:
		int a;
		int b;

		Bean();
		void set(int a,int b);
		std::string to_s();
};
// 値を操作するクラス
class Test{
public:
		int add(int a,int b);
		std::shared_ptr add(std::shared_ptr a ,std::shared_ptr b);
};
#endif
  • Test.cpp
#include "Test.h"
#include 
Bean::Bean(){
		a=0;
		b=0;
}
void Bean::set(int a_,int b_){
		a=a_;
		b=b_;
}
std::string Bean::to_s(){
		char buf[1000];
		sprintf(buf,"a=%d,b=%d",a,b);
		return std::string(buf);
}
int Test::add(int a, int b){
		return a+b;
}
std::shared_ptr Test::add(std::shared_ptr a ,std::shared_ptr b){
		std::shared_ptr ret=std::shared_ptr(new Bean());
		int va=a->a+b->a;
		int vb=b->b+b->b;
		ret->set(va,vb);
		return ret;
}

ついでにObjective-Cのクラスも追加します。こちらを参考に

  • Utils.h
#import 

@interface Utils : NSObject
+(NSString*)addBrackets:(NSString*)string;
@end
  • Utils.m
#import "Utils.h"

@implementation Utils
+(NSString *)addBrackets:(NSString *)string
{
		return [NSString stringWithFormat:@"[-- %@ --]", string];
}
@end

それぞれのヘッダファイルをPublickにしたのちに、コンパイルするのですが、Build Rulesの Apple LLVM 5.0 Language C++のC++ Language Dialect を -std=c++11へ、C++ Standard Libraryを libc++(LLVM C++ standard library with C++ 11 supportへ変更します。また Apple LLVM 5.0 Language のCompile Source As をObjective-C++へと変更しておきます。

これでコンパイルOKのはず

TestAppというアプリ作成

OSXのApplicationのCocoaApplicationからプロジェクトを作成し、テキストボックスとボタンを配置しておきます。また、プロジェクトのFrameworksに先ほど作成したUtilsのフレームワークを追加しておきます。

  • AppDelegate.h
#import 

@interface AppDelegate : NSObject 

@property (assign) IBOutlet NSWindow *window;

@property IBOutlet NSButton *button;
@property IBOutlet NSTextField *text;

-(IBAction)pushButton:(id)sender;
@end
  • AppDelegate.m
#import "AppDelegate.h"

#import 
#import 
#include 

@implementation AppDelegate
@synthesize button;
@synthesize text;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
		// Insert code here to initialize your application
}

-(IBAction)pushButton:(id)sender{
		std::shared_ptr a=std::shared_ptr(new Bean());
		std::shared_ptr b=std::shared_ptr(new Bean());
		std::shared_ptr t=std::shared_ptr(new Test());

		a->set(1,2);
		b->set(10,20);
		std::shared_ptr tt=t->add(a,b);
		NSString* swk=[NSString stringWithCString:tt->to_s().c_str() encoding:[NSString defaultCStringEncoding]];
		NSString* str=[Utils addBrackets:swk];
		[text setStringValue:str];
}
@end

先ほどのライブラリと同様にBuildSettingを変更しておきます。

これでコンパイルとリンクがOKのはず。。

ボタンを押すとライブラリで計算した値がテキストボックスに出るはずです

Mac用アプリの自動テスト

最近自動テストツールにはまっています。

さすがにはやりというかiOSやAndroid用のテストツールは山ほど(というほどでもありませんが)有るのですが、Macのアプリ用のテストツールはほとんど有りません。

ちなみにこちらにまとめて書いてあるので参考になります

http://iphone-dev.g.hatena.ne.jp/laiso/20120111/1326280021

KIFのMac版が有るとのことなのでちょっと試してみました。

こちらのサイトを参考に

http://d.hatena.ne.jp/laiso+iphone/20121013/1350134198

環境

  • MacOS10.9
  • Xcode 5

この組み合わせで少しはまりました。

http://d.hatena.ne.jp/laiso+iphone/20121013/1350134198

こちらのサイトのサンプルであるGITHubからダウンロードします

https://github.com/laiso/Mac-Samples/tree/master/KIFMac01

git clone https://github.com/laiso/Mac-Samples.git

このままではKIFがとれないので別途取得します

mkdir work
cd work
git init
mkdir Vendor
git submodule add https://github.com/joshaber/KIF.git	Vendors/KIF-Mac

先ほど取得したサンプルに上書きします

mv Vendors/KIF-Mac ../../Mac-Sample/.

Xcodeを起動しプロジェクトを取り込みます

IntegrationTestのターゲットを選んで実行。しても止まってしまいます。

ログを見ると、

System Preferences => Universal Access => Enable access for assistive devices

どうやらアクセシビリティがだめな模様。

システム環境設定のアクセシビリティを見ても設定項目なし。。

http://www.tekrevue.com/how-to-enable-access-for-assistive-devices-in-os-x-mavericks/

こちらに答えが書いていました。

システム環境設定のセキュリティとプライバシーの中から、プライバシータグのアクセシビリティにXCodeのチェックボックスをオン

これでサンプル通りに動きました

OpenGLで文字列

OpenGLで文字列を描画したく、いろいろ探してみたのですが、みんな苦労しているみたいです。

とりあえずわかったことが2つ、日本語はちょっと難しい。

  • まずGLUTを使うやり方

これの場合、日本語は描画できません。ただし簡単に書くことができます

http://d.hatena.ne.jp/osyo-manga/20110827/1314417606

こちらのサイトを参考に

-(void)paint{
		glClearColor(0.5f, 0.5f, 0.5f, 1.0f); // default background color
		glClear(GL_COLOR_BUFFER_BIT);
		
		[self renderString:@"kana" x:0.5 y:0.5f];
		glFlush();
}

-(void)renderString:(NSString *)str_ x:(float)x_ y:(float)y_{
		glRasterPos2d(x_, y_);
		const char* charStr=[str_ UTF8String];
		int len=(int)[str_ length];
		for(int i=0;i

  • テクスチャを使うやり方

日本語を表示したい場合にはテクスチャを使うみたいです。結構重いとのことなので決まった文字列の場合には画像をそのまま貼付けた方が良さそうです

http://null-null.net/blog/2007/10/566.php

こちらのサイトを参考に

+ (void) draw:(NSString *)string_	x:(float)x_ y:(float)y_{
	NSAttributedString *attrString;
	GLuint *texId;

		glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
		glClearDepth( 1.0f );
		glDisable(GL_CULL_FACE);
		glEnable( GL_BLEND );
		glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
		
		int i, strSize;
		NSFont *nsfont;
		NSDictionary *attrsDictionary;
		NSAttributedString *singleChar;
		NSImage **images;
		NSBitmapImageRep **imageReps;
		NSImage* img;
		NSPoint point;
		NSSize size;

		int texWidth_=10;
		int texHeight_=10;
		
		// alloc texture id and image buffer
		strSize = (int)[string_ length];
		texId = (GLuint*)malloc( sizeof(GLuint)*strSize );
		images = (NSImage**)malloc( sizeof(NSImage*)*strSize );
		imageReps = (NSBitmapImageRep**)malloc( sizeof(NSBitmapImageRep*)*strSize );
		for( i = 0; i < strSize; i++ ){
				images[i] = [[NSImage alloc] initWithSize:NSMakeSize( texWidth_, texHeight_ )];
		}
		
		// font settings
		NSSize screenResolution = [[[[NSScreen mainScreen] deviceDescription] objectForKey:NSDeviceResolution] sizeValue];
		int fontSize_ = texWidth_ * 72.0/screenResolution.width;
		
		nsfont = [NSFont fontWithName:@"HiraKakuPro-W6" size:fontSize_];
		attrsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
											 nsfont, NSFontAttributeName,
											 [NSColor whiteColor], NSForegroundColorAttributeName,
											 [NSColor clearColor], NSBackgroundColorAttributeName,
											 nil
											 ];
		
		// alloc attributed string
		attrString = [[NSAttributedString alloc] initWithString:string_ attributes:attrsDictionary];
		
		// create texture id
		glEnable( GL_TEXTURE_2D );
		glGenTextures( strSize, texId );
		
		// build texture image
		for( i = 0; i < strSize; i++ ){
				img = images[i];
				singleChar = [attrString attributedSubstringFromRange:NSMakeRange(i,1)];
				// setting background color
				[img setBackgroundColor:[NSColor clearColor]];
				// calc center position
				size = [singleChar size];
				point = NSMakePoint( (texWidth_-size.width)/2, (texHeight_-size.height)/2 );
				// draw character to image
				[img lockFocus];
				[singleChar drawAtPoint:point];
				[img unlockFocus];
				
				// alloc bitmap image from NSImage
				imageReps[i] = [[NSBitmapImageRep alloc] initWithData:[img TIFFRepresentation]];
				
				// texture settings
				glBindTexture( GL_TEXTURE_2D, texId[i] );
				glTexImage2D(
										 GL_TEXTURE_2D, 0, GL_RGBA,
										 texWidth_, texHeight_, 0,
										 [imageReps[i] hasAlpha] ? GL_RGBA :GL_RGB,
										 GL_UNSIGNED_BYTE,
										 [imageReps[i] bitmapData]
										 );
				glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
				glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
				glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
				glBindTexture( GL_TEXTURE_2D, 0 );
		}
		glDisable( GL_TEXTURE_2D );
		
		// release
		for( i = 0; i < strSize; i++ ){
				[imageReps[i] release];
				[images[i] release];
		}
		free( imageReps );
		free( images );
		
	strSize = (int)[string_ length];
	glEnable( GL_TEXTURE_2D );
	for( i = 0; i < strSize; i++ ){
		glBindTexture( GL_TEXTURE_2D, texId[i] );
		glBegin(GL_POLYGON);

						float wf_=0.1f; // TODO Viewのサイズからきちんと求める
						float hf_=0.1f;

						glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
						glTexCoord2f( 0, 1 );
						glVertex2f( x_+i*wf_, y_ );
						glTexCoord2f( 1, 1 );
						glVertex2f( x_+(i+1)*wf_, y_ );
						glTexCoord2f( 1, 0 );
						glVertex2f( x_+(i+1)*wf_, y_+hf_ );
						glTexCoord2f( 0, 0 );
						glVertex2f( x_+i*wf_, y_+hf_);
		glEnd();
	}
	glDisable( GL_TEXTURE_2D );

		glDeleteTextures((int) [string_ length], texId );
		free( texId );
		[attrString release];
}

ちょっといい加減ですがこんな感じ

NSNumberの拡張?

どうもCocoaプログラミングでは通常のC言語のような数値配列を使うことはできなさそう。

NSNumberとNSMutableArrayを駆使して書くのが一般的みたいです。

ただ、NSNumberだと一回作ったオブジェクトの値がどうやら変更不可らしい。

これでは数値計算には使えません。

そこでNSNumberを拡張してみました

@interface MYNUmber:NSNumber{

}

@property(readwrite)float floatValue;

@property(readwrite)bool boolValue;

@property(readwrite)int intValue;

@property(readwrite)double doubleValue;

@end

これでとりあえずコンパイルは通りました

initialization method -initWithInt: cannot be sent to an abstract object

が、allocする際に何やらエラー。。

まだ道のりは遠い。。