28 #include "../../SDL_internal.h"
31 #if SDL_AUDIO_DRIVER_PULSEAUDIO
39 #include <sys/types.h>
40 #include <pulse/pulseaudio.h>
44 #include "../SDL_audio_c.h"
47 #include "../../thread/SDL_systhread.h"
49 #if (PA_API_VERSION < 12)
51 static SDL_INLINE int PA_CONTEXT_IS_GOOD(pa_context_state_t
x) {
53 x == PA_CONTEXT_CONNECTING ||
54 x == PA_CONTEXT_AUTHORIZING ||
55 x == PA_CONTEXT_SETTING_NAME ||
56 x == PA_CONTEXT_READY;
59 static SDL_INLINE int PA_STREAM_IS_GOOD(pa_stream_state_t
x) {
61 x == PA_STREAM_CREATING ||
67 static const char *(*PULSEAUDIO_pa_get_library_version) (
void);
68 static pa_channel_map *(*PULSEAUDIO_pa_channel_map_init_auto) (
69 pa_channel_map *, unsigned, pa_channel_map_def_t);
70 static const char * (*PULSEAUDIO_pa_strerror) (
int);
71 static pa_mainloop * (*PULSEAUDIO_pa_mainloop_new) (
void);
72 static pa_mainloop_api * (*PULSEAUDIO_pa_mainloop_get_api) (pa_mainloop *);
73 static int (*PULSEAUDIO_pa_mainloop_iterate) (pa_mainloop *,
int,
int *);
74 static int (*PULSEAUDIO_pa_mainloop_run) (pa_mainloop *,
int *);
75 static void (*PULSEAUDIO_pa_mainloop_quit) (pa_mainloop *,
int);
76 static void (*PULSEAUDIO_pa_mainloop_free) (pa_mainloop *);
78 static pa_operation_state_t (*PULSEAUDIO_pa_operation_get_state) (
80 static void (*PULSEAUDIO_pa_operation_cancel) (pa_operation *);
81 static void (*PULSEAUDIO_pa_operation_unref) (pa_operation *);
83 static pa_context * (*PULSEAUDIO_pa_context_new) (pa_mainloop_api *,
85 static int (*PULSEAUDIO_pa_context_connect) (pa_context *,
const char *,
86 pa_context_flags_t,
const pa_spawn_api *);
87 static pa_operation * (*PULSEAUDIO_pa_context_get_sink_info_list) (pa_context *, pa_sink_info_cb_t,
void *);
88 static pa_operation * (*PULSEAUDIO_pa_context_get_source_info_list) (pa_context *, pa_source_info_cb_t,
void *);
89 static pa_operation * (*PULSEAUDIO_pa_context_get_sink_info_by_index) (pa_context *,
uint32_t, pa_sink_info_cb_t,
void *);
90 static pa_operation * (*PULSEAUDIO_pa_context_get_source_info_by_index) (pa_context *,
uint32_t, pa_source_info_cb_t,
void *);
91 static pa_context_state_t (*PULSEAUDIO_pa_context_get_state) (pa_context *);
92 static pa_operation * (*PULSEAUDIO_pa_context_subscribe) (pa_context *, pa_subscription_mask_t, pa_context_success_cb_t,
void *);
93 static void (*PULSEAUDIO_pa_context_set_subscribe_callback) (pa_context *, pa_context_subscribe_cb_t,
void *);
94 static void (*PULSEAUDIO_pa_context_disconnect) (pa_context *);
95 static void (*PULSEAUDIO_pa_context_unref) (pa_context *);
97 static pa_stream * (*PULSEAUDIO_pa_stream_new) (pa_context *,
const char *,
98 const pa_sample_spec *,
const pa_channel_map *);
99 static int (*PULSEAUDIO_pa_stream_connect_playback) (pa_stream *,
const char *,
100 const pa_buffer_attr *, pa_stream_flags_t, pa_cvolume *, pa_stream *);
101 static int (*PULSEAUDIO_pa_stream_connect_record) (pa_stream *,
const char *,
102 const pa_buffer_attr *, pa_stream_flags_t);
103 static pa_stream_state_t (*PULSEAUDIO_pa_stream_get_state) (pa_stream *);
104 static size_t (*PULSEAUDIO_pa_stream_writable_size) (pa_stream *);
105 static size_t (*PULSEAUDIO_pa_stream_readable_size) (pa_stream *);
106 static int (*PULSEAUDIO_pa_stream_write) (pa_stream *,
const void *,
size_t,
107 pa_free_cb_t,
int64_t, pa_seek_mode_t);
108 static pa_operation * (*PULSEAUDIO_pa_stream_drain) (pa_stream *,
109 pa_stream_success_cb_t,
void *);
110 static int (*PULSEAUDIO_pa_stream_peek) (pa_stream *,
const void **,
size_t *);
111 static int (*PULSEAUDIO_pa_stream_drop) (pa_stream *);
112 static pa_operation * (*PULSEAUDIO_pa_stream_flush) (pa_stream *,
113 pa_stream_success_cb_t,
void *);
114 static int (*PULSEAUDIO_pa_stream_disconnect) (pa_stream *);
115 static void (*PULSEAUDIO_pa_stream_unref) (pa_stream *);
117 static int load_pulseaudio_syms(
void);
120 #ifdef SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC
122 static const char *pulseaudio_library = SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC;
123 static void *pulseaudio_handle =
NULL;
126 load_pulseaudio_sym(
const char *fn,
void **
addr)
138 #define SDL_PULSEAUDIO_SYM(x) \
139 if (!load_pulseaudio_sym(#x, (void **) (char *) &PULSEAUDIO_##x)) return -1
142 UnloadPulseAudioLibrary(
void)
144 if (pulseaudio_handle !=
NULL) {
146 pulseaudio_handle =
NULL;
151 LoadPulseAudioLibrary(
void)
154 if (pulseaudio_handle ==
NULL) {
156 if (pulseaudio_handle ==
NULL) {
160 retval = load_pulseaudio_syms();
162 UnloadPulseAudioLibrary();
171 #define SDL_PULSEAUDIO_SYM(x) PULSEAUDIO_##x = x
174 UnloadPulseAudioLibrary(
void)
179 LoadPulseAudioLibrary(
void)
181 load_pulseaudio_syms();
189 load_pulseaudio_syms(
void)
191 SDL_PULSEAUDIO_SYM(pa_get_library_version);
192 SDL_PULSEAUDIO_SYM(pa_mainloop_new);
193 SDL_PULSEAUDIO_SYM(pa_mainloop_get_api);
194 SDL_PULSEAUDIO_SYM(pa_mainloop_iterate);
195 SDL_PULSEAUDIO_SYM(pa_mainloop_run);
196 SDL_PULSEAUDIO_SYM(pa_mainloop_quit);
197 SDL_PULSEAUDIO_SYM(pa_mainloop_free);
198 SDL_PULSEAUDIO_SYM(pa_operation_get_state);
199 SDL_PULSEAUDIO_SYM(pa_operation_cancel);
200 SDL_PULSEAUDIO_SYM(pa_operation_unref);
201 SDL_PULSEAUDIO_SYM(pa_context_new);
202 SDL_PULSEAUDIO_SYM(pa_context_connect);
203 SDL_PULSEAUDIO_SYM(pa_context_get_sink_info_list);
204 SDL_PULSEAUDIO_SYM(pa_context_get_source_info_list);
205 SDL_PULSEAUDIO_SYM(pa_context_get_sink_info_by_index);
206 SDL_PULSEAUDIO_SYM(pa_context_get_source_info_by_index);
207 SDL_PULSEAUDIO_SYM(pa_context_get_state);
208 SDL_PULSEAUDIO_SYM(pa_context_subscribe);
209 SDL_PULSEAUDIO_SYM(pa_context_set_subscribe_callback);
210 SDL_PULSEAUDIO_SYM(pa_context_disconnect);
211 SDL_PULSEAUDIO_SYM(pa_context_unref);
212 SDL_PULSEAUDIO_SYM(pa_stream_new);
213 SDL_PULSEAUDIO_SYM(pa_stream_connect_playback);
214 SDL_PULSEAUDIO_SYM(pa_stream_connect_record);
215 SDL_PULSEAUDIO_SYM(pa_stream_get_state);
216 SDL_PULSEAUDIO_SYM(pa_stream_writable_size);
217 SDL_PULSEAUDIO_SYM(pa_stream_readable_size);
218 SDL_PULSEAUDIO_SYM(pa_stream_write);
219 SDL_PULSEAUDIO_SYM(pa_stream_drain);
220 SDL_PULSEAUDIO_SYM(pa_stream_disconnect);
221 SDL_PULSEAUDIO_SYM(pa_stream_peek);
222 SDL_PULSEAUDIO_SYM(pa_stream_drop);
223 SDL_PULSEAUDIO_SYM(pa_stream_flush);
224 SDL_PULSEAUDIO_SYM(pa_stream_unref);
225 SDL_PULSEAUDIO_SYM(pa_channel_map_init_auto);
226 SDL_PULSEAUDIO_SYM(pa_strerror);
231 squashVersion(
const int major,
const int minor,
const int patch)
233 return ((major & 0xFF) << 16) | ((minor & 0xFF) << 8) | (patch & 0xFF);
242 const char *verstr = PULSEAUDIO_pa_get_library_version();
243 retval =
"SDL Application";
244 if (verstr !=
NULL) {
246 if (
SDL_sscanf(verstr,
"%d.%d.%d", &maj, &
min, &patch) == 3) {
247 if (squashVersion(maj,
min, patch) >= squashVersion(0, 9, 15)) {
257 WaitForPulseOperation(pa_mainloop *mainloop, pa_operation *o)
262 while (okay && (PULSEAUDIO_pa_operation_get_state(o) == PA_OPERATION_RUNNING)) {
263 okay = (PULSEAUDIO_pa_mainloop_iterate(mainloop, 1,
NULL) >= 0);
265 PULSEAUDIO_pa_operation_unref(o);
270 DisconnectFromPulseServer(pa_mainloop *mainloop, pa_context *
context)
273 PULSEAUDIO_pa_context_disconnect(
context);
274 PULSEAUDIO_pa_context_unref(
context);
276 if (mainloop !=
NULL) {
277 PULSEAUDIO_pa_mainloop_free(mainloop);
282 ConnectToPulseServer_Internal(pa_mainloop **_mainloop, pa_context **_context)
284 pa_mainloop *mainloop =
NULL;
286 pa_mainloop_api *mainloop_api =
NULL;
293 if (!(mainloop = PULSEAUDIO_pa_mainloop_new())) {
297 mainloop_api = PULSEAUDIO_pa_mainloop_get_api(mainloop);
300 context = PULSEAUDIO_pa_context_new(mainloop_api, getAppName());
302 PULSEAUDIO_pa_mainloop_free(mainloop);
308 PULSEAUDIO_pa_context_unref(
context);
309 PULSEAUDIO_pa_mainloop_free(mainloop);
310 return SDL_SetError(
"Could not setup connection to PulseAudio");
314 if (PULSEAUDIO_pa_mainloop_iterate(mainloop, 1,
NULL) < 0) {
315 PULSEAUDIO_pa_context_unref(
context);
316 PULSEAUDIO_pa_mainloop_free(mainloop);
320 if (!PA_CONTEXT_IS_GOOD(
state)) {
321 PULSEAUDIO_pa_context_unref(
context);
322 PULSEAUDIO_pa_mainloop_free(mainloop);
325 }
while (
state != PA_CONTEXT_READY);
328 *_mainloop = mainloop;
334 ConnectToPulseServer(pa_mainloop **_mainloop, pa_context **_context)
336 const int retval = ConnectToPulseServer_Internal(_mainloop, _context);
338 DisconnectFromPulseServer(*_mainloop, *_context);
346 PULSEAUDIO_WaitDevice(
_THIS)
351 if (PULSEAUDIO_pa_context_get_state(
h->context) != PA_CONTEXT_READY ||
352 PULSEAUDIO_pa_stream_get_state(
h->stream) != PA_STREAM_READY ||
353 PULSEAUDIO_pa_mainloop_iterate(
h->mainloop, 1,
NULL) < 0) {
357 if (PULSEAUDIO_pa_stream_writable_size(
h->stream) >=
h->mixlen) {
364 PULSEAUDIO_PlayDevice(
_THIS)
369 if (PULSEAUDIO_pa_stream_write(
h->stream,
h->mixbuf,
h->mixlen,
NULL, 0LL, PA_SEEK_RELATIVE) < 0) {
376 PULSEAUDIO_GetDeviceBuf(
_THIS)
378 return (this->hidden->mixbuf);
383 PULSEAUDIO_CaptureFromDevice(
_THIS,
void *
buffer,
int buflen)
390 if (
h->capturebuf !=
NULL) {
391 const int cpy =
SDL_min(buflen,
h->capturelen);
394 h->capturebuf += cpy;
395 h->capturelen -= cpy;
396 if (
h->capturelen == 0) {
397 h->capturebuf =
NULL;
398 PULSEAUDIO_pa_stream_drop(
h->stream);
403 if (PULSEAUDIO_pa_context_get_state(
h->context) != PA_CONTEXT_READY ||
404 PULSEAUDIO_pa_stream_get_state(
h->stream) != PA_STREAM_READY ||
405 PULSEAUDIO_pa_mainloop_iterate(
h->mainloop, 1,
NULL) < 0) {
410 if (PULSEAUDIO_pa_stream_readable_size(
h->stream) == 0) {
415 PULSEAUDIO_pa_stream_peek(
h->stream, &
data, &nbytes);
418 PULSEAUDIO_pa_stream_drop(
h->stream);
423 h->capturelen = nbytes;
431 PULSEAUDIO_FlushCapture(
_THIS)
437 if (
h->capturebuf !=
NULL) {
438 PULSEAUDIO_pa_stream_drop(
h->stream);
439 h->capturebuf =
NULL;
444 if (PULSEAUDIO_pa_context_get_state(
h->context) != PA_CONTEXT_READY ||
445 PULSEAUDIO_pa_stream_get_state(
h->stream) != PA_STREAM_READY ||
446 PULSEAUDIO_pa_mainloop_iterate(
h->mainloop, 1,
NULL) < 0) {
451 if (PULSEAUDIO_pa_stream_readable_size(
h->stream) == 0) {
456 PULSEAUDIO_pa_stream_peek(
h->stream, &
data, &nbytes);
457 PULSEAUDIO_pa_stream_drop(
h->stream);
462 PULSEAUDIO_CloseDevice(
_THIS)
464 if (this->hidden->stream) {
465 if (this->hidden->capturebuf !=
NULL) {
466 PULSEAUDIO_pa_stream_drop(this->hidden->stream);
468 PULSEAUDIO_pa_stream_disconnect(this->hidden->stream);
469 PULSEAUDIO_pa_stream_unref(this->hidden->stream);
472 DisconnectFromPulseServer(this->hidden->mainloop, this->hidden->context);
474 SDL_free(this->hidden->device_name);
479 SinkDeviceNameCallback(pa_context *
c,
const pa_sink_info *
i,
int is_last,
void *
data)
482 char **devname = (
char **)
data;
488 SourceDeviceNameCallback(pa_context *
c,
const pa_source_info *
i,
int is_last,
void *
data)
491 char **devname = (
char **)
data;
506 WaitForPulseOperation(
h->mainloop,
507 PULSEAUDIO_pa_context_get_source_info_by_index(
h->context,
idx,
508 SourceDeviceNameCallback, &
h->device_name));
510 WaitForPulseOperation(
h->mainloop,
511 PULSEAUDIO_pa_context_get_sink_info_by_index(
h->context,
idx,
512 SinkDeviceNameCallback, &
h->device_name));
515 return (
h->device_name !=
NULL);
523 pa_sample_spec paspec;
524 pa_buffer_attr paattr;
525 pa_channel_map pacmap;
526 pa_stream_flags_t
flags = 0;
534 if (this->hidden ==
NULL) {
539 paspec.format = PA_SAMPLE_INVALID;
543 (paspec.format == PA_SAMPLE_INVALID) && test_format;) {
545 fprintf(stderr,
"Trying format 0x%4.4x\n", test_format);
547 switch (test_format) {
549 paspec.format = PA_SAMPLE_U8;
552 paspec.format = PA_SAMPLE_S16LE;
555 paspec.format = PA_SAMPLE_S16BE;
558 paspec.format = PA_SAMPLE_S32LE;
561 paspec.format = PA_SAMPLE_S32BE;
564 paspec.format = PA_SAMPLE_FLOAT32LE;
567 paspec.format = PA_SAMPLE_FLOAT32BE;
570 paspec.format = PA_SAMPLE_INVALID;
573 if (paspec.format == PA_SAMPLE_INVALID) {
577 if (paspec.format == PA_SAMPLE_INVALID) {
578 return SDL_SetError(
"Couldn't find any hardware audio formats");
583 #ifdef PA_STREAM_ADJUST_LATENCY
592 if (
h->mixbuf ==
NULL) {
595 SDL_memset(
h->mixbuf, this->spec.silence, this->spec.size);
602 #ifdef PA_STREAM_ADJUST_LATENCY
604 paattr.tlength =
h->mixlen * 4;
606 paattr.maxlength = -1;
608 paattr.minreq =
h->mixlen;
609 flags = PA_STREAM_ADJUST_LATENCY;
611 paattr.tlength =
h->mixlen*2;
612 paattr.prebuf =
h->mixlen*2;
613 paattr.maxlength =
h->mixlen*2;
614 paattr.minreq =
h->mixlen;
617 if (ConnectToPulseServer(&
h->mainloop, &
h->context) < 0) {
618 return SDL_SetError(
"Could not connect to PulseAudio server");
622 return SDL_SetError(
"Requested PulseAudio sink/source missing?");
627 PULSEAUDIO_pa_channel_map_init_auto(&pacmap, this->
spec.
channels,
628 PA_CHANNEL_MAP_WAVEEX);
632 h->stream = PULSEAUDIO_pa_stream_new(
639 if (
h->stream ==
NULL) {
640 return SDL_SetError(
"Could not set up PulseAudio stream");
645 if (
h->device_name !=
NULL) {
646 flags |= PA_STREAM_DONT_MOVE;
650 rc = PULSEAUDIO_pa_stream_connect_record(
h->stream,
h->device_name, &paattr,
flags);
652 rc = PULSEAUDIO_pa_stream_connect_playback(
h->stream,
h->device_name, &paattr,
flags,
NULL,
NULL);
656 return SDL_SetError(
"Could not connect PulseAudio stream");
660 if (PULSEAUDIO_pa_mainloop_iterate(
h->mainloop, 1,
NULL) < 0) {
663 state = PULSEAUDIO_pa_stream_get_state(
h->stream);
664 if (!PA_STREAM_IS_GOOD(
state)) {
665 return SDL_SetError(
"Could not connect PulseAudio stream");
667 }
while (
state != PA_STREAM_READY);
673 static pa_mainloop *hotplug_mainloop =
NULL;
674 static pa_context *hotplug_context =
NULL;
681 SinkInfoCallback(pa_context *
c,
const pa_sink_info *
i,
int is_last,
void *
data)
690 SourceInfoCallback(pa_context *
c,
const pa_source_info *
i,
int is_last,
void *
data)
694 if (
i->monitor_of_sink == PA_INVALID_INDEX) {
702 HotplugCallback(pa_context *
c, pa_subscription_event_type_t
t,
uint32_t idx,
void *
data)
704 const SDL_bool added = ((
t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW);
705 const SDL_bool removed = ((
t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE);
707 if (added || removed) {
708 const SDL_bool sink = ((
t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK);
709 const SDL_bool source = ((
t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE);
713 PULSEAUDIO_pa_context_get_sink_info_by_index(hotplug_context,
idx, SinkInfoCallback,
NULL);
714 }
else if (added &&
source) {
715 PULSEAUDIO_pa_context_get_source_info_by_index(hotplug_context,
idx, SourceInfoCallback,
NULL);
725 HotplugThread(
void *
data)
729 PULSEAUDIO_pa_context_set_subscribe_callback(hotplug_context, HotplugCallback,
NULL);
730 o = PULSEAUDIO_pa_context_subscribe(hotplug_context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE,
NULL,
NULL);
731 PULSEAUDIO_pa_operation_unref(o);
732 PULSEAUDIO_pa_mainloop_run(hotplug_mainloop,
NULL);
737 PULSEAUDIO_DetectDevices()
739 WaitForPulseOperation(hotplug_mainloop, PULSEAUDIO_pa_context_get_sink_info_list(hotplug_context, SinkInfoCallback,
NULL));
740 WaitForPulseOperation(hotplug_mainloop, PULSEAUDIO_pa_context_get_source_info_list(hotplug_context, SourceInfoCallback,
NULL));
747 PULSEAUDIO_Deinitialize(
void)
749 if (hotplug_thread) {
750 PULSEAUDIO_pa_mainloop_quit(hotplug_mainloop, 0);
752 hotplug_thread =
NULL;
755 DisconnectFromPulseServer(hotplug_mainloop, hotplug_context);
756 hotplug_mainloop =
NULL;
757 hotplug_context =
NULL;
759 UnloadPulseAudioLibrary();
765 if (LoadPulseAudioLibrary() < 0) {
769 if (ConnectToPulseServer(&hotplug_mainloop, &hotplug_context) < 0) {
770 UnloadPulseAudioLibrary();
791 "pulseaudio",
"PulseAudio", PULSEAUDIO_Init, 0
#define SDL_assert(condition)
void SDL_RemoveAudioDevice(const int iscapture, void *handle)
void SDL_AddAudioDevice(const int iscapture, const char *name, void *handle)
void SDL_CalculateAudioSpec(SDL_AudioSpec *spec)
SDL_AudioFormat SDL_FirstAudioFormat(SDL_AudioFormat format)
void SDL_OpenedAudioDeviceDisconnected(SDL_AudioDevice *device)
SDL_AudioFormat SDL_NextAudioFormat(void)
#define SDL_SetThreadPriority
SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char const char SDL_SCANF_FORMAT_STRING const char return SDL_ThreadFunction const char void return Uint32 return Uint32 void
#define SDL_OutOfMemory()
#define SDL_HINT_AUDIO_DEVICE_APP_NAME
Specify an application name for an audio device.
#define SDL_HINT_AUDIO_DEVICE_STREAM_NAME
Specify an application name for an audio device.
void * SDL_LoadFunction(void *handle, const char *name)
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
GLint GLint GLint GLint GLint x
GLenum GLenum GLsizei const GLuint GLboolean enabled
GLsizei GLsizei GLchar * source
GLuint const GLchar * name
GLsizei GLenum GLboolean sink
GLfloat GLfloat GLfloat GLfloat h
AudioBootStrap PULSEAUDIO_bootstrap
SDL_Thread * SDL_CreateThreadInternal(int(*fn)(void *), const char *name, const size_t stacksize, void *data)
@ SDL_THREAD_PRIORITY_LOW
return Display return Display Bool Bool int int int return Display XEvent Bool(*) XPointer return Display return Display Drawable _Xconst char unsigned int unsigned int return Display Pixmap Pixmap XColor XColor unsigned int unsigned int return Display _Xconst char char int char return Display Visual unsigned int int int char unsigned int unsigned int in i)
EGLImageKHR EGLint EGLint * handle
set set set set set set set macro pixldst1 abits if abits op else op endif endm macro pixldst2 abits if abits op else op endif endm macro pixldst4 abits if abits op else op endif endm macro pixldst0 idx
void(* PlayDevice)(_THIS)
void(* WaitDevice)(_THIS)
void(* CloseDevice)(_THIS)
void(* FlushCapture)(_THIS)
void(* DetectDevices)(void)
Uint8 *(* GetDeviceBuf)(_THIS)
void(* Deinitialize)(void)
int(* CaptureFromDevice)(_THIS, void *buffer, int buflen)
int(* OpenDevice)(_THIS, void *handle, const char *devname, int iscapture)
static screen_context_t context
typedef int(__stdcall *FARPROC)()