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