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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1500 - (hide annotations) (download)
Wed Nov 21 00:52:09 2007 UTC (16 years, 5 months ago) by senoner
File size: 23214 byte(s)
* win32 port, work in progress:
 - added ASIO low latency audio output driver
* MME MIDI input driver:
 - fixed number of PORTS to 1 as the win32 MME MIDI API
 allows to connect to only one MIDI port at time,

1 senoner 1500 /*
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     // If microsoft compiler we can call IASIO directly so IASIOThiscallResolver
162     // is not used.
163     #if !defined(_MSC_VER)
164    
165     #include <new>
166     #include <assert.h>
167    
168     // We have a mechanism in iasiothiscallresolver.h to ensure that asio.h is
169     // #include'd before it in client code, we do NOT want to do this test here.
170     #define iasiothiscallresolver_sourcefile 1
171     #include "iasiothiscallresolver.h"
172     #undef iasiothiscallresolver_sourcefile
173    
174     // iasiothiscallresolver.h redefines ASIOInit for clients, but we don't want
175     // this macro defined in this translation unit.
176     #undef ASIOInit
177    
178     // theAsioDriver is a global pointer to the current IASIO instance which the
179     // ASIO SDK uses to perform all actions on the IASIO interface. We substitute
180     // our own forwarding interface into this pointer.
181     extern IASIO* theAsioDriver;
182    
183     // The following macros define the inline assembler for BORLAND first then gcc
184    
185     #if defined(__BCPLUSPLUS__) || defined(__BORLANDC__)
186    
187     #define CALL_THISCALL_0( resultName, thisPtr, funcOffset )\
188     void *this_ = (thisPtr); \
189     __asm { \
190     mov ecx, this_ ; \
191     mov eax, [ecx] ; \
192     call [eax+funcOffset] ; \
193     mov resultName, eax ; \
194     }
195    
196     #define CALL_VOID_THISCALL_1( thisPtr, funcOffset, param1 )\
197     void *this_ = (thisPtr); \
198     __asm { \
199     mov eax, param1 ; \
200     push eax ; \
201     mov ecx, this_ ; \
202     mov eax, [ecx] ; \
203     call [eax+funcOffset] ; \
204     }
205    
206     #define CALL_THISCALL_1( resultName, thisPtr, funcOffset, param1 )\
207     void *this_ = (thisPtr); \
208     __asm { \
209     mov eax, param1 ; \
210     push eax ; \
211     mov ecx, this_ ; \
212     mov eax, [ecx] ; \
213     call [eax+funcOffset] ; \
214     mov resultName, eax ; \
215     }
216    
217     #define CALL_THISCALL_1_DOUBLE( resultName, thisPtr, funcOffset, param1 )\
218     void *this_ = (thisPtr); \
219     void *doubleParamPtr_ (&param1); \
220     __asm { \
221     mov eax, doubleParamPtr_ ; \
222     push [eax+4] ; \
223     push [eax] ; \
224     mov ecx, this_ ; \
225     mov eax, [ecx] ; \
226     call [eax+funcOffset] ; \
227     mov resultName, eax ; \
228     }
229    
230     #define CALL_THISCALL_2( resultName, thisPtr, funcOffset, param1, param2 )\
231     void *this_ = (thisPtr); \
232     __asm { \
233     mov eax, param2 ; \
234     push eax ; \
235     mov eax, param1 ; \
236     push eax ; \
237     mov ecx, this_ ; \
238     mov eax, [ecx] ; \
239     call [eax+funcOffset] ; \
240     mov resultName, eax ; \
241     }
242    
243     #define CALL_THISCALL_4( resultName, thisPtr, funcOffset, param1, param2, param3, param4 )\
244     void *this_ = (thisPtr); \
245     __asm { \
246     mov eax, param4 ; \
247     push eax ; \
248     mov eax, param3 ; \
249     push eax ; \
250     mov eax, param2 ; \
251     push eax ; \
252     mov eax, param1 ; \
253     push eax ; \
254     mov ecx, this_ ; \
255     mov eax, [ecx] ; \
256     call [eax+funcOffset] ; \
257     mov resultName, eax ; \
258     }
259    
260     #elif defined(__GNUC__)
261    
262     #define CALL_THISCALL_0( resultName, thisPtr, funcOffset ) \
263     __asm__ __volatile__ ("movl (%1), %%edx\n\t" \
264     "call *"#funcOffset"(%%edx)\n\t" \
265     :"=a"(resultName) /* Output Operands */ \
266     :"c"(thisPtr) /* Input Operands */ \
267     ); \
268    
269     #define CALL_VOID_THISCALL_1( thisPtr, funcOffset, param1 ) \
270     __asm__ __volatile__ ("pushl %0\n\t" \
271     "movl (%1), %%edx\n\t" \
272     "call *"#funcOffset"(%%edx)\n\t" \
273     : /* Output Operands */ \
274     :"r"(param1), /* Input Operands */ \
275     "c"(thisPtr) \
276     ); \
277    
278     #define CALL_THISCALL_1( resultName, thisPtr, funcOffset, param1 ) \
279     __asm__ __volatile__ ("pushl %1\n\t" \
280     "movl (%2), %%edx\n\t" \
281     "call *"#funcOffset"(%%edx)\n\t" \
282     :"=a"(resultName) /* Output Operands */ \
283     :"r"(param1), /* Input Operands */ \
284     "c"(thisPtr) \
285     ); \
286    
287     #define CALL_THISCALL_1_DOUBLE( resultName, thisPtr, funcOffset, param1 ) \
288     __asm__ __volatile__ ("pushl 4(%1)\n\t" \
289     "pushl (%1)\n\t" \
290     "movl (%2), %%edx\n\t" \
291     "call *"#funcOffset"(%%edx);\n\t" \
292     :"=a"(resultName) /* Output Operands */ \
293     :"a"(&param1), /* Input Operands */ \
294     /* Note: Using "r" above instead of "a" fails */ \
295     /* when using GCC 3.3.3, and maybe later versions*/\
296     "c"(thisPtr) \
297     ); \
298    
299     #define CALL_THISCALL_2( resultName, thisPtr, funcOffset, param1, param2 ) \
300     __asm__ __volatile__ ("pushl %1\n\t" \
301     "pushl %2\n\t" \
302     "movl (%3), %%edx\n\t" \
303     "call *"#funcOffset"(%%edx)\n\t" \
304     :"=a"(resultName) /* Output Operands */ \
305     :"r"(param2), /* Input Operands */ \
306     "r"(param1), \
307     "c"(thisPtr) \
308     ); \
309    
310     #define CALL_THISCALL_4( resultName, thisPtr, funcOffset, param1, param2, param3, param4 )\
311     __asm__ __volatile__ ("pushl %1\n\t" \
312     "pushl %2\n\t" \
313     "pushl %3\n\t" \
314     "pushl %4\n\t" \
315     "movl (%5), %%edx\n\t" \
316     "call *"#funcOffset"(%%edx)\n\t" \
317     :"=a"(resultName) /* Output Operands */ \
318     :"r"(param4), /* Input Operands */ \
319     "r"(param3), \
320     "r"(param2), \
321     "r"(param1), \
322     "c"(thisPtr) \
323     ); \
324    
325     #endif
326    
327     // Our static singleton instance.
328     IASIOThiscallResolver IASIOThiscallResolver::instance;
329    
330     // Constructor called to initialize static Singleton instance above. Note that
331     // it is important not to clear that_ incase it has already been set by the call
332     // to placement new in ASIOInit().
333     IASIOThiscallResolver::IASIOThiscallResolver()
334     {
335     }
336    
337     // Constructor called from ASIOInit() below
338     IASIOThiscallResolver::IASIOThiscallResolver(IASIO* that)
339     : that_( that )
340     {
341     }
342    
343     // Implement IUnknown methods as assert(false). IASIOThiscallResolver is not
344     // really a COM object, just a wrapper which will work with the ASIO SDK.
345     // If you wanted to use ASIO without the SDK you might want to implement COM
346     // aggregation in these methods.
347     HRESULT STDMETHODCALLTYPE IASIOThiscallResolver::QueryInterface(REFIID riid, void **ppv)
348     {
349     (void)riid; // suppress unused variable warning
350    
351     assert( false ); // this function should never be called by the ASIO SDK.
352    
353     *ppv = NULL;
354     return E_NOINTERFACE;
355     }
356    
357     ULONG STDMETHODCALLTYPE IASIOThiscallResolver::AddRef()
358     {
359     assert( false ); // this function should never be called by the ASIO SDK.
360    
361     return 1;
362     }
363    
364     ULONG STDMETHODCALLTYPE IASIOThiscallResolver::Release()
365     {
366     assert( false ); // this function should never be called by the ASIO SDK.
367    
368     return 1;
369     }
370    
371     // Implement the IASIO interface methods by performing the vptr manipulation
372     // described above then delegating to the real implementation.
373     ASIOBool IASIOThiscallResolver::init(void *sysHandle)
374     {
375     ASIOBool result;
376     CALL_THISCALL_1( result, that_, 12, sysHandle );
377     return result;
378     }
379    
380     void IASIOThiscallResolver::getDriverName(char *name)
381     {
382     CALL_VOID_THISCALL_1( that_, 16, name );
383     }
384    
385     long IASIOThiscallResolver::getDriverVersion()
386     {
387     ASIOBool result;
388     CALL_THISCALL_0( result, that_, 20 );
389     return result;
390     }
391    
392     void IASIOThiscallResolver::getErrorMessage(char *string)
393     {
394     CALL_VOID_THISCALL_1( that_, 24, string );
395     }
396    
397     ASIOError IASIOThiscallResolver::start()
398     {
399     ASIOBool result;
400     CALL_THISCALL_0( result, that_, 28 );
401     return result;
402     }
403    
404     ASIOError IASIOThiscallResolver::stop()
405     {
406     ASIOBool result;
407     CALL_THISCALL_0( result, that_, 32 );
408     return result;
409     }
410    
411     ASIOError IASIOThiscallResolver::getChannels(long *numInputChannels, long *numOutputChannels)
412     {
413     ASIOBool result;
414     CALL_THISCALL_2( result, that_, 36, numInputChannels, numOutputChannels );
415     return result;
416     }
417    
418     ASIOError IASIOThiscallResolver::getLatencies(long *inputLatency, long *outputLatency)
419     {
420     ASIOBool result;
421     CALL_THISCALL_2( result, that_, 40, inputLatency, outputLatency );
422     return result;
423     }
424    
425     ASIOError IASIOThiscallResolver::getBufferSize(long *minSize, long *maxSize,
426     long *preferredSize, long *granularity)
427     {
428     ASIOBool result;
429     CALL_THISCALL_4( result, that_, 44, minSize, maxSize, preferredSize, granularity );
430     return result;
431     }
432    
433     ASIOError IASIOThiscallResolver::canSampleRate(ASIOSampleRate sampleRate)
434     {
435     ASIOBool result;
436     CALL_THISCALL_1_DOUBLE( result, that_, 48, sampleRate );
437     return result;
438     }
439    
440     ASIOError IASIOThiscallResolver::getSampleRate(ASIOSampleRate *sampleRate)
441     {
442     ASIOBool result;
443     CALL_THISCALL_1( result, that_, 52, sampleRate );
444     return result;
445     }
446    
447     ASIOError IASIOThiscallResolver::setSampleRate(ASIOSampleRate sampleRate)
448     {
449     ASIOBool result;
450     CALL_THISCALL_1_DOUBLE( result, that_, 56, sampleRate );
451     return result;
452     }
453    
454     ASIOError IASIOThiscallResolver::getClockSources(ASIOClockSource *clocks, long *numSources)
455     {
456     ASIOBool result;
457     CALL_THISCALL_2( result, that_, 60, clocks, numSources );
458     return result;
459     }
460    
461     ASIOError IASIOThiscallResolver::setClockSource(long reference)
462     {
463     ASIOBool result;
464     CALL_THISCALL_1( result, that_, 64, reference );
465     return result;
466     }
467    
468     ASIOError IASIOThiscallResolver::getSamplePosition(ASIOSamples *sPos, ASIOTimeStamp *tStamp)
469     {
470     ASIOBool result;
471     CALL_THISCALL_2( result, that_, 68, sPos, tStamp );
472     return result;
473     }
474    
475     ASIOError IASIOThiscallResolver::getChannelInfo(ASIOChannelInfo *info)
476     {
477     ASIOBool result;
478     CALL_THISCALL_1( result, that_, 72, info );
479     return result;
480     }
481    
482     ASIOError IASIOThiscallResolver::createBuffers(ASIOBufferInfo *bufferInfos,
483     long numChannels, long bufferSize, ASIOCallbacks *callbacks)
484     {
485     ASIOBool result;
486     CALL_THISCALL_4( result, that_, 76, bufferInfos, numChannels, bufferSize, callbacks );
487     return result;
488     }
489    
490     ASIOError IASIOThiscallResolver::disposeBuffers()
491     {
492     ASIOBool result;
493     CALL_THISCALL_0( result, that_, 80 );
494     return result;
495     }
496    
497     ASIOError IASIOThiscallResolver::controlPanel()
498     {
499     ASIOBool result;
500     CALL_THISCALL_0( result, that_, 84 );
501     return result;
502     }
503    
504     ASIOError IASIOThiscallResolver::future(long selector,void *opt)
505     {
506     ASIOBool result;
507     CALL_THISCALL_2( result, that_, 88, selector, opt );
508     return result;
509     }
510    
511     ASIOError IASIOThiscallResolver::outputReady()
512     {
513     ASIOBool result;
514     CALL_THISCALL_0( result, that_, 92 );
515     return result;
516     }
517    
518     // Implement our substitute ASIOInit() method
519     ASIOError IASIOThiscallResolver::ASIOInit(ASIODriverInfo *info)
520     {
521     // To ensure that our instance's vptr is correctly constructed, even if
522     // ASIOInit is called prior to main(), we explicitly call its constructor
523     // (potentially over the top of an existing instance). Note that this is
524     // pretty ugly, and is only safe because IASIOThiscallResolver has no
525     // destructor and contains no objects with destructors.
526     new((void*)&instance) IASIOThiscallResolver( theAsioDriver );
527    
528     // Interpose between ASIO client code and the real driver.
529     theAsioDriver = &instance;
530    
531     // Note that we never need to switch theAsioDriver back to point to the
532     // real driver because theAsioDriver is reset to zero in ASIOExit().
533    
534     // Delegate to the real ASIOInit
535     return ::ASIOInit(info);
536     }
537    
538     #endif /* !defined(_MSC_VER) */
539    
540     #endif /* Win32 */

  ViewVC Help
Powered by ViewVC