/[svn]/linuxsampler/trunk/src/drivers/audio/iasiothiscallresolver.cpp
ViewVC logotype

Contents of /linuxsampler/trunk/src/drivers/audio/iasiothiscallresolver.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2175 - (show annotations) (download)
Mon Apr 25 08:12:36 2011 UTC (13 years ago) by persson
File size: 24043 byte(s)
* sfz engine: implemeted filters. Filter types: lowpass, bandpass,
  bandreject and highpass. 1, 2, 4 and 6 pole filters. Opcodes:
  fil_type, cutoff, resonance, fil_veltrack, fil_keytrack,
  fil_keycenter, cutoff_cc, cutoff_chanaft.
* sfz engine: bugfix: zero ampeg_sustain didn't work
* gig engine: bugfix: pitch LFO controller "internal+aftertouch" was broken
* gig engine: bugfix: filter keyboard tracking was broken
* gig engine: filter performance fix (an unnecessary copy was made of
  the filter parameters in each sub fragment)
* ASIO driver: fixes for newer gcc versions (fix from PortAudio)

1 /*
2 IASIOThiscallResolver.cpp see the comments in iasiothiscallresolver.h for
3 the top level description - this comment describes the technical details of
4 the implementation.
5
6 The latest version of this file is available from:
7 http://www.audiomulch.com/~rossb/code/calliasio
8
9 please email comments to Ross Bencina <rossb@audiomulch.com>
10
11 BACKGROUND
12
13 The IASIO interface declared in the Steinberg ASIO 2 SDK declares
14 functions with no explicit calling convention. This causes MSVC++ to default
15 to using the thiscall convention, which is a proprietary convention not
16 implemented by some non-microsoft compilers - notably borland BCC,
17 C++Builder, and gcc. MSVC++ is the defacto standard compiler used by
18 Steinberg. As a result of this situation, the ASIO sdk will compile with
19 any compiler, however attempting to execute the compiled code will cause a
20 crash due to different default calling conventions on non-Microsoft
21 compilers.
22
23 IASIOThiscallResolver solves the problem by providing an adapter class that
24 delegates to the IASIO interface using the correct calling convention
25 (thiscall). Due to the lack of support for thiscall in the Borland and GCC
26 compilers, the calls have been implemented in assembly language.
27
28 A number of macros are defined for thiscall function calls with different
29 numbers of parameters, with and without return values - it may be possible
30 to modify the format of these macros to make them work with other inline
31 assemblers.
32
33
34 THISCALL DEFINITION
35
36 A number of definitions of the thiscall calling convention are floating
37 around the internet. The following definition has been validated against
38 output from the MSVC++ compiler:
39
40 For non-vararg functions, thiscall works as follows: the object (this)
41 pointer is passed in ECX. All arguments are passed on the stack in
42 right to left order. The return value is placed in EAX. The callee
43 clears the passed arguments from the stack.
44
45
46 FINDING FUNCTION POINTERS FROM AN IASIO POINTER
47
48 The first field of a COM object is a pointer to its vtble. Thus a pointer
49 to an object implementing the IASIO interface also points to a pointer to
50 that object's vtbl. The vtble is a table of function pointers for all of
51 the virtual functions exposed by the implemented interfaces.
52
53 If we consider a variable declared as a pointer to IASO:
54
55 IASIO *theAsioDriver
56
57 theAsioDriver points to:
58
59 object implementing IASIO
60 {
61 IASIOvtbl *vtbl
62 other data
63 }
64
65 in other words, theAsioDriver points to a pointer to an IASIOvtbl
66
67 vtbl points to a table of function pointers:
68
69 IASIOvtbl ( interface IASIO : public IUnknown )
70 {
71 (IUnknown functions)
72 0 virtual HRESULT STDMETHODCALLTYPE (*QueryInterface)(REFIID riid, void **ppv) = 0;
73 4 virtual ULONG STDMETHODCALLTYPE (*AddRef)() = 0;
74 8 virtual ULONG STDMETHODCALLTYPE (*Release)() = 0;
75
76 (IASIO functions)
77 12 virtual ASIOBool (*init)(void *sysHandle) = 0;
78 16 virtual void (*getDriverName)(char *name) = 0;
79 20 virtual long (*getDriverVersion)() = 0;
80 24 virtual void (*getErrorMessage)(char *string) = 0;
81 28 virtual ASIOError (*start)() = 0;
82 32 virtual ASIOError (*stop)() = 0;
83 36 virtual ASIOError (*getChannels)(long *numInputChannels, long *numOutputChannels) = 0;
84 40 virtual ASIOError (*getLatencies)(long *inputLatency, long *outputLatency) = 0;
85 44 virtual ASIOError (*getBufferSize)(long *minSize, long *maxSize,
86 long *preferredSize, long *granularity) = 0;
87 48 virtual ASIOError (*canSampleRate)(ASIOSampleRate sampleRate) = 0;
88 52 virtual ASIOError (*getSampleRate)(ASIOSampleRate *sampleRate) = 0;
89 56 virtual ASIOError (*setSampleRate)(ASIOSampleRate sampleRate) = 0;
90 60 virtual ASIOError (*getClockSources)(ASIOClockSource *clocks, long *numSources) = 0;
91 64 virtual ASIOError (*setClockSource)(long reference) = 0;
92 68 virtual ASIOError (*getSamplePosition)(ASIOSamples *sPos, ASIOTimeStamp *tStamp) = 0;
93 72 virtual ASIOError (*getChannelInfo)(ASIOChannelInfo *info) = 0;
94 76 virtual ASIOError (*createBuffers)(ASIOBufferInfo *bufferInfos, long numChannels,
95 long bufferSize, ASIOCallbacks *callbacks) = 0;
96 80 virtual ASIOError (*disposeBuffers)() = 0;
97 84 virtual ASIOError (*controlPanel)() = 0;
98 88 virtual ASIOError (*future)(long selector,void *opt) = 0;
99 92 virtual ASIOError (*outputReady)() = 0;
100 };
101
102 The numbers in the left column show the byte offset of each function ptr
103 from the beginning of the vtbl. These numbers are used in the code below
104 to select different functions.
105
106 In order to find the address of a particular function, theAsioDriver
107 must first be dereferenced to find the value of the vtbl pointer:
108
109 mov eax, theAsioDriver
110 mov edx, [theAsioDriver] // edx now points to vtbl[0]
111
112 Then an offset must be added to the vtbl pointer to select a
113 particular function, for example vtbl+44 points to the slot containing
114 a pointer to the getBufferSize function.
115
116 Finally vtbl+x must be dereferenced to obtain the value of the function
117 pointer stored in that address:
118
119 call [edx+44] // call the function pointed to by
120 // the value in the getBufferSize field of the vtbl
121
122
123 SEE ALSO
124
125 Martin Fay's OpenASIO DLL at http://www.martinfay.com solves the same
126 problem by providing a new COM interface which wraps IASIO with an
127 interface that uses portable calling conventions. OpenASIO must be compiled
128 with MSVC, and requires that you ship the OpenASIO DLL with your
129 application.
130
131
132 ACKNOWLEDGEMENTS
133
134 Ross Bencina: worked out the thiscall details above, wrote the original
135 Borland asm macros, and a patch for asio.cpp (which is no longer needed).
136 Thanks to Martin Fay for introducing me to the issues discussed here,
137 and to Rene G. Ceballos for assisting with asm dumps from MSVC++.
138
139 Antti Silvast: converted the original calliasio to work with gcc and NASM
140 by implementing the asm code in a separate file.
141
142 Fraser Adams: modified the original calliasio containing the Borland inline
143 asm to add inline asm for gcc i.e. Intel syntax for Borland and AT&T syntax
144 for gcc. This seems a neater approach for gcc than to have a separate .asm
145 file and it means that we only need one version of the thiscall patch.
146
147 Fraser Adams: rewrote the original calliasio patch in the form of the
148 IASIOThiscallResolver class in order to avoid modifications to files from
149 the Steinberg SDK, which may have had potential licence issues.
150
151 Andrew Baldwin: contributed fixes for compatibility problems with more
152 recent versions of the gcc assembler.
153 */
154
155
156 // We only need IASIOThiscallResolver at all if we are on Win32. For other
157 // platforms we simply bypass the IASIOThiscallResolver definition to allow us
158 // to be safely #include'd whatever the platform to keep client code portable
159 #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__)
160
161 // gcc 4.4 is using the same calling conventions as Microsoft on Win64
162 #if !defined(__WIN64__)
163
164 // If microsoft compiler we can call IASIO directly so IASIOThiscallResolver
165 // is not used.
166 #if !defined(_MSC_VER)
167
168 #include <new>
169 #include <assert.h>
170
171 // We have a mechanism in iasiothiscallresolver.h to ensure that asio.h is
172 // #include'd before it in client code, we do NOT want to do this test here.
173 #define iasiothiscallresolver_sourcefile 1
174 #include "iasiothiscallresolver.h"
175 #undef iasiothiscallresolver_sourcefile
176
177 // iasiothiscallresolver.h redefines ASIOInit for clients, but we don't want
178 // this macro defined in this translation unit.
179 #undef ASIOInit
180
181 // theAsioDriver is a global pointer to the current IASIO instance which the
182 // ASIO SDK uses to perform all actions on the IASIO interface. We substitute
183 // our own forwarding interface into this pointer.
184 extern IASIO* theAsioDriver;
185
186 // The following macros define the inline assembler for BORLAND first then gcc
187
188 #if defined(__BCPLUSPLUS__) || defined(__BORLANDC__)
189
190 #define CALL_THISCALL_0( resultName, thisPtr, funcOffset )\
191 void *this_ = (thisPtr); \
192 __asm { \
193 mov ecx, this_ ; \
194 mov eax, [ecx] ; \
195 call [eax+funcOffset] ; \
196 mov resultName, eax ; \
197 }
198
199 #define CALL_VOID_THISCALL_1( thisPtr, funcOffset, param1 )\
200 void *this_ = (thisPtr); \
201 __asm { \
202 mov eax, param1 ; \
203 push eax ; \
204 mov ecx, this_ ; \
205 mov eax, [ecx] ; \
206 call [eax+funcOffset] ; \
207 }
208
209 #define CALL_THISCALL_1( resultName, thisPtr, funcOffset, param1 )\
210 void *this_ = (thisPtr); \
211 __asm { \
212 mov eax, param1 ; \
213 push eax ; \
214 mov ecx, this_ ; \
215 mov eax, [ecx] ; \
216 call [eax+funcOffset] ; \
217 mov resultName, eax ; \
218 }
219
220 #define CALL_THISCALL_1_DOUBLE( resultName, thisPtr, funcOffset, param1 )\
221 void *this_ = (thisPtr); \
222 void *doubleParamPtr_ (&param1); \
223 __asm { \
224 mov eax, doubleParamPtr_ ; \
225 push [eax+4] ; \
226 push [eax] ; \
227 mov ecx, this_ ; \
228 mov eax, [ecx] ; \
229 call [eax+funcOffset] ; \
230 mov resultName, eax ; \
231 }
232
233 #define CALL_THISCALL_2( resultName, thisPtr, funcOffset, param1, param2 )\
234 void *this_ = (thisPtr); \
235 __asm { \
236 mov eax, param2 ; \
237 push eax ; \
238 mov eax, param1 ; \
239 push eax ; \
240 mov ecx, this_ ; \
241 mov eax, [ecx] ; \
242 call [eax+funcOffset] ; \
243 mov resultName, eax ; \
244 }
245
246 #define CALL_THISCALL_4( resultName, thisPtr, funcOffset, param1, param2, param3, param4 )\
247 void *this_ = (thisPtr); \
248 __asm { \
249 mov eax, param4 ; \
250 push eax ; \
251 mov eax, param3 ; \
252 push eax ; \
253 mov eax, param2 ; \
254 push eax ; \
255 mov eax, param1 ; \
256 push eax ; \
257 mov ecx, this_ ; \
258 mov eax, [ecx] ; \
259 call [eax+funcOffset] ; \
260 mov resultName, eax ; \
261 }
262
263 #elif defined(__GNUC__)
264
265 #define CALL_THISCALL_0( resultName, thisPtr, funcOffset ) \
266 __asm__ __volatile__ ("movl (%1), %%edx\n\t" \
267 "call *"#funcOffset"(%%edx)\n\t" \
268 :"=a"(resultName) /* Output Operands */ \
269 :"c"(thisPtr) /* Input Operands */ \
270 : "%edx" /* Clobbered Registers */ \
271 ); \
272
273 #define CALL_VOID_THISCALL_1( thisPtr, funcOffset, param1 ) \
274 __asm__ __volatile__ ("pushl %0\n\t" \
275 "movl (%1), %%edx\n\t" \
276 "call *"#funcOffset"(%%edx)\n\t" \
277 : /* Output Operands */ \
278 :"r"(param1), /* Input Operands */ \
279 "c"(thisPtr) \
280 : "%edx" /* Clobbered Registers */ \
281 ); \
282
283 #define CALL_THISCALL_1( resultName, thisPtr, funcOffset, param1 ) \
284 __asm__ __volatile__ ("pushl %1\n\t" \
285 "movl (%2), %%edx\n\t" \
286 "call *"#funcOffset"(%%edx)\n\t" \
287 :"=a"(resultName) /* Output Operands */ \
288 :"r"(param1), /* Input Operands */ \
289 "c"(thisPtr) \
290 : "%edx" /* Clobbered Registers */ \
291 ); \
292
293 #define CALL_THISCALL_1_DOUBLE( resultName, thisPtr, funcOffset, param1 ) \
294 do { \
295 double param1f64 = param1; /* Cast explicitly to double */ \
296 double *param1f64Ptr = &param1f64; /* Make pointer to address */ \
297 __asm__ __volatile__ ("pushl 4(%1)\n\t" \
298 "pushl (%1)\n\t" \
299 "movl (%2), %%edx\n\t" \
300 "call *"#funcOffset"(%%edx);\n\t" \
301 : "=a"(resultName) /* Output Operands */ \
302 : "r"(param1f64Ptr), /* Input Operands */ \
303 "c"(thisPtr), \
304 "m"(*param1f64Ptr) /* Using address */ \
305 : "%edx" /* Clobbered Registers */ \
306 ); \
307 } while (0); \
308
309
310 #define CALL_THISCALL_2( resultName, thisPtr, funcOffset, param1, param2 ) \
311 __asm__ __volatile__ ("pushl %1\n\t" \
312 "pushl %2\n\t" \
313 "movl (%3), %%edx\n\t" \
314 "call *"#funcOffset"(%%edx)\n\t" \
315 :"=a"(resultName) /* Output Operands */ \
316 :"r"(param2), /* Input Operands */ \
317 "r"(param1), \
318 "c"(thisPtr) \
319 : "%edx" /* Clobbered Registers */ \
320 ); \
321
322 #define CALL_THISCALL_4( resultName, thisPtr, funcOffset, param1, param2, param3, param4 )\
323 __asm__ __volatile__ ("pushl %1\n\t" \
324 "pushl %2\n\t" \
325 "pushl %3\n\t" \
326 "pushl %4\n\t" \
327 "movl (%5), %%edx\n\t" \
328 "call *"#funcOffset"(%%edx)\n\t" \
329 :"=a"(resultName) /* Output Operands */ \
330 :"r"(param4), /* Input Operands */ \
331 "r"(param3), \
332 "r"(param2), \
333 "r"(param1), \
334 "c"(thisPtr) \
335 : "%edx" /* Clobbered Registers */ \
336 ); \
337
338 #endif
339
340 // Our static singleton instance.
341 IASIOThiscallResolver IASIOThiscallResolver::instance;
342
343 // Constructor called to initialize static Singleton instance above. Note that
344 // it is important not to clear that_ incase it has already been set by the call
345 // to placement new in ASIOInit().
346 IASIOThiscallResolver::IASIOThiscallResolver()
347 {
348 }
349
350 // Constructor called from ASIOInit() below
351 IASIOThiscallResolver::IASIOThiscallResolver(IASIO* that)
352 : that_( that )
353 {
354 }
355
356 // Implement IUnknown methods as assert(false). IASIOThiscallResolver is not
357 // really a COM object, just a wrapper which will work with the ASIO SDK.
358 // If you wanted to use ASIO without the SDK you might want to implement COM
359 // aggregation in these methods.
360 HRESULT STDMETHODCALLTYPE IASIOThiscallResolver::QueryInterface(REFIID riid, void **ppv)
361 {
362 (void)riid; // suppress unused variable warning
363
364 assert( false ); // this function should never be called by the ASIO SDK.
365
366 *ppv = NULL;
367 return E_NOINTERFACE;
368 }
369
370 ULONG STDMETHODCALLTYPE IASIOThiscallResolver::AddRef()
371 {
372 assert( false ); // this function should never be called by the ASIO SDK.
373
374 return 1;
375 }
376
377 ULONG STDMETHODCALLTYPE IASIOThiscallResolver::Release()
378 {
379 assert( false ); // this function should never be called by the ASIO SDK.
380
381 return 1;
382 }
383
384 // Implement the IASIO interface methods by performing the vptr manipulation
385 // described above then delegating to the real implementation.
386 ASIOBool IASIOThiscallResolver::init(void *sysHandle)
387 {
388 ASIOBool result;
389 CALL_THISCALL_1( result, that_, 12, sysHandle );
390 return result;
391 }
392
393 void IASIOThiscallResolver::getDriverName(char *name)
394 {
395 CALL_VOID_THISCALL_1( that_, 16, name );
396 }
397
398 long IASIOThiscallResolver::getDriverVersion()
399 {
400 ASIOBool result;
401 CALL_THISCALL_0( result, that_, 20 );
402 return result;
403 }
404
405 void IASIOThiscallResolver::getErrorMessage(char *string)
406 {
407 CALL_VOID_THISCALL_1( that_, 24, string );
408 }
409
410 ASIOError IASIOThiscallResolver::start()
411 {
412 ASIOBool result;
413 CALL_THISCALL_0( result, that_, 28 );
414 return result;
415 }
416
417 ASIOError IASIOThiscallResolver::stop()
418 {
419 ASIOBool result;
420 CALL_THISCALL_0( result, that_, 32 );
421 return result;
422 }
423
424 ASIOError IASIOThiscallResolver::getChannels(long *numInputChannels, long *numOutputChannels)
425 {
426 ASIOBool result;
427 CALL_THISCALL_2( result, that_, 36, numInputChannels, numOutputChannels );
428 return result;
429 }
430
431 ASIOError IASIOThiscallResolver::getLatencies(long *inputLatency, long *outputLatency)
432 {
433 ASIOBool result;
434 CALL_THISCALL_2( result, that_, 40, inputLatency, outputLatency );
435 return result;
436 }
437
438 ASIOError IASIOThiscallResolver::getBufferSize(long *minSize, long *maxSize,
439 long *preferredSize, long *granularity)
440 {
441 ASIOBool result;
442 CALL_THISCALL_4( result, that_, 44, minSize, maxSize, preferredSize, granularity );
443 return result;
444 }
445
446 ASIOError IASIOThiscallResolver::canSampleRate(ASIOSampleRate sampleRate)
447 {
448 ASIOBool result;
449 CALL_THISCALL_1_DOUBLE( result, that_, 48, sampleRate );
450 return result;
451 }
452
453 ASIOError IASIOThiscallResolver::getSampleRate(ASIOSampleRate *sampleRate)
454 {
455 ASIOBool result;
456 CALL_THISCALL_1( result, that_, 52, sampleRate );
457 return result;
458 }
459
460 ASIOError IASIOThiscallResolver::setSampleRate(ASIOSampleRate sampleRate)
461 {
462 ASIOBool result;
463 CALL_THISCALL_1_DOUBLE( result, that_, 56, sampleRate );
464 return result;
465 }
466
467 ASIOError IASIOThiscallResolver::getClockSources(ASIOClockSource *clocks, long *numSources)
468 {
469 ASIOBool result;
470 CALL_THISCALL_2( result, that_, 60, clocks, numSources );
471 return result;
472 }
473
474 ASIOError IASIOThiscallResolver::setClockSource(long reference)
475 {
476 ASIOBool result;
477 CALL_THISCALL_1( result, that_, 64, reference );
478 return result;
479 }
480
481 ASIOError IASIOThiscallResolver::getSamplePosition(ASIOSamples *sPos, ASIOTimeStamp *tStamp)
482 {
483 ASIOBool result;
484 CALL_THISCALL_2( result, that_, 68, sPos, tStamp );
485 return result;
486 }
487
488 ASIOError IASIOThiscallResolver::getChannelInfo(ASIOChannelInfo *info)
489 {
490 ASIOBool result;
491 CALL_THISCALL_1( result, that_, 72, info );
492 return result;
493 }
494
495 ASIOError IASIOThiscallResolver::createBuffers(ASIOBufferInfo *bufferInfos,
496 long numChannels, long bufferSize, ASIOCallbacks *callbacks)
497 {
498 ASIOBool result;
499 CALL_THISCALL_4( result, that_, 76, bufferInfos, numChannels, bufferSize, callbacks );
500 return result;
501 }
502
503 ASIOError IASIOThiscallResolver::disposeBuffers()
504 {
505 ASIOBool result;
506 CALL_THISCALL_0( result, that_, 80 );
507 return result;
508 }
509
510 ASIOError IASIOThiscallResolver::controlPanel()
511 {
512 ASIOBool result;
513 CALL_THISCALL_0( result, that_, 84 );
514 return result;
515 }
516
517 ASIOError IASIOThiscallResolver::future(long selector,void *opt)
518 {
519 ASIOBool result;
520 CALL_THISCALL_2( result, that_, 88, selector, opt );
521 return result;
522 }
523
524 ASIOError IASIOThiscallResolver::outputReady()
525 {
526 ASIOBool result;
527 CALL_THISCALL_0( result, that_, 92 );
528 return result;
529 }
530
531 // Implement our substitute ASIOInit() method
532 ASIOError IASIOThiscallResolver::ASIOInit(ASIODriverInfo *info)
533 {
534 // To ensure that our instance's vptr is correctly constructed, even if
535 // ASIOInit is called prior to main(), we explicitly call its constructor
536 // (potentially over the top of an existing instance). Note that this is
537 // pretty ugly, and is only safe because IASIOThiscallResolver has no
538 // destructor and contains no objects with destructors.
539 new((void*)&instance) IASIOThiscallResolver( theAsioDriver );
540
541 // Interpose between ASIO client code and the real driver.
542 theAsioDriver = &instance;
543
544 // Note that we never need to switch theAsioDriver back to point to the
545 // real driver because theAsioDriver is reset to zero in ASIOExit().
546
547 // Delegate to the real ASIOInit
548 return ::ASIOInit(info);
549 }
550
551 #endif /* !defined(_MSC_VER) */
552 #endif /* !defined(__WIN64__) */
553 #endif /* Win32 */

  ViewVC Help
Powered by ViewVC