Posts Tagged Programming

「OpenGLで作るiPhone SDKゲームプログラミング」が面白かった

OpenGLで作るiPhone SDKゲームプログラミング

著者/訳者:横江 宗太(株式会社パンカク)

出版社:インプレスジャパン( 2009-12-18 )

定価:¥ 3,024

単行本 ( 352 ページ )

ISBN-10 : 4844328085

ISBN-13 : 9784844328087


一言で言うと、この本がとても面白かった。
内容は非常にストレートで、「iPhone向けに簡単なレースゲームを作る」という趣旨の本。
1章がOpenGL ES 1.0を使った2Dの取り扱い。画面への描画。
2章がその応用で「はえたたきゲーム」を作る。

はえたたきゲーム


3章が”パーティクルシステム”と呼ばれる煙などの表現に使われる演出の実装。

パーティクルシステム


4章が「2Dレースゲーム」を作る。

2Dレースゲーム


5章が”衝突判定”の実装。

衝突判定の実装と画面調整


6章が全ての章を応用して「3Dレースゲーム」を作る。

3Dレースゲーム

まず、Objective-CとC++の知識が最低限求められる。
あと、最終章の3Dレースゲームと言ってもそれほどのものを期待してはいけない。
ただ、ゲームの骨組みを組む方法や、画面描画と操作の連携などを学ぶことが出来る。
俺みたいな脳たりんには丁度良い内容となった。

基本的なゲームロジックはC++で実装されているため、そのままではつまらないのでObjective-Cで実装することにした。
これがC++のコードとの対比が出来てなかなか面白かった。
実際問題、C++を前提に書いてあるので付け焼き刃な俺のObjective-C知識ではなかなか無駄な処理が出まくっているのはわかっているのだけれども、それでもやって良かったと思う。
書店で見かけたら、ちょっと目を通してみるのもいいかもしれない。
これだけでゲームは作れないけど、本当に良いきっかけを作ってくれると思う。

ちなみに、上記のゲーム画像はアセットこそお借りしたものの、ソースコードは写経+Objective-Cで書き直したものをiPhone 3G上で実際に動かしたもののスクリーンショット。
一応この程度のものは出来る。

また、Windows用になってしまうけれど、この本の冒頭に書いてある参考文献である「ゲームプログラマになる前に覚えておきたい技術」をこの本の次に読むといい気がしてきた。
実は「ゲームプログラマになる前に~」も持ってはいるものの、なかなか読み進められないでいた。
けど、今回の「OpenGLで作る~」を読んでからなんとなく進められるようになってきた。
基本的に本に書かれているコードは執筆時のいずれかの段階のコードなので、実は動かないものも多いけど、ソースコードに当たれば問題ないレベル。
徐々にステップアップして行くには良いかなぁ、と自分に言い聞かせつつ読み進めてまふ。
参考までに。

ゲームプログラマになる前に覚えておきたい技術

著者/訳者:平山 尚

出版社:秀和システム( 2008-11-14 )

定価:¥ 4,860

Amazon価格:¥ 4,860

単行本 ( 872 ページ )

ISBN-10 : 4798021180

ISBN-13 : 9784798021188


そんだけ。

Post to Twitter

, , , , ,

No Comments

Objective-Cのお勉強 #3 セレクタ

iPhone SDK アプリケーション開発ガイドを読んでいた昨今、ど〜しても理解しがたかったのが、メソッド宣言してなくて、しかもそのメソッドが書かれている箇所より上のメソッドより上から呼び出されていて、それがすんなり通る。
でも、その呼び方が不自然で、

[self hoge];

などじゃなく、

        hogeButton = [[UIBarButtonItem alloc] initWithTitle:@"Hoge"
style:UIBarButtonItemStylePlain
target:appDelegate
action:@selector(hoge)];

と言う書き方。
ちなみに、これはアプリケーション開発ガイドのPageDemoと言うプログラムで見ることが出来る。

で、@selectorと言うのがどうにも怪しいので詳解 Objective-C 2.0を手に取る。
で、こんなメソッドが用意されているらしい。

// 引数無し実行
- (id)performSelector:(SEL)aSelector;
// 引数あり実行
- (id)performSelector:(SEL)aSelector withObject:(id)anObject;

こんな感じで呼べるらしい。

内容読んだら関数ポインタをダイナミックにしたような物、と言う雰囲気。
実感湧かないから書いてみることに。
hoge.h

// hoge.h
#import <Foundation/NSObject.h>
@interface Hoge : NSObject
- (void) foo;
@end
@interface Moge : NSObject
@end

hoge.m

// hoge.m
#import "hoge.h"
#import <stdio.h>
@implementation Hoge
- (void) foo {
printf( "Message from Hoge's foo.\n" );
// このコードは動かないから、コメントアウト。
//[self bar];
// このコードは動く
[self performSelector:@selector(bar)];
// モゲが動く
id moge = [[Moge alloc] init];
[moge performSelector:@selector(bar)];
[moge dealloc];
// ホゲが動く
moge = self;
[moge performSelector:@selector(bar)];
}
- (void) bar {
printf("Message from Hoge's bar.\n");
}
@end
@implementation Moge
- (void) bar {
printf("Message from Moge's bar.\n");
}
@end

main.m

main.m
#import "hoge.h"
int main()
{
// idと言う型の変数を定義します
id obj;
// クラスのインスタンス化
obj = [[Hoge alloc] init];
// メソッド呼び出し
[obj foo];
[obj dealloc];
return 0;
}

実行結果

McLaren% gcc -o hoge_test main_hoge.m hoge.m -framework Foundation
McLaren% ./hoge_test
Message from Hoge's foo.
Message from Hoge's bar.
Message from Moge's bar.
Message from Hoge's bar.

なるほどね。。
俺ほどの実力になると、使い時さえ思いつかないと言う有様。
精進します。。。

そんだけ。

Post to Twitter

,

No Comments

iPhone プログラミングノート #1

さてさて、iPhone SDK アプリケーション開発ガイドをパラパラ見ること3章、おぼろげながらiPhoneのプログラミング方法がわかって来たのでちょいと備忘録でも。

で、とりあえずテキトーにiPhoneのプロジェクトを作る。
とりあえずWindow-based Applicationとかで。
恐らく下記のようなファイルが出来るはず。
Classes
・プロジェクト名AppDelegate.h
・プロジェクト名AppDelegate.m
Other Resources
・main.m
・プロジェクト名-Info.plist

あと、その他諸々ついてくるけど、とりあえず気にしないことに。
ただこのままだと○○.xibというファイルを読み込んでしまうので、それを防がなくてはならない。
とりあえずInfo.plistを開いて「Main nib file base name」の項目自体を削除、と。

XML形式で開いてしまったら、当該箇所をやはり削除。
例えば下記のような2行があったら削除。

	<key>NSMainNibFile</key>
<string>MainView.nib</string>

ちなみに、プロジェクト名はHelloWorld・・・と言うことで。

で、エントリーポイント(プログラム実行時、最初に呼び出される関数)はC/C++と同様main。
で、Windowsのように自動でプログラムのループを司るWinMainのような仕組みは、mainに書くことになっているみたい。
ひな形として与えられるのは下記の通り。

#import <UIKit/UIKit.h>
int main(int argc, char *argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
[pool release];
return retVal;
}

なんか、argcとargvをUIApplicationMainに突っ込んでる所からして、ここがプログラムループ、らしい。。
ここの第4引数を下記のように修正。

	int retVal = UIApplicationMain(argc, argv, nil, @"HelloWorldAppDelegate");

ちなみに、.mとか.hとか付けていないのはわざとなので付けないこと。

お次はHelloWorldAppDelegate.hと同.mを編集。

// HelloWorldAppDelegate.h
#import <UIKit/UIKit.h>
// メッセージ表示用にUITextViewと言うビューを継承し、カスタムビューを作成
@interface CustomView : UITextView
{
// カスタムビュー用のメンバ変数
UITextView *myTextView;
}
@end
@interface HelloWorldAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
CustomView *mainView;
}
@property (nonatomic, retain) IBOutlet UIWindow *window;
@end
// HelloWorldAppDelegate.m
#import "HelloWorldAppDelegate.h"
@implementation CustomView
- (id)initWithFrame:(CGRect)frame {
// 親クラスの親メソッド呼び出し
self = [super initWithFrame:frame];
// 親メソッド取得成功時のみ実行
if (self != nil) {
// テキストビューを初期化
myTextView = [[UITextView alloc] initWithFrame:frame];
// テキストビューへテキスト追加
myTextView.text = @"おはこんばんちは";
// テキストビューをこのカスタムビューに適用?
[self addSubview:myTextView];
}
return self;
}
- (void)dealloc {
// メモリ解放
[myTextView release];
[super dealloc];
}
@end
@implementation HelloWorldAppDelegate
@synthesize window;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// バウンズ(画面領域)を取得
CGRect bounds = [[UIScreen mainScreen] applicationFrame];
// ウィンドウの範囲を画面サイズギリギリまで占有
window = [[UIWindow alloc] initWithFrame:bounds];
// ビューのサイズをステータスバーを除いて占有
mainView = [[CustomView alloc] initWithFrame:CGRectMake(0, 0, bounds.size.width, bounds.size.height)];
// ウィンドウへビューを設定
[window addSubview:mainView];
// ビューを表示
[window makeKeyAndVisible];
}
- (void)dealloc {
[mainView release];
[window release];
[super dealloc];
}
@end

これをコンパイルするとこんな感じ。

ふむふむ、何となくわかったような、わからないような。
実際はこの次のViewControllerまでやってるんだけど、復習はここまで。
本のコードとちょっと違うのはやはりわざと。
行儀が良いのか悪いのかは知らんけど、これでも動く。

そんだけ。

Post to Twitter

, , , , ,

No Comments

iPhone SDK アプリケーション開発ガイド

いろいろObjective-C 2.0で迷い、Erica Sadunの「iPhone デベロッパーズ クックブック」を試したりもした(ただし俺が持ってるのは英語版)。
これがなかなかどうして、敷居が高くて困っていた。

素直にO’Reillyの、しかも日本語版の書籍を手に取っておけば良かった・・・。
と言うわけで、先週買いに行ったのがこれ。
iPhone SDK アプリケーション開発ガイド

この本はかなりかゆいところに手が届くと言うか、Erica Sadunと同様iPhone入門書の中で珍しいInterface Builderを使わないiPhoneプログラミングで進んで行く。
そのため、ツール嫌いのコード人間で、俺みたいな脳足りんにはちょうど良い。
敷居はCとオブジェクト指向の知識、あとiPhoneとかiPod Touchを使っていればおおむね理解できる。
今3章までパチパチ打ったり修正したりしてるけど、これがなかなかどうして面白い。
Cookbookではどうにもコードが断片的で、コードの意図するところはとりあえず書いてあるものの、それ以外の変更点その他は女史のサイトからDLして自分で読み解かないといけない。
それに対して、図解が乏しい本書は逆にコード量とその前のコード説明がしっかりとなされている感じ。
そのため、読んでいていろいろ考えさせる。
本来学ぶ上で気にしなくていいようなどうでも良い事柄にはまることが無いため、読んでいて素直に学習できる。

そんなわけで、この本を元にチョイチョイメモを残そうと思った。
ちなみに、この本の作者であるJonathan Zdziarskiは前著「iPhone Open Application Development」でJailbreak前提のオープンソース開発を披露していた偉大なるハッカー。
そのためか、iPhone SDKには隠された機能が多いとして前書きで不満をこぼしているw
この本のサンプルコードに関してはほぼパブリックドメインの形を取っていて、似るなり、焼くなり、売るなり、改変して公開するなり、非公開するなり自由とのこと。
(ただし、サンプルコードの多くの部分を使う場合はO’Reillyとの協議が必要とのこと。)

とりあえず良い本とだけご紹介。

Post to Twitter

, , ,

No Comments

WindowsでBitBltなるものを勉強

最近ちょっと落ち着いてきたので、「プログラミングWindows」を読み進めていたんだけど、どーにもつまらない。
俺がやりたいのは、ドット単位での画面描画なのに、ずーっとベクター系。
で、終わったと思えば「キーボード」の章。
ん~・・・。

やりたいことは結構単純で、リアルタイム3DCGの本があるからその公式やらを元にウィンドウ内に書き込みたいなぁ、と。
だから、勉強方法を変えてみることにした。
ウィンドウ描いてその中をラスタライズして2Dにしたリアルタイム3Dを表示したいだけ。
思い切ってページをすっ飛ばして、画面のビットマップ情報をいじることに。
その手始めとして「BitBlt(Bit-block transfer:ビットブリット)」というのをやってみた。

#include <windows.h>
LRESULT CALLBACK MyWindow(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpCmdLine,int iCmdShow)
{
static	TCHAR	szAppName[] =
TEXT("おいらのアプリ");	//アプリ名?
HWND			hwnd;	//ウィンドウハンドル
MSG				msg;	//メッセージ
WNDCLASS		wndCls;	//ウィンドウクラス構造体
//ウィンドウスタイルを指定
wndCls.style			= CS_HREDRAW | CS_VREDRAW;
//ウィンドウプロシージャを指定
wndCls.lpfnWndProc		= MyWindow;
wndCls.cbClsExtra		= 0;
wndCls.cbWndExtra		= 0;
//インスタンスハンドル変数を指定
wndCls.hInstance		= hInstance;
//ウィンドウのアイコンを指定
wndCls.hIcon			= LoadIcon(NULL, IDI_APPLICATION);
//カーソルの形を指定
wndCls.hCursor			= LoadCursor(NULL, IDC_CROSS);
//バックグラウンド色を指定
wndCls.hbrBackground	= (HBRUSH) GetStockObject(WHITE_BRUSH);
wndCls.lpszMenuName		= NULL;
wndCls.lpszClassName	= szAppName;
if (!RegisterClass(&wndCls)) {
MessageBox(NULL,
TEXT("このプログラムはWindowsNT以降対応です。"),
TEXT("そーりー"), MB_ICONERROR);
return 0;
}
hwnd = CreateWindow(szAppName, TEXT("どうよ? - Bitmap Viewer"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
//ウィンドウ表示状態を指定
ShowWindow(hwnd, iCmdShow);
//ウィンドウ更新実行
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0)) { //メッセージループ
TranslateMessage(&msg);	//命令変換?
DispatchMessage(&msg);	//命令転送
}
return msg.wParam;
}
LRESULT CALLBACK MyWindow(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int	cxClient, cyClient, cxSource, cySource;
HDC			hdcClient, hdcWindow;
PAINTSTRUCT ps;
int			x, y;
TCHAR		buff[512];
switch (message) {
case WM_CREATE:
cxSource = GetSystemMetrics (SM_CXSIZEFRAME) +
GetSystemMetrics (SM_CXSIZE);
cySource = GetSystemMetrics (SM_CYSIZEFRAME) +
GetSystemMetrics (SM_CYCAPTION);
wsprintf(buff,
TEXT("GetSystemMetrics (SM_CXSIZEFRAME):%d\n")
TEXT("GetSystemMetrics (SM_CXSIZE):%d\n")
TEXT("GetSystemMetrics (SM_CYSIZEFRAME):%d\n")
TEXT("GetSystemMetrics (SM_CYCAPTION):%d"),
GetSystemMetrics (SM_CXSIZEFRAME),
GetSystemMetrics (SM_CXSIZE),
GetSystemMetrics (SM_CYSIZEFRAME),
GetSystemMetrics (SM_CYCAPTION));
MessageBox(NULL, buff, TEXT("情報"), MB_OK);
return 0;
case WM_SIZE:
cxClient = LOWORD (lParam);
cyClient = HIWORD (lParam);
return 0;
case WM_PAINT:
hdcClient = BeginPaint (hwnd, &ps);
hdcWindow = GetWindowDC (hwnd);
for (y = 0; y < cyClient; y += cySource)
for (x = 0; x < cxClient; x += cxSource) {
BitBlt (hdcClient, x, y, cxSource, cySource,
hdcWindow, 0, 0, SRCCOPY);
}
ReleaseDC (hwnd, hdcWindow);
EndPaint (hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage (0);
return 0;
}
return DefWindowProc (hwnd, message, wParam, lParam);
}

ほぼほぼ「プログラミングWindows」上のソースの写経。
何がやりたいかというと、タイトルバーの左端にあるアイコンをウィンドウ内に敷き詰めるというもの。
改造点はプログラムが立ち上がる際に、アイコンの左端の取得アイコン座標の情報を表示する点。

一生やってろと言いたくなるくらいのペースに苦笑い。

そんだけ。

Post to Twitter

, ,

No Comments

C++でのクラスと構造体

前回C++のクラスは構造体の拡張であるという話し。
まず一般的な構造体から見ていきましょう、と。

#include <iostream>
#include <string.h>
using namespace std;
struct hoge
{
int a;
double b;
char c[128];
} foo;
int main()
{
hoge* pFoo = &foo;
foo.a = 100;
foo.b = 3.141592674;
strcpy(foo.c, "構造体のテストじゃ~。");
cout << "直接参照:\n" <<
"foo.a = " << foo.a <<
"\nfoo.b = " << foo.b <<
"\nfoo.c = " << foo.c << endl;
cout << "間接参照:\n" <<
"pFoo->a = " << pFoo->a <<
"\npFoo->b = " << pFoo->b <<
"\npFoo->c = " << pFoo->c << endl;
cout << "\n\n";
pFoo->a = 500;
pFoo->b = 1.05;
strcpy(pFoo->c, "ポインタから更新じゃじゃ~。");
cout << "直接参照:\n" <<
"foo.a = " << foo.a <<
"\nfoo.b = " << foo.b <<
"\nfoo.c = " << foo.c << endl;
cout << "間接参照:\n" <<
"pFoo->a = " << pFoo->a <<
"\npFoo->b = " << pFoo->b <<
"\npFoo->c = " << pFoo->c << endl;
return 0;
}

まぁ、これは普通に読めること前提。
一応実行結果はこれ。

直接参照:
foo.a = 100
foo.b = 3.14159
foo.c = 構造体のテストじゃ~。
間接参照:
pFoo->a = 100
pFoo->b = 3.14159
pFoo->c = 構造体のテストじゃ~。
直接参照:
foo.a = 500
foo.b = 1.05
foo.c = ポインタから更新じゃじゃ~。
間接参照:
pFoo->a = 500
pFoo->b = 1.05
pFoo->c = ポインタから更新じゃじゃ~。

で、これをそっくりクラスで書くと下記の通り。

#include <iostream>
#include <string.h>
using namespace std;
class hoge
{
public:
int a;
double b;
char c[128];
} foo;
int main()
{
hoge* pFoo = &foo;
foo.a = 100;
foo.b = 3.141592674;
strcpy(foo.c, "クラスのテストじゃ~。");
cout << "直接参照:\n" <<
"foo.a = " << foo.a <<
"\nfoo.b = " << foo.b <<
"\nfoo.c = " << foo.c << endl;
cout << "間接参照:\n" <<
"pFoo->a = " << pFoo->a <<
"\npFoo->b = " << pFoo->b <<
"\npFoo->c = " << pFoo->c << endl;
cout << "\n\n";
pFoo->a = 500;
pFoo->b = 1.05;
strcpy(pFoo->c, "ポインタから更新じゃじゃ~。");
cout << "直接参照:\n" <<
"foo.a = " << foo.a <<
"\nfoo.b = " << foo.b <<
"\nfoo.c = " << foo.c << endl;
cout << "間接参照:\n" <<
"pFoo->a = " << pFoo->a <<
"\npFoo->b = " << pFoo->b <<
"\npFoo->c = " << pFoo->c << endl;
return 0;
}

こんな風にpublic:をつけるだけ。
感がいい人はわかってると思うけど、構造体でもprivate:を付けてしまえばクラスっぽくなると言うね。
となると、構造体でもメンバ関数(メソッド)を用意することができる。

同じようなコードにすると下記なような感じになる。

#include <iostream>
#include <string.h>
using namespace std;
class CFoo
{
private:
int a;
double b;
char c[128];
public:
CFoo():a(0),b(0.0)
{
for (int i = 0; i < 128; ++i)
c[i] = '\0';
}
~CFoo() {}
int setA(int num)
{
a = num;
return 0;
}
int setB(double num)
{
b = num;
return 0;
}
int setC(char* szStr)
{
if (strlen(szStr) > 127) {
return 1;
}
strcpy(c, szStr);
return 0;
}
int getA() { return a; }
double getB() { return b; }
char* getC() { return c; }
};
struct Bar_t
{
private:
int a;
double b;
char c[128];
public:
Bar_t():a(0),b(0.0)
{
for (int i = 0; i < 128; ++i)
c[i] = '\0';
}
~Bar_t() {}
int setA(int num)
{
a = num;
return 0;
}
int setB(double num)
{
b = num;
return 0;
}
int setC(char* szStr)
{
if (strlen(szStr) > 127) {
return 1;
}
strcpy(c, szStr);
return 0;
}
int getA() { return a; }
double getB() { return b; }
char* getC() { return c; }
};
int main()
{
CFoo obj;
Bar_t obj2;
cout << "初期化時のクラス:\n" <<
"\tobj.getA() = " << obj.getA() <<
"\n\tobj.getB() = " << obj.getB() <<
"\n\tobj.getC() = " << obj.getC() << endl;
cout << "初期化時の構造体:\n" <<
"\tobj2.getA() = " << obj2.getA() <<
"\n\tobj2.getB() = " << obj2.getB() <<
"\n\tobj2.getC() = " << obj2.getC() << endl;
obj.setA(10000);
obj.setB(11.11);
obj.setC("我が輩の年齢は10万と26歳である、フハハハハ!");
obj2.setA(-1);
obj2.setB(42.195);
obj2.setC("オラ、ワクワクしてきたぞ!");
cout << "代入時のクラス:\n" <<
"\tobj.getA() = " << obj.getA() <<
"\n\tobj.getB() = " << obj.getB() <<
"\n\tobj.getC() = " << obj.getC() << endl;
cout << "代入時の構造体:\n" <<
"\tobj2.getA() = " << obj2.getA() <<
"\n\tobj2.getB() = " << obj2.getB() <<
"\n\tobj2.getC() = " << obj2.getC() << endl;
return 0;
}

何となく命名規則をそれっぽくしてみたけど、こんな感じ?
ほとんどというか、private:とpublic:を付けてしまえば両方とも一緒という結果。

そんだけ。

Post to Twitter

, , ,

No Comments

C++でのメンバ変数として、動的オブジェクトを作る

C++を勉強していて何が困ったって、メインで動的にメモリ領域をとる方法はたくさん載っているのに、クラスのメンバ変数を動的にとる方法がなかなか載っていない。
と言うわけで、いろいろ試行錯誤した結果、下記のようになった。

テスト用コード

#include <iostream>
#include <string.h>
using namespace std;
class hoge
{
public:
char* szStr;
hoge(char* szNewStr)
{
szStr = new char[strlen(szNewStr)];
strcpy(szStr, szNewStr);
}
~hoge() { delete szNewStr; }
};
int main()
{
char szStr[] = {"ばかやろ~~~~~~~~~~~~~~~"};
cout << szStr << endl;
cout << "end" << endl;
cout << strlen(szStr) << endl;
hoge* obj;
obj = new hoge(szStr);
cout << "\ntest\n";
cout << szStr << endl;
delete obj;
return 0;
}

実行結果。

ばかやろ~~~~~~~~~~~~~~~
end
57
test
ばかやろ~~~~~~~~~~~~~~~

まず、hogeなるクラスを定義します、と。
そのメンバ変数に動的に確保したい型のポインタを置きます、と。
それをコンストラクタなり、なんなりでnewしてあげるとそこにで動的に確保される、と。
ちなみに、ここで言う「型」というのは文字通りの型じゃなくても良くて、クラスでも良い。
なぜかというと、C++でのクラスは構造体を拡張したものだから。

その詳細はまた別の記事にて。

そんだけ。

Post to Twitter

, , ,

No Comments

C++でbad_alloc例外を2回取るテスト

と言うか、2回エラーをキャッチすることができるンかい?と言うお話し。
ほら、メモリを取得するときに多めにとってダメで、少なめに取ったらOKかもしれない場合、どうなのかなぁ?とかそんなこと思っただけ。
まぁ、メモリ取れなかった時点で普通はもうやめちゃう訳なんだけれども。
一応出来るのか出来ないのかをはっきりさせたかった。

で、テストコード。

#include <iostream>
#include <iomanip>
#include <new>
using namespace std;
#define MEMSIZE 10000000
int main()
{
double* pDat;
int i = MEMSIZE;
while (true) {
try {
pDat = new double[i];
}
catch (bad_alloc) {
pDat = NULL;
break;
}
cout << "#1 count " << setiosflags( ios::right )
<< setw(2) << i/MEMSIZE << "  |  "
<< setw(9) << i << "bytes | "
<< setw(6) << i/1000 << "Kbytes | "
<< setw(3) << i/1000000 << "Mbytes\n";
delete pDat;
i += MEMSIZE;
}
cout << "after bad_alloc\n";
i -= MEMSIZE;
cout << i/1000000 << "Mbytes can take " << i/sizeof(char) << " characters\n\n";
i = MEMSIZE;
while (true) {
try {
pDat = new double[i];
}
catch (bad_alloc) {
pDat = NULL;
break;
}
cout << "#2 count " << setiosflags( ios::right )
<< setw(2) << i/MEMSIZE << "  |  "
<< setw(9) << i << "bytes | "
<< setw(6) << i/1000 << "Kbytes | "
<< setw(3) << i/1000000 << "Mbytes\n";
delete pDat;
i += MEMSIZE;
}
cout << "after bad_alloc\n";
i -= MEMSIZE;
cout << i/1000000 << "Mbytes can take " << i/sizeof(char) << " characters\n\n";
return 0;
}

実行結果。

#1 count  1  |   10000000bytes |  10000Kbytes |  10Mbytes
#1 count  2  |   20000000bytes |  20000Kbytes |  20Mbytes
#1 count  3  |   30000000bytes |  30000Kbytes |  30Mbytes
#1 count  4  |   40000000bytes |  40000Kbytes |  40Mbytes
#1 count  5  |   50000000bytes |  50000Kbytes |  50Mbytes
#1 count  6  |   60000000bytes |  60000Kbytes |  60Mbytes
#1 count  7  |   70000000bytes |  70000Kbytes |  70Mbytes
#1 count  8  |   80000000bytes |  80000Kbytes |  80Mbytes
#1 count  9  |   90000000bytes |  90000Kbytes |  90Mbytes
#1 count 10  |  100000000bytes | 100000Kbytes | 100Mbytes
#1 count 11  |  110000000bytes | 110000Kbytes | 110Mbytes
#1 count 12  |  120000000bytes | 120000Kbytes | 120Mbytes
#1 count 13  |  130000000bytes | 130000Kbytes | 130Mbytes
#1 count 14  |  140000000bytes | 140000Kbytes | 140Mbytes
#1 count 15  |  150000000bytes | 150000Kbytes | 150Mbytes
after bad_alloc
#2 count  1  |   10000000bytes |  10000Kbytes |  10Mbytes
#2 count  2  |   20000000bytes |  20000Kbytes |  20Mbytes
#2 count  3  |   30000000bytes |  30000Kbytes |  30Mbytes
#2 count  4  |   40000000bytes |  40000Kbytes |  40Mbytes
#2 count  5  |   50000000bytes |  50000Kbytes |  50Mbytes
#2 count  6  |   60000000bytes |  60000Kbytes |  60Mbytes
#2 count  7  |   70000000bytes |  70000Kbytes |  70Mbytes
#2 count  8  |   80000000bytes |  80000Kbytes |  80Mbytes
#2 count  9  |   90000000bytes |  90000Kbytes |  90Mbytes
#2 count 10  |  100000000bytes | 100000Kbytes | 100Mbytes
#2 count 11  |  110000000bytes | 110000Kbytes | 110Mbytes
#2 count 12  |  120000000bytes | 120000Kbytes | 120Mbytes
#2 count 13  |  130000000bytes | 130000Kbytes | 130Mbytes
#2 count 14  |  140000000bytes | 140000Kbytes | 140Mbytes
#2 count 15  |  150000000bytes | 150000Kbytes | 150Mbytes
after bad_alloc

結論、出来るw

おそまつ。

Post to Twitter

, , ,

No Comments

Visual Studio 2008 Express Editionを使い始める、の巻

ちょっとCとC++の勉強もあったまってきたので、「猫でもできるプログラミング」を参考にしながらWindows SDKプログラミングもちょっくらいじりだそうかと思っている昨今。
さっそくイントロダクションでつまったので、備忘録を載せておくことに。

写経すること数分、正直言ってビルド失敗。
で、チキン野郎なので、そのままコピペ・・・したら(エンコーディングの問題からか、)1行になったので、それを分解・・・。
するとどうでしょう。
うごかねぇ~~。。

そのエラーを探ること数分、見つけましたよ、原因を。
そもそも、このページのオーナーさんである粂井さんが一連の記事を書き始めたときのVCのバージョンが4.0だったことが事の発端なんだとか。
というわけで、どこを直せばいいかを書いておこうかなぁ、と。

まず、コンパイラのエラーを見る。

error C2440: '=' : 'HGDIOBJ' から 'HBRUSH' に変換できません。
'void*' から非 'void' 型への変換には明示的なキャストが必要です。
error C2440: '=' : 'char [25]' から 'LPCWSTR' に変換できません。
指示された型は関連がありません。変換には reinterpret_cast、C スタイル キャストまたは関数スタイルのキャストが必要です。
error C2664: 'CreateWindowExW' : 2 番目の引数を 'char [25]' から 'LPCWSTR' に変換できません。(新しい機能 ; ヘルプを参照)
指示された型は関連がありません。変換には reinterpret_cast、C スタイル キャストまたは関数スタイルのキャストが必要です。

最初に、本来HBRUSH型を要求するhbrBackgroundにGetStockObject(WHITE_BRUSH)を入れていることからエラーとなったご様子。
HGDIOBにキャストしてみる。

2番目はszClassNameがchar型なのに対して、LPCWSTR型を要求する場所を引数として入れていることが問題のご様子。
LPCWSTRにキャストしてみる。
(3箇所)

で、該当箇所はこの辺り。

	WNDCLASS myProg;
if (!hPreInst) {
myProg.style			= CS_HREDRAW | CS_VREDRAW;
myProg.lpfnWndProc		= WndProc;
myProg.cbClsExtra		= 0;
myProg.cbWndExtra		= 0;
myProg.hInstance		= hInstance;
myProg.hIcon			= NULL;
myProg.hCursor			= LoadCursor(NULL, IDC_ARROW);
// VC 6.0以降は型のチェックが徐々に厳しくなっている。
// 本来HBRUSH型を要求するhbrBackgroundにGetStockObject(WHITE_BRUSH)を入れたところ、
// エラーとなるのでHGDIOBにキャストしている。
//myProg.hbrBackground	= GetStockObject(WHITE_BRUSH);
myProg.hbrBackground	= (HBRUSH) GetStockObject(WHITE_BRUSH);
myProg.lpszMenuName		= NULL;
// myProg.lpszClassName	= szClassNme;
myProg.lpszClassName	= (LPCWSTR) szClassNme;
if (!RegisterClass(&myProg)) return FALSE;
}
hWnd = CreateWindow(
//szClassNme,
//"俺にもできる?Windowsプログラミング",
(LPCWSTR) szClassNme,
(LPCWSTR) "俺にもできる?Windowsプログラミング",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);

で、これを実行すると・・・。
blank_window_false
タイトル、化けちゃってるね・・・。

どうやら、そもそもの修正方法が違うらしい。
粂井さんのサンプルはどうやらSJIS前提らしく、関数もSJIS前提の関数とかっぽい??
現状のプロジェクトファイルはUnicodeで設定されていて、それを直したりすれば行けるっぽい。

とりあえず、文字エンコーディングを直す。
(Alt-F7でプロジェクトのプロパティ)
プロジェクトのプロパティで「構成」が「Unicode文字セットを使用する」とか書いてあると思うんだけど、
vc2008express_property_unicode
「マルチバイト文字セットを使用する」とする。
vc2008express_property_sjis
恐らくこれでSJISになる。

で、今度は別のエラーが。

error C2440: '=' : 'LPCWSTR' から 'LPCSTR' に変換できません。
指示された型は関連がありません。変換には reinterpret_cast、C スタイル キャストまたは関数スタイルのキャストが必要です。
error C2664: 'CreateWindowExA' : 2 番目の引数を 'LPCWSTR' から 'LPCSTR' に変換できません。(新しい機能 ; ヘルプを参照)
指示された型は関連がありません。変換には reinterpret_cast、C スタイル キャストまたは関数スタイルのキャストが必要です。

なんだとか。
・・・LPCWSTR型にしなくてよかった、と。。。

さっきの箇所は、これでいいらしい・・・。

	WNDCLASS myProg;
if (!hPreInst) {
myProg.style			= CS_HREDRAW | CS_VREDRAW;
myProg.lpfnWndProc		= WndProc;
myProg.cbClsExtra		= 0;
myProg.cbWndExtra		= 0;
myProg.hInstance		= hInstance;
myProg.hIcon			= NULL;
myProg.hCursor			= LoadCursor(NULL, IDC_ARROW);
// VC 6.0以降は型のチェックが徐々に厳しくなっている。
// 本来HBRUSH型を要求するhbrBackgroundにGetStockObject(WHITE_BRUSH)を入れたところ、
// エラーとなるのでHGDIOBにキャストしている。
//myProg.hbrBackground	= GetStockObject(WHITE_BRUSH);
myProg.hbrBackground	= (HBRUSH) GetStockObject(WHITE_BRUSH);
myProg.lpszMenuName		= NULL;
myProg.lpszClassName	= szClassNme;
if (!RegisterClass(&myProg)) return FALSE;
}
hWnd = CreateWindow(
szClassNme,
"俺にもできる?Windowsプログラミング",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);

結果はこれ。
blank_window_success

お粗末さまでした。

Post to Twitter

, , , ,

No Comments

インライン関数について考える

一個前の投稿ではCの仮引数付きマクロについて取り上げたけど、本題はC++のインライン関数だったり。
仮引数付きマクロでは()で正しく演算順序を指定しないと問題が起きがちですよ、と。
で、CからインクリメントしたC++ではそれを一歩上行くインライン関数と言う物があり、より関数に近い形で置換できると言う優れもの。
使い方は以下の通り。

#include <iostream>
using namespace std;
#define PI 3.141592674
inline double area_of_circle(double r)
{
return r * r * PI;
}
int main()
{
double radius;
cout << "円の半径を入力して下さい。\n";
cin >> radius;
cout << "円の面積は" << area_of_circle(radius - 1.0 + 1.0) << "です。\n";
return 0;
}

実行結果は以下の通り

McLaren% ./inline_test
円の半径を入力して下さい。
4
円の面積は50.2655です。

18行目のように、わざと計算を挟んでみた。
仮引数付きマクロならば

radius - 1.0 + (1.0 * radius) - 1.0 + (1.0 * PI)

となるところを、

(radius - 1.0 + 1.0) * (radius - 1.0 + 1.0) * PI

と言うように、パーレンで囲わなくてもコンパイラが適宜最適化してくれると言う仕組み。
型の指定をしていることからわかる通り、型もチェックしてくれます、と。
これは便利。

ただ、独習C++によると

inline指定子は、コンパイラにとってはコマンドではなく要求であることを覚えておいてください。

とのこと。
どうやらループとか書いてあるとダメなコンパイラもあるらしい。
ただ、普及しているコンパイラはOKっぽい?
そもそも自分で明示的にインライン関数にするのは好ましくないから、コンパイラが自動的にインライン化する機能を有しているので、それに任せた方が良いんだとか。
ん〜、さすが後発言語。
何から何まで便利。

で、インライン化できなかった場合は普通の関数になるらしい。
ここで疑問になるのがプロトタイプ宣言の是非なんだけど、Wikipediaによれば

インライン関数はモジュール単位に定義する必要がある(通常の関数は1つのモジュールで定義すればよい)。これにより、モジュール単位に独立したコンパイルができるようになっている。

となる。
と言うことは、モジュール単位で書くのが当然で、外部から呼び出すのは無理?もしくはナンセンス?
一応ちょっと試したんだけど、ヘッダに書いたら読める。
ただ、別ソースファイルに書いたら読めなかった。
なるほど、独立しているとはそう言うことなのか??

一応動くソースは下記の通り。
(文字列を渡すところで警告あり。でも動く。)

inline_test_main.cpp

#include <iostream>
#include "inline.h"
using namespace std;
int main()
{
double radius;
echo("円の半径を入力して下さい。");
cin >> radius;
cout << "円の面積は" << area_of_circle(radius) << "です。\n";
return 0;
}

inline.h

#include <iostream>
using namespace std;
#define PI 3.141592674
void echo(char []);
inline double area_of_circle(double r)
{
return r * r * PI;
}

inline.cpp

#include "inline.h"
void echo(char str[])
{
cout << str << endl;
}

コンパイルと実行結果は下記のとおり。

McLaren% g++ -o inline inline_test_main.cpp inline.cpp
inline_test_main.cpp: In function ‘int main()’:
inline_test_main.cpp:9: 警告: deprecated conversion from string constant to ‘char*’
McLaren% ./inline
円の半径を入力して下さい。
3
円の面積は28.2743です。

そんだけ。

Post to Twitter

, , , ,

No Comments