/[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 1889 - (show 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 /*
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 ); \
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 #endif /* !defined(__WIN64__) */
543 #endif /* Win32 */

  ViewVC Help
Powered by ViewVC