/[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 1889 - (hide annotations) (download)
Sun Apr 26 12:19:00 2009 UTC (15 years ago) by persson
File size: 23342 byte(s)
* fixed a memory management error which could cause a crash when a
  plugin was unloaded
* minor fixes in ASIO and MME drivers for win64

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 persson 1889 // gcc 4.4 is using the same calling conventions as Microsoft on Win64
162     #if !defined(__WIN64__)
163    
164 senoner 1500 // 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     ); \
271    
272     #define CALL_VOID_THISCALL_1( thisPtr, funcOffset, param1 ) \
273     __asm__ __volatile__ ("pushl %0\n\t" \
274     "movl (%1), %%edx\n\t" \
275     "call *"#funcOffset"(%%edx)\n\t" \
276     : /* Output Operands */ \
277     :"r"(param1), /* Input Operands */ \
278     "c"(thisPtr) \
279     ); \
280    
281     #define CALL_THISCALL_1( resultName, thisPtr, funcOffset, param1 ) \
282     __asm__ __volatile__ ("pushl %1\n\t" \
283     "movl (%2), %%edx\n\t" \
284     "call *"#funcOffset"(%%edx)\n\t" \
285     :"=a"(resultName) /* Output Operands */ \
286     :"r"(param1), /* Input Operands */ \
287     "c"(thisPtr) \
288     ); \
289    
290     #define CALL_THISCALL_1_DOUBLE( resultName, thisPtr, funcOffset, param1 ) \
291     __asm__ __volatile__ ("pushl 4(%1)\n\t" \
292     "pushl (%1)\n\t" \
293     "movl (%2), %%edx\n\t" \
294     "call *"#funcOffset"(%%edx);\n\t" \
295     :"=a"(resultName) /* Output Operands */ \
296     :"a"(&param1), /* Input Operands */ \
297     /* Note: Using "r" above instead of "a" fails */ \
298     /* when using GCC 3.3.3, and maybe later versions*/\
299     "c"(thisPtr) \
300     ); \
301    
302     #define CALL_THISCALL_2( resultName, thisPtr, funcOffset, param1, param2 ) \
303     __asm__ __volatile__ ("pushl %1\n\t" \
304     "pushl %2\n\t" \
305     "movl (%3), %%edx\n\t" \
306     "call *"#funcOffset"(%%edx)\n\t" \
307     :"=a"(resultName) /* Output Operands */ \
308     :"r"(param2), /* Input Operands */ \
309     "r"(param1), \
310     "c"(thisPtr) \
311     ); \
312    
313     #define CALL_THISCALL_4( resultName, thisPtr, funcOffset, param1, param2, param3, param4 )\
314     __asm__ __volatile__ ("pushl %1\n\t" \
315     "pushl %2\n\t" \
316     "pushl %3\n\t" \
317     "pushl %4\n\t" \
318     "movl (%5), %%edx\n\t" \
319     "call *"#funcOffset"(%%edx)\n\t" \
320     :"=a"(resultName) /* Output Operands */ \
321     :"r"(param4), /* Input Operands */ \
322     "r"(param3), \
323     "r"(param2), \
324     "r"(param1), \
325     "c"(thisPtr) \
326     ); \
327    
328     #endif
329    
330     // Our static singleton instance.
331     IASIOThiscallResolver IASIOThiscallResolver::instance;
332    
333     // Constructor called to initialize static Singleton instance above. Note that
334     // it is important not to clear that_ incase it has already been set by the call
335     // to placement new in ASIOInit().
336     IASIOThiscallResolver::IASIOThiscallResolver()
337     {
338     }
339    
340     // Constructor called from ASIOInit() below
341     IASIOThiscallResolver::IASIOThiscallResolver(IASIO* that)
342     : that_( that )
343     {
344     }
345    
346     // Implement IUnknown methods as assert(false). IASIOThiscallResolver is not
347     // really a COM object, just a wrapper which will work with the ASIO SDK.
348     // If you wanted to use ASIO without the SDK you might want to implement COM
349     // aggregation in these methods.
350     HRESULT STDMETHODCALLTYPE IASIOThiscallResolver::QueryInterface(REFIID riid, void **ppv)
351     {
352     (void)riid; // suppress unused variable warning
353    
354     assert( false ); // this function should never be called by the ASIO SDK.
355    
356     *ppv = NULL;
357     return E_NOINTERFACE;
358     }
359    
360     ULONG STDMETHODCALLTYPE IASIOThiscallResolver::AddRef()
361     {
362     assert( false ); // this function should never be called by the ASIO SDK.
363    
364     return 1;
365     }
366    
367     ULONG STDMETHODCALLTYPE IASIOThiscallResolver::Release()
368     {
369     assert( false ); // this function should never be called by the ASIO SDK.
370    
371     return 1;
372     }
373    
374     // Implement the IASIO interface methods by performing the vptr manipulation
375     // described above then delegating to the real implementation.
376     ASIOBool IASIOThiscallResolver::init(void *sysHandle)
377     {
378     ASIOBool result;
379     CALL_THISCALL_1( result, that_, 12, sysHandle );
380     return result;
381     }
382    
383     void IASIOThiscallResolver::getDriverName(char *name)
384     {
385     CALL_VOID_THISCALL_1( that_, 16, name );
386     }
387    
388     long IASIOThiscallResolver::getDriverVersion()
389     {
390     ASIOBool result;
391     CALL_THISCALL_0( result, that_, 20 );
392     return result;
393     }
394    
395     void IASIOThiscallResolver::getErrorMessage(char *string)
396     {
397     CALL_VOID_THISCALL_1( that_, 24, string );
398     }
399    
400     ASIOError IASIOThiscallResolver::start()
401     {
402     ASIOBool result;
403     CALL_THISCALL_0( result, that_, 28 );
404     return result;
405     }
406    
407     ASIOError IASIOThiscallResolver::stop()
408     {
409     ASIOBool result;
410     CALL_THISCALL_0( result, that_, 32 );
411     return result;
412     }
413    
414     ASIOError IASIOThiscallResolver::getChannels(long *numInputChannels, long *numOutputChannels)
415     {
416     ASIOBool result;
417     CALL_THISCALL_2( result, that_, 36, numInputChannels, numOutputChannels );
418     return result;
419     }
420    
421     ASIOError IASIOThiscallResolver::getLatencies(long *inputLatency, long *outputLatency)
422     {
423     ASIOBool result;
424     CALL_THISCALL_2( result, that_, 40, inputLatency, outputLatency );
425     return result;
426     }
427    
428     ASIOError IASIOThiscallResolver::getBufferSize(long *minSize, long *maxSize,
429     long *preferredSize, long *granularity)
430     {
431     ASIOBool result;
432     CALL_THISCALL_4( result, that_, 44, minSize, maxSize, preferredSize, granularity );
433     return result;
434     }
435    
436     ASIOError IASIOThiscallResolver::canSampleRate(ASIOSampleRate sampleRate)
437     {
438     ASIOBool result;
439     CALL_THISCALL_1_DOUBLE( result, that_, 48, sampleRate );
440     return result;
441     }
442    
443     ASIOError IASIOThiscallResolver::getSampleRate(ASIOSampleRate *sampleRate)
444     {
445     ASIOBool result;
446     CALL_THISCALL_1( result, that_, 52, sampleRate );
447     return result;
448     }
449    
450     ASIOError IASIOThiscallResolver::setSampleRate(ASIOSampleRate sampleRate)
451     {
452     ASIOBool result;
453     CALL_THISCALL_1_DOUBLE( result, that_, 56, sampleRate );
454     return result;
455     }
456    
457     ASIOError IASIOThiscallResolver::getClockSources(ASIOClockSource *clocks, long *numSources)
458     {
459     ASIOBool result;
460     CALL_THISCALL_2( result, that_, 60, clocks, numSources );
461     return result;
462     }
463    
464     ASIOError IASIOThiscallResolver::setClockSource(long reference)
465     {
466     ASIOBool result;
467     CALL_THISCALL_1( result, that_, 64, reference );
468     return result;
469     }
470    
471     ASIOError IASIOThiscallResolver::getSamplePosition(ASIOSamples *sPos, ASIOTimeStamp *tStamp)
472     {
473     ASIOBool result;
474     CALL_THISCALL_2( result, that_, 68, sPos, tStamp );
475     return result;
476     }
477    
478     ASIOError IASIOThiscallResolver::getChannelInfo(ASIOChannelInfo *info)
479     {
480     ASIOBool result;
481     CALL_THISCALL_1( result, that_, 72, info );
482     return result;
483     }
484    
485     ASIOError IASIOThiscallResolver::createBuffers(ASIOBufferInfo *bufferInfos,
486     long numChannels, long bufferSize, ASIOCallbacks *callbacks)
487     {
488     ASIOBool result;
489     CALL_THISCALL_4( result, that_, 76, bufferInfos, numChannels, bufferSize, callbacks );
490     return result;
491     }
492    
493     ASIOError IASIOThiscallResolver::disposeBuffers()
494     {
495     ASIOBool result;
496     CALL_THISCALL_0( result, that_, 80 );
497     return result;
498     }
499    
500     ASIOError IASIOThiscallResolver::controlPanel()
501     {
502     ASIOBool result;
503     CALL_THISCALL_0( result, that_, 84 );
504     return result;
505     }
506    
507     ASIOError IASIOThiscallResolver::future(long selector,void *opt)
508     {
509     ASIOBool result;
510     CALL_THISCALL_2( result, that_, 88, selector, opt );
511     return result;
512     }
513    
514     ASIOError IASIOThiscallResolver::outputReady()
515     {
516     ASIOBool result;
517     CALL_THISCALL_0( result, that_, 92 );
518     return result;
519     }
520    
521     // Implement our substitute ASIOInit() method
522     ASIOError IASIOThiscallResolver::ASIOInit(ASIODriverInfo *info)
523     {
524     // To ensure that our instance's vptr is correctly constructed, even if
525     // ASIOInit is called prior to main(), we explicitly call its constructor
526     // (potentially over the top of an existing instance). Note that this is
527     // pretty ugly, and is only safe because IASIOThiscallResolver has no
528     // destructor and contains no objects with destructors.
529     new((void*)&instance) IASIOThiscallResolver( theAsioDriver );
530    
531     // Interpose between ASIO client code and the real driver.
532     theAsioDriver = &instance;
533    
534     // Note that we never need to switch theAsioDriver back to point to the
535     // real driver because theAsioDriver is reset to zero in ASIOExit().
536    
537     // Delegate to the real ASIOInit
538     return ::ASIOInit(info);
539     }
540    
541     #endif /* !defined(_MSC_VER) */
542 persson 1889 #endif /* !defined(__WIN64__) */
543 senoner 1500 #endif /* Win32 */

  ViewVC Help
Powered by ViewVC