/[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 1500 - (show 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 /*
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