Q:
OpenGL エントリポイントへの関数ポインタを取得するには、どうしたらよいのでしょうか?
A:
OpenGL エントリポイントへの関数ポインタは、Cocoa または Carbon のどちらからでも単純な手続きで取得します。Cocoa Mach-O または Carbon Mach-O では、既存の NSLookupAndBindSymbol を使用して OpenGL エントリポイントのアドレスを取得します。Carbon CFM では手続きがやや複雑で、OpenGL Framework バンドルを探し出し、CFBundleGetFunctionPointerForName 関数を使用して実際の関数を取得する必要があります。Carbon の手続きは、そのまま Mach-O および CFM の 2 つのバイナリ形式でも機能します。
OpenGL のエントリポイントの数は増え続けていますが、全エントリポイントがあらゆるバージョンの Mac OS X でエクスポートされているわけではありません。関数ポインタを使用するというこの手法を、適切な拡張ストリングのチェックおよび OpenGL のバージョンチェックと組み合わせれば、開発者は、Mac OS X の多様なバージョンを対象に開発を行うことができます。これは、リンク時あるいは実行時にエントリポイントがサポートされているかどうかを問いません。さらに、Windows または Linux 出身の多くの開発者が使用するコードでは、wglGetProcAddress のような関数を使用して OpenGL エントリポイントへの関数ポインタを取得する仕込みになっています。この Q&A では、これらの問題に対する解決方法を提供し、Mac OS X で関数ポインタを簡単に使用できるようにします。下記のリストに、Mach-O アプリケーションおよび CFM アプリケーション向けの Cocoa バージョンおよび Carbon バージョンの GetProcAddress を示します。
リスト 1 に、NSGLGetProcAddress の詳細を示します。このコードではまず、要求されたシンボルネームを標準的な名前マングリング(名前変換)規則に従って修正した後、シンボルが定義されていることを確認しています。そして最後に、シンボルを調べて適切なポインタを返しています。このようにして返されたポインタを使用する前には、ポインタが有効な(ゼロではない)ことを確認するべきです。なお、NSSymbol 関数は system framework の一部であって、Cocoa の一部ではないので、NSGLGetProcAddress は Cocoa アプリケーションにはもちろんのこと、Mach-O Carbon アプリケーションにも使用できます。
#import <mach-o/dyld.h>
#import <stdlib.h>
#import <string.h>
void *NSGLGetProcAddress(const char *name)
{
NSSymbol symbol;
char *symbolName;
// UNIX における C のシンボルマングリング規則
// に従って、‘_’を前に付けます
symbolName = malloc (strlen (name) + 2);
strcpy(symbolName + 1, name);
symbolName[0] = '_';
symbol = NULL;
if (NSIsSymbolNameDefined (symbolName))
symbol = NSLookupAndBindSymbol (symbolName);
free (symbolName);
return symbol ? NSAddressOfSymbol (symbol) : NULL;
}
| |
リスト 1. NSGLGetProcAddress
|
リスト 2 に示す aglGetProcAddress には 3 つの関数が含まれています。1 つ目は aglInitEntryPoints です。これは、バンドル参照をセットアップします。2 つ目は aglDellocEntryPoints です。これは参照の解放によりバンドルを一掃します。そして 3 つ目は、実際にアドレスを取得する aglGetProcAddress 関数です。aglInitEntryPoints を見ると、初期セットアップの後に、「Frameworks」フォルダを探し出して CFURL へ変換するための処理が続き、最後にバンドルを作成して実行ファイルをロードしていることがわかります。いったんこの処理が完了すれば、OpenGL Framework の既存の任意のエントリを対象に aglGetProcAddress を呼び出せます。終了時、アプリケーションは必ず aglDellocEntryPoints を呼び出してバンドルの割り当てを解除する必要があります。aglDellocEntryPoints は、バンドルをアンロードし、それに対するリファレンスを解放するものです。このコードは Carbon を前提としており、Mach-O Carbon アプリケーションおよび CFM Carbon アプリケーションで動作するように設計されています。
#include <Carbon/Carbon.h>
CFBundleRef gBundleRefOpenGL = NULL;
// -------------------------
OSStatus aglInitEntryPoints (void)
{
OSStatus err = noErr;
const Str255 frameworkName = "\pOpenGL.framework";
FSRefParam fileRefParam;
FSRef fileRef;
CFURLRef bundleURLOpenGL;
memset(&fileRefParam, 0, sizeof(fileRefParam));
memset(&fileRef, 0, sizeof(fileRef));
fileRefParam.ioNamePtr = frameworkName;
fileRefParam.newRef = &fileRef;
// 「Frameworks」ディレクトリ/フォルダ
err = FindFolder (kSystemDomain, kFrameworksFolderType, false,
&fileRefParam.ioVRefNum, &fileRefParam.ioDirID);
if (noErr != err) {
DebugStr ("\pCould not find frameworks folder");
return err;
}
err = PBMakeFSRefSync (&fileRefParam); // フォルダを指す FSRef を作成する
if (noErr != err) {
DebugStr ("\pCould make FSref to frameworks folder");
return err;
}
// フォルダを指す URL を作成する
bundleURLOpenGL = CFURLCreateFromFSRef (kCFAllocatorDefault,
&fileRef);
if (!bundleURLOpenGL) {
DebugStr ("\pCould create OpenGL Framework bundle URL");
return paramErr;
}
// GL のバンドルを指す Ref を作成する
gBundleRefOpenGL = CFBundleCreate (kCFAllocatorDefault,
bundleURLOpenGL);
if (!gBundleRefOpenGL) {
DebugStr ("\pCould not create OpenGL Framework bundle");
return paramErr;
}
CFRelease (bundleURLOpenGL); // 作成したバンドルを解放する
// バンドルのコードを正しくロードできたら、ファンクションを探す
if (!CFBundleLoadExecutable (gBundleRefOpenGL)) {
DebugStr ("\pCould not load MachO executable");
return paramErr;
}
return err;
}
// -------------------------
void aglDellocEntryPoints (void)
{
if (gBundleRefOpenGL != NULL) {
// バンドルのコードをアンロードします
CFBundleUnloadExecutable (gBundleRefOpenGL);
CFRelease (gBundleRefOpenGL);
gBundleRefOpenGL = NULL;
}
}
// -------------------------
void * aglGetProcAddress (char * pszProc)
{
return CFBundleGetFunctionPointerForName (gBundleRefOpenGL,
CFStringCreateWithCStringNoCopy (NULL,
pszProc, CFStringGetSystemEncoding (), NULL));
}
| |
リスト 2. aglGetProcAddress
|
リスト 3 とリスト 4 にコードの抜粋を 2 つ示します。これらは、いくつかの OpenGL エントリポイントを対象に aglGetProcAddress と NSGLGetProcAddress を使用する方法を示します。両者は大変よく似ていますが、aglGetProcAddress では aglInitEntryPoints を呼び出して、関数ポインタの取得に先立ってバンドルをロードし、aglDellocEntryPoints を呼び出して、アプリケーションの終了に先立ってバンドルをアンロードしています。これら以外の点では、両者はほぼ同じです。
#import "NSGLGetProcAddress.h" // リスト 1 における aglGetProcAddress ファン
// クションを取得するヘッダファイル
static void InitEntryPoints (void);
static void DeallocEntryPoints (void);
// ファンクションポインタを typedef で定義する
typedef void (*glBlendColorProcPtr)(GLclampf red,
GLclampf green,
GLclampf blue,
GLclampf alpha);
typedef void (*glBlendEquationProcPtr)(GLenum mode);
typedef void (*glDrawRangeElementsProcPtr)(GLenum mode,
GLuint start,
GLuint end,
GLsizei count,
GLenum type,
const GLvoid *indices);
// ファンクションポインタ宣言
// 注意:ファンクション名をそのまま使用しないこと
// pfXXX は、「ファンクションへのポインタ」を
// を表す省略形として選んだ
glBlendColorProcPtr pfglBlendColor = NULL;
glBlendEquationProcPtr pfglBlendEquation = NULL;
glDrawRangeElementsProcPtr pfglDrawRangeElements = NULL;
// ==========================
static void InitEntryPoints (void)
{
pfglBlendColor = (glBlendColorProcPtr)
NSGLGetProcAddress ("glBlendColor");
pfglBlendEquation = (glBlendEquationProcPtr)
NSGLGetProcAddress ("glBlendEquation");
pfglDrawRangeElements = (glDrawRangeElementsProcPtr)
NSGLGetProcAddress ("glDrawRangeElements");
}
// -------------------------
static void DeallocEntryPoints (void)
{
pfglBlendColor = NULL;
pfglBlendEquation = NULL;
pfglDrawRangeElements = NULL;;
}
| |
リスト 3. NSGLGetProcAddress の使用例
|
#include "aglGetProcAddress.h" // リスト 2 における aglGetProcAddress フ
// ァンクションを取得するヘッダファイル
static OSStatus InitEntryPoints (void);
static void DeallocEntryPoints (void);
// ファンクションポインタを typedef で定義する
typedef void (*glBlendColorProcPtr)(GLclampf red,
GLclampf green,
GLclampf blue,
GLclampf alpha);
typedef void (*glBlendEquationProcPtr)(GLenum mode);
typedef void (*glDrawRangeElementsProcPtr)(GLenum mode,
GLuint start,
GLuint end,
GLsizei count,
GLenum type,
const GLvoid *indices);
// ファンクションポインタ宣言
// 注意:ファンクション名をそのまま使用しないこと
// pfXXX は、「ファンクションへのポインタ」を
// 表す省略形として選んだ
glBlendColorProcPtr pfglBlendColor = NULL;
glBlendEquationProcPtr pfglBlendEquation = NULL;
glDrawRangeElementsProcPtr pfglDrawRangeElements = NULL;
// ==========================
static OSStatus InitEntryPoints (void)
{
OSStatus err = aglInitEntryPoints (); // バンドルの初期化
if (noErr == err) {
pfglBlendColor = (glBlendColorProcPtr)
aglGetProcAddress ("glBlendColor");
pfglBlendEquation = (glBlendEquationProcPtr)
aglGetProcAddress ("glBlendEquation");
pfglDrawRangeElements = (glDrawRangeElementsProcPtr)
aglGetProcAddress ("glDrawRangeElements");
}
return err;
}
// -------------------------
static void DeallocEntryPoints (void)
{
pfglBlendColor = NULL;
pfglBlendEquation = NULL;
pfglDrawRangeElements = NULL;;
aglDellocEntryPoints (); // バンドルの破棄
}
| |
リスト 4. aglGetProcAddress の使用例
|
OpenGL エントリポイントへの関数ポインタの使用に関して、もう一点、知っておくとよいことがあります。有効な関数ポインタを取得できても、それはそのエントリポイントが OpenGL Framework によってエクスポートされていることを意味するだけで、そのルーチンがサポートされていて、アプリケーションの中から呼び出せるかどうかを示すものではありません。OpenGL アプリケーションでは、(動的にロードされたかどうかにかかわらず)OpenGL エントリポイントを呼び出す前に、必ずレンダラの現在の拡張ストリングまたは OpenGL のバージョン番号、あるいはその両方をチェックして、使おうとしている機能がサポートされているかどうかを確認するべきです。つまり、リンク時または実行時のエントリポイントの存在は、特定のレンダラにおいて特定の機能がサポートされていることを示すものではありません。
まとめると、複数バージョンの OS を対象に開発をする場合、対象となるすべての OS バージョンにエントリポイントが存在するとは限らないため、未定義のエントリポイントによる起動エラーを回避するために、CFM アプリケーションでは、OpenGL スタブライブラリに対する弱いリンクを有するか、関数ポインタを使用することが望ましく、Mach-O アプリケーションでは、エントリポイントへの関数ポインタを使用することが望ましいということになります。
[2002 年 11 月 25 日]
|