2 * ALURE OpenAL utility library
3 * Copyright (c) 2009 by Chris Robinson.
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 /* Title: Main and Miscellanious */
42 std::map
<ALint
,UserCallbacks
> InstalledCallbacks
;
43 CRITICAL_SECTION cs_StreamPlay
;
44 alureStream::ListType
alureStream::StreamList
;
46 PFNALCSETTHREADCONTEXTPROC alcSetThreadContext
;
47 PFNALCGETTHREADCONTEXTPROC alcGetThreadContext
;
51 static inline void LoadALCProc(ALCdevice
*dev
, const char *name
, T
**ptr
)
52 { *ptr
= reinterpret_cast<T
*>(alcGetProcAddress(dev
, name
)); }
55 #ifdef HAVE_GCC_CONSTRUCTOR
56 static void init_alure(void) __attribute__((constructor
));
57 static void deinit_alure(void) __attribute__((destructor
));
58 static struct MyConstructorClass
{
60 { alureStream::Clear(); };
62 #elif defined(_WIN32) && !defined(ALURE_STATIC_LIBRARY)
63 static void init_alure(void);
64 static void deinit_alure(void);
65 static struct MyConstructorClass
{
67 { alureStream::Clear(); };
70 extern "C" BOOL APIENTRY
DllMain(HINSTANCE module
, DWORD reason
, LPVOID
/*reserved*/)
72 // Perform actions based on the reason for calling.
75 case DLL_PROCESS_ATTACH
:
76 DisableThreadLibraryCalls(module
);
80 case DLL_PROCESS_DETACH
:
87 static void init_alure(void);
88 static void deinit_alure(void);
90 static struct MyConstructorClass
{
94 { alureStream::Clear();
99 static void init_alure(void)
101 InitializeCriticalSection(&cs_StreamPlay
);
103 // These calls actually just return references to the codecs' Decoder
104 // objects. They aren't really used for anything other than to prevent the
105 // compiler from removing the codec initializers.
108 #ifdef HAS_VORBISFILE
109 alure_init_vorbisfile();
115 alure_init_sndfile();
117 #ifdef HAS_FLUIDSYNTH
118 alure_init_fluidsynth();
124 alure_init_modplug();
130 if(alcIsExtensionPresent(NULL
, "ALC_EXT_thread_local_context"))
132 LoadALCProc(NULL
, "alcSetThreadContext", &alcSetThreadContext
);
133 LoadALCProc(NULL
, "alcGetThreadContext", &alcGetThreadContext
);
134 if(!alcSetThreadContext
|| !alcGetThreadContext
)
136 fprintf(stderr
, "Alure lib: ALC_EXT_thread_local_context advertised, but missing function:\n"
137 " alcSetThreadContext=%p\n"
138 " alcGetThreadContext=%p\n",
139 alcSetThreadContext
, alcGetThreadContext
);
140 alcSetThreadContext
= NULL
;
141 alcGetThreadContext
= NULL
;
146 static void deinit_alure(void)
148 DeleteCriticalSection(&cs_StreamPlay
);
153 void *OpenLib(const char*)
154 { return (void*)0xDEADBEEF; }
158 #elif defined(_WIN32)
160 void *OpenLib(const char *libname
)
161 { return LoadLibraryA(libname
); }
162 void CloseLib(void *hdl
)
163 { FreeLibrary((HINSTANCE
)hdl
); }
164 void *GetLibProc(void *hdl
, const char *funcname
)
165 { return (void*)GetProcAddress((HINSTANCE
)hdl
, funcname
); }
169 void *OpenLib(const char *libname
)
171 const char *err
= dlerror();
172 void *hdl
= dlopen(libname
, RTLD_NOW
);
173 if((err
=dlerror()) != NULL
)
175 fprintf(stderr
, "Error loading %s: %s\n", libname
, err
);
180 void *GetLibProc(void *hdl
, const char *funcname
)
182 const char *err
= dlerror();
183 void *fn
= dlsym(hdl
, funcname
);
184 if((err
=dlerror()) != NULL
)
186 fprintf(stderr
, "Error loading %s: %s\n", funcname
, err
);
191 void CloseLib(void *hdl
)
196 static const ALchar
*last_error
= "No error";
197 void SetError(const char *err
)
203 ALuint
DetectBlockAlignment(ALenum format
)
207 #define CHECK_RET(f,s) case (f): return (s)
208 CHECK_RET(AL_FORMAT_MONO8
, sizeof(ALubyte
));
209 CHECK_RET(AL_FORMAT_MONO16
, sizeof(ALshort
));
210 CHECK_RET(AL_FORMAT_MONO_FLOAT32
, sizeof(ALfloat
));
211 CHECK_RET(AL_FORMAT_MONO_DOUBLE_EXT
, sizeof(ALdouble
));
212 CHECK_RET(AL_FORMAT_MONO_MULAW
, sizeof(ALubyte
)*1);
214 CHECK_RET(AL_FORMAT_STEREO8
, sizeof(ALubyte
)*2);
215 CHECK_RET(AL_FORMAT_STEREO16
, sizeof(ALshort
)*2);
216 CHECK_RET(AL_FORMAT_STEREO_FLOAT32
, sizeof(ALfloat
)*2);
217 CHECK_RET(AL_FORMAT_STEREO_DOUBLE_EXT
, sizeof(ALdouble
)*2);
218 CHECK_RET(AL_FORMAT_STEREO_MULAW
, sizeof(ALubyte
)*2);
220 CHECK_RET(AL_FORMAT_QUAD8
, sizeof(ALubyte
)*4);
221 CHECK_RET(AL_FORMAT_QUAD16
, sizeof(ALshort
)*4);
222 CHECK_RET(AL_FORMAT_QUAD32
, sizeof(ALfloat
)*4);
223 CHECK_RET(AL_FORMAT_QUAD_MULAW
, sizeof(ALubyte
)*4);
225 CHECK_RET(AL_FORMAT_REAR8
, sizeof(ALubyte
)*2);
226 CHECK_RET(AL_FORMAT_REAR16
, sizeof(ALshort
)*2);
227 CHECK_RET(AL_FORMAT_REAR32
, sizeof(ALfloat
)*2);
228 CHECK_RET(AL_FORMAT_REAR_MULAW
, sizeof(ALubyte
)*2);
230 CHECK_RET(AL_FORMAT_51CHN8
, sizeof(ALubyte
)*6);
231 CHECK_RET(AL_FORMAT_51CHN16
, sizeof(ALshort
)*6);
232 CHECK_RET(AL_FORMAT_51CHN32
, sizeof(ALfloat
)*6);
233 CHECK_RET(AL_FORMAT_51CHN_MULAW
, sizeof(ALubyte
)*6);
235 CHECK_RET(AL_FORMAT_61CHN8
, sizeof(ALubyte
)*7);
236 CHECK_RET(AL_FORMAT_61CHN16
, sizeof(ALshort
)*7);
237 CHECK_RET(AL_FORMAT_61CHN32
, sizeof(ALfloat
)*7);
238 CHECK_RET(AL_FORMAT_61CHN_MULAW
, sizeof(ALubyte
)*7);
240 CHECK_RET(AL_FORMAT_71CHN8
, sizeof(ALubyte
)*8);
241 CHECK_RET(AL_FORMAT_71CHN16
, sizeof(ALshort
)*8);
242 CHECK_RET(AL_FORMAT_71CHN32
, sizeof(ALfloat
)*8);
243 CHECK_RET(AL_FORMAT_71CHN_MULAW
, sizeof(ALubyte
)*8);
245 CHECK_RET(AL_FORMAT_MONO_IMA4
, 36);
246 CHECK_RET(AL_FORMAT_STEREO_IMA4
, 36*2);
252 ALuint
DetectCompressionRate(ALenum format
)
256 case AL_FORMAT_MONO8
:
257 case AL_FORMAT_MONO16
:
258 case AL_FORMAT_MONO_FLOAT32
:
259 case AL_FORMAT_MONO_DOUBLE_EXT
:
260 case AL_FORMAT_STEREO8
:
261 case AL_FORMAT_STEREO16
:
262 case AL_FORMAT_STEREO_FLOAT32
:
263 case AL_FORMAT_STEREO_DOUBLE_EXT
:
264 case AL_FORMAT_QUAD8
:
265 case AL_FORMAT_QUAD16
:
266 case AL_FORMAT_QUAD32
:
267 case AL_FORMAT_REAR8
:
268 case AL_FORMAT_REAR16
:
269 case AL_FORMAT_REAR32
:
270 case AL_FORMAT_51CHN8
:
271 case AL_FORMAT_51CHN16
:
272 case AL_FORMAT_51CHN32
:
273 case AL_FORMAT_61CHN8
:
274 case AL_FORMAT_61CHN16
:
275 case AL_FORMAT_61CHN32
:
276 case AL_FORMAT_71CHN8
:
277 case AL_FORMAT_71CHN16
:
278 case AL_FORMAT_71CHN32
:
281 case AL_FORMAT_MONO_MULAW
:
282 case AL_FORMAT_STEREO_MULAW
:
283 case AL_FORMAT_QUAD_MULAW
:
284 case AL_FORMAT_REAR_MULAW
:
285 case AL_FORMAT_51CHN_MULAW
:
286 case AL_FORMAT_61CHN_MULAW
:
287 case AL_FORMAT_71CHN_MULAW
:
290 case AL_FORMAT_MONO_IMA4
:
291 case AL_FORMAT_STEREO_IMA4
:
294 fprintf(stderr
, "Alure lib: Unhandled format: %#x\n", format
);
298 ALenum
GetSampleFormat(ALuint channels
, ALuint bits
, bool isFloat
)
300 #define CHECK_FMT_RET(f) do { \
301 ALenum fmt = alGetEnumValue(#f); \
302 if(alGetError() == AL_NO_ERROR && fmt != 0 && fmt != -1) \
309 if(channels
== 1) CHECK_FMT_RET(AL_FORMAT_MONO8
);
310 if(channels
== 2) CHECK_FMT_RET(AL_FORMAT_STEREO8
);
311 if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
313 if(channels
== 4) CHECK_FMT_RET(AL_FORMAT_QUAD8
);
314 if(channels
== 6) CHECK_FMT_RET(AL_FORMAT_51CHN8
);
315 if(channels
== 7) CHECK_FMT_RET(AL_FORMAT_61CHN8
);
316 if(channels
== 8) CHECK_FMT_RET(AL_FORMAT_71CHN8
);
318 if(alIsExtensionPresent("AL_LOKI_quadriphonic"))
320 if(channels
== 4) CHECK_FMT_RET(AL_FORMAT_QUAD8_LOKI
);
322 SetError("Unsupported 8-bit channel count\n");
327 if(channels
== 1) CHECK_FMT_RET(AL_FORMAT_MONO16
);
328 if(channels
== 2) CHECK_FMT_RET(AL_FORMAT_STEREO16
);
329 if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
331 if(channels
== 4) CHECK_FMT_RET(AL_FORMAT_QUAD16
);
332 if(channels
== 6) CHECK_FMT_RET(AL_FORMAT_51CHN16
);
333 if(channels
== 7) CHECK_FMT_RET(AL_FORMAT_61CHN16
);
334 if(channels
== 8) CHECK_FMT_RET(AL_FORMAT_71CHN16
);
336 if(alIsExtensionPresent("AL_LOKI_quadriphonic"))
338 if(channels
== 4) CHECK_FMT_RET(AL_FORMAT_QUAD16_LOKI
);
340 SetError("Unsupported 16-bit channel count\n");
343 SetError("Unsupported PCM bit depth\n");
347 if(bits
== 32 && alIsExtensionPresent("AL_EXT_FLOAT32"))
349 if(channels
== 1) CHECK_FMT_RET(AL_FORMAT_MONO_FLOAT32
);
350 if(channels
== 2) CHECK_FMT_RET(AL_FORMAT_STEREO_FLOAT32
);
351 if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
353 if(channels
== 4) CHECK_FMT_RET(AL_FORMAT_QUAD32
);
354 if(channels
== 6) CHECK_FMT_RET(AL_FORMAT_51CHN32
);
355 if(channels
== 7) CHECK_FMT_RET(AL_FORMAT_61CHN32
);
356 if(channels
== 8) CHECK_FMT_RET(AL_FORMAT_71CHN32
);
358 SetError("Unsupported float32 channel count\n");
361 if(bits
== 64 && alIsExtensionPresent("AL_EXT_DOUBLE"))
363 if(channels
== 1) CHECK_FMT_RET(AL_FORMAT_MONO_DOUBLE_EXT
);
364 if(channels
== 2) CHECK_FMT_RET(AL_FORMAT_STEREO_DOUBLE_EXT
);
365 SetError("Unsupported double channel count\n");
370 SetError("Unsupported float bit depth\n");
376 /* Function: alureGetVersion
378 * Stores the major and minor version of the library. If either major or minor
379 * are NULL, that value is not provided.
381 ALURE_API
void ALURE_APIENTRY
alureGetVersion(ALuint
*major
, ALuint
*minor
)
383 if(major
) *major
= ALURE_VER_MAJOR
;
384 if(minor
) *minor
= ALURE_VER_MINOR
;
387 /* Function: alureGetErrorString
389 * Returns a string describing the last error encountered.
391 ALURE_API
const ALchar
* ALURE_APIENTRY
alureGetErrorString(void)
393 const ALchar
*ret
= last_error
;
394 last_error
= "No error";
399 /* Function: alureGetDeviceNames
401 * Gets an array of device name strings from OpenAL. This encapsulates
402 * AL_ENUMERATE_ALL_EXT (if supported and 'all' is true) and standard
403 * enumeration, with 'count' being set to the number of returned device
407 * An array of device name strings, or NULL on error.
410 * <alureFreeDeviceNames>
412 ALURE_API
const ALCchar
** ALURE_APIENTRY
alureGetDeviceNames(ALCboolean all
, ALCsizei
*count
)
414 const ALCchar
*list
= NULL
;
415 if(all
&& alcIsExtensionPresent(NULL
, "ALC_ENUMERATE_ALL_EXT"))
416 list
= alcGetString(NULL
, ALC_ALL_DEVICES_SPECIFIER
);
418 list
= alcGetString(NULL
, ALC_DEVICE_SPECIFIER
);
422 SetError("No device names found");
426 const ALCchar
*cur
= list
;
427 ALuint retlistLen
= 0;
430 cur
+= strlen(cur
)+1;
434 const ALCchar
**retlist
= new const ALCchar
*[retlistLen
+1];
439 ALCuint len
= strlen(cur
)+1;
440 ALCchar
*newstr
= new ALCchar
[len
];
442 memcpy(newstr
, cur
, len
);
445 retlist
[retlistLen
] = newstr
;
448 retlist
[retlistLen
] = NULL
;
454 /* Function: alureFreeDeviceNames
456 * Frees the device name array returned from alureGetDeviceNames.
459 * <alureGetDeviceNames>
461 ALURE_API ALvoid ALURE_APIENTRY
alureFreeDeviceNames(const ALCchar
**names
)
465 for(ALCuint i
= 0;names
[i
];i
++)
466 delete[] const_cast<ALCchar
*>(names
[i
]);
472 /* Function: alureInitDevice
474 * Opens the named device, creates a context with the given attributes, and
475 * sets that context as current. The name and attribute list would be the same
476 * as what's passed to alcOpenDevice and alcCreateContext respectively.
482 * <alureShutdownDevice>
484 ALURE_API ALboolean ALURE_APIENTRY
alureInitDevice(const ALCchar
*name
, const ALCint
*attribs
)
486 ALCdevice
*device
= alcOpenDevice(name
);
491 SetError("Device open failed");
495 ALCcontext
*context
= alcCreateContext(device
, attribs
);
496 if(!context
|| alcMakeContextCurrent(context
) == ALC_FALSE
)
499 alcDestroyContext(context
);
500 alcCloseDevice(device
);
502 SetError("Context setup failed");
510 /* Function: alureShutdownDevice
512 * Destroys the current context and closes its associated device.
520 ALURE_API ALboolean ALURE_APIENTRY
alureShutdownDevice(void)
522 ALCcontext
*context
= alcGetCurrentContext();
523 ALCdevice
*device
= alcGetContextsDevice(context
);
524 if(!context
|| !device
)
527 SetError("Failed to get current device");
531 if(alcMakeContextCurrent(NULL
) == ALC_FALSE
)
534 SetError("Failed to unset current context");
538 alcDestroyContext(context
);
539 alcCloseDevice(device
);
546 /* Function: alureGetSampleFormat
548 * Retrieves an OpenAL format for the given sample format. If bits is non-0,
549 * floatbits must be 0, and if floatbits is non-0, bits must be 0. The
550 * application should not rely on any particular format enum being returned as
551 * it is dependant on the available extensions. The returned format will be
552 * valid for the current context. Requires an active context.
555 * An OpenAL format enum for the given sample format, or AL_NONE if one can't
558 ALURE_API ALenum ALURE_APIENTRY
alureGetSampleFormat(ALuint channels
, ALuint bits
, ALuint floatbits
)
560 if(alGetError() != AL_NO_ERROR
)
562 SetError("Existing OpenAL error");
566 if(bits
&& floatbits
)
568 SetError("Both bit-types specified");
573 return GetSampleFormat(channels
, bits
, false);
574 return GetSampleFormat(channels
, floatbits
, true);
578 /* Function: alureInstallDecodeCallbacks
580 * Installs callbacks to enable ALURE to handle more file types. The index is
581 * the order that each given set of callbacks will be tried, starting at the
582 * most negative number (INT_MIN) and going up. Negative indices will be tried
583 * before the built-in decoders, and positive indices will be tried after.
584 * Installing callbacks onto the same index multiple times will remove the
585 * previous callbacks, and removing old callbacks won't affect any opened files
586 * using them (they'll continue to use the old functions until properly closed,
587 * although newly opened files will use the new ones). Passing NULL for all
588 * callbacks is a valid way to remove an installed set, otherwise certain
589 * callbacks must be specified. Callbacks that are not specified will assume
593 * open_file - This callback is expected to open the named file and prepare it
594 * for decoding. If the callbacks cannot decode the file, NULL
595 * should be returned to indicate failure. Upon success, a non-NULL
596 * handle must be returned, which will be used as a unique
597 * identifier for the decoder instance. This callback is required
598 * if open_memory is not specified.
599 * open_memory - This callback behaves the same as open_file, except it takes a
600 * memory segment for input instead of a filename. The given
601 * memory will remain valid while the instance is open. This
602 * callback is required if open_file is not specified.
603 * get_format - This callback is used to retrieve the format of the decoded
604 * data for the given instance. It is the responsibility of the
605 * function to make sure the returned format is valid for the
606 * current AL context (eg. don't return AL_FORMAT_QUAD16 if the
607 * AL_EXT_MCFORMATS extension isn't available). Returning 0 for
608 * samplerate or blocksize, or returning AL_NONE for format, will
609 * cause a failure. Returning AL_FALSE indicates failure. This
610 * callback is required.
611 * decode - This callback is called to get more decoded data. Up to the
612 * specified amount of bytes should be written to the data pointer.
613 * The number of bytes written should be a multiple of the block size,
614 * otherwise an OpenAL error may occur during buffering. The function
615 * should return the number of bytes written. This callback is
617 * rewind - This callback is for rewinding the instance so that the next decode
618 * calls for it will get audio data from the start of the sound file.
619 * If the stream fails to rewind, AL_FALSE should be returned.
620 * close - This callback is called at the end of processing for a particular
621 * instance. The handle will not be used further and any associated
622 * data may be deleted.
627 ALURE_API ALboolean ALURE_APIENTRY
alureInstallDecodeCallbacks(ALint index
,
628 void* (*open_file
)(const ALchar
*filename
),
629 void* (*open_memory
)(const ALubyte
*data
, ALuint length
),
630 ALboolean (*get_format
)(void *instance
, ALenum
*format
, ALuint
*samplerate
, ALuint
*blocksize
),
631 ALuint (*decode
)(void *instance
, ALubyte
*data
, ALuint bytes
),
632 ALboolean (*rewind
)(void *instance
),
633 void (*close
)(void *instance
))
635 if(!open_file
&& !open_memory
&& !get_format
&& !decode
&& !rewind
&& !close
)
637 std::map
<ALint
,UserCallbacks
>::iterator i
= InstalledCallbacks
.find(index
);
638 if(i
!= InstalledCallbacks
.end())
639 InstalledCallbacks
.erase(i
);
643 if((!open_file
&& !open_memory
) || !get_format
|| !decode
)
645 SetError("Missing callback functions");
650 newcb
.open_file
= open_file
;
651 newcb
.open_mem
= open_memory
;
652 newcb
.get_fmt
= get_format
;
653 newcb
.decode
= decode
;
654 newcb
.rewind
= rewind
;
657 InstalledCallbacks
[index
] = newcb
;
663 /* Function: alureSleep
665 * Rests the calling thread for the given number of seconds.
670 ALURE_API ALboolean ALURE_APIENTRY
alureSleep(ALfloat duration
)
674 SetError("Invalid duration");
678 ALuint seconds
= (ALuint
)duration
;
679 ALdouble rest
= duration
- (ALdouble
)seconds
;
681 #ifdef HAVE_NANOSLEEP
683 struct timespec t
, remainingTime
;
684 t
.tv_sec
= (time_t)seconds
;
685 t
.tv_nsec
= (long)(rest
*1000000000);
687 while(nanosleep(&t
, &remainingTime
) < 0 && errno
== EINTR
)
690 #elif defined(HAVE_WINDOWS_H)
697 Sleep((DWORD
)(rest
* 1000));
705 /* Function: alureGetProcAddress
707 * Returns a pointer for the named ALURE function.
712 * *Version Added*: 1.1
714 ALURE_API
void* ALURE_APIENTRY
alureGetProcAddress(const ALchar
*funcname
)
716 static const struct {
720 #define ADD_FUNCTION(x) { #x, (void*)x },
721 ADD_FUNCTION(alureGetVersion
)
722 ADD_FUNCTION(alureGetErrorString
)
723 ADD_FUNCTION(alureGetDeviceNames
)
724 ADD_FUNCTION(alureFreeDeviceNames
)
725 ADD_FUNCTION(alureInitDevice
)
726 ADD_FUNCTION(alureShutdownDevice
)
727 ADD_FUNCTION(alureGetSampleFormat
)
728 ADD_FUNCTION(alureSleep
)
729 ADD_FUNCTION(alureCreateBufferFromFile
)
730 ADD_FUNCTION(alureCreateBufferFromMemory
)
731 ADD_FUNCTION(alureBufferDataFromFile
)
732 ADD_FUNCTION(alureBufferDataFromMemory
)
733 ADD_FUNCTION(alureCreateStreamFromFile
)
734 ADD_FUNCTION(alureCreateStreamFromMemory
)
735 ADD_FUNCTION(alureCreateStreamFromStaticMemory
)
736 ADD_FUNCTION(alureCreateStreamFromCallback
)
737 ADD_FUNCTION(alureRewindStream
)
738 ADD_FUNCTION(alureDestroyStream
)
739 ADD_FUNCTION(alureSetStreamOrder
)
740 ADD_FUNCTION(alureSetStreamPatchset
)
741 ADD_FUNCTION(alureInstallDecodeCallbacks
)
742 ADD_FUNCTION(alureSetIOCallbacks
)
743 ADD_FUNCTION(alureGetProcAddress
)
744 ADD_FUNCTION(alurePlaySourceStream
)
745 ADD_FUNCTION(alurePlaySource
)
746 ADD_FUNCTION(alureStopSource
)
752 for(i
= 0;FunctionList
[i
].name
;i
++)
754 if(strcmp(FunctionList
[i
].name
, funcname
) == 0)
758 if(!FunctionList
[i
].name
)
759 SetError("Function not found");
760 return FunctionList
[i
].func
;