|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572 |
- /*
- IASIOThiscallResolver.cpp see the comments in iasiothiscallresolver.h for
- the top level description - this comment describes the technical details of
- the implementation.
-
- The latest version of this file is available from:
- http://www.audiomulch.com/~rossb/code/calliasio
-
- please email comments to Ross Bencina <rossb@audiomulch.com>
-
- BACKGROUND
-
- The IASIO interface declared in the Steinberg ASIO 2 SDK declares
- functions with no explicit calling convention. This causes MSVC++ to default
- to using the thiscall convention, which is a proprietary convention not
- implemented by some non-microsoft compilers - notably borland BCC,
- C++Builder, and gcc. MSVC++ is the defacto standard compiler used by
- Steinberg. As a result of this situation, the ASIO sdk will compile with
- any compiler, however attempting to execute the compiled code will cause a
- crash due to different default calling conventions on non-Microsoft
- compilers.
-
- IASIOThiscallResolver solves the problem by providing an adapter class that
- delegates to the IASIO interface using the correct calling convention
- (thiscall). Due to the lack of support for thiscall in the Borland and GCC
- compilers, the calls have been implemented in assembly language.
-
- A number of macros are defined for thiscall function calls with different
- numbers of parameters, with and without return values - it may be possible
- to modify the format of these macros to make them work with other inline
- assemblers.
-
-
- THISCALL DEFINITION
-
- A number of definitions of the thiscall calling convention are floating
- around the internet. The following definition has been validated against
- output from the MSVC++ compiler:
-
- For non-vararg functions, thiscall works as follows: the object (this)
- pointer is passed in ECX. All arguments are passed on the stack in
- right to left order. The return value is placed in EAX. The callee
- clears the passed arguments from the stack.
-
-
- FINDING FUNCTION POINTERS FROM AN IASIO POINTER
-
- The first field of a COM object is a pointer to its vtble. Thus a pointer
- to an object implementing the IASIO interface also points to a pointer to
- that object's vtbl. The vtble is a table of function pointers for all of
- the virtual functions exposed by the implemented interfaces.
-
- If we consider a variable declared as a pointer to IASO:
-
- IASIO *theAsioDriver
-
- theAsioDriver points to:
-
- object implementing IASIO
- {
- IASIOvtbl *vtbl
- other data
- }
-
- in other words, theAsioDriver points to a pointer to an IASIOvtbl
-
- vtbl points to a table of function pointers:
-
- IASIOvtbl ( interface IASIO : public IUnknown )
- {
- (IUnknown functions)
- 0 virtual HRESULT STDMETHODCALLTYPE (*QueryInterface)(REFIID riid, void **ppv) = 0;
- 4 virtual ULONG STDMETHODCALLTYPE (*AddRef)() = 0;
- 8 virtual ULONG STDMETHODCALLTYPE (*Release)() = 0;
-
- (IASIO functions)
- 12 virtual ASIOBool (*init)(void *sysHandle) = 0;
- 16 virtual void (*getDriverName)(char *name) = 0;
- 20 virtual long (*getDriverVersion)() = 0;
- 24 virtual void (*getErrorMessage)(char *string) = 0;
- 28 virtual ASIOError (*start)() = 0;
- 32 virtual ASIOError (*stop)() = 0;
- 36 virtual ASIOError (*getChannels)(long *numInputChannels, long *numOutputChannels) = 0;
- 40 virtual ASIOError (*getLatencies)(long *inputLatency, long *outputLatency) = 0;
- 44 virtual ASIOError (*getBufferSize)(long *minSize, long *maxSize,
- long *preferredSize, long *granularity) = 0;
- 48 virtual ASIOError (*canSampleRate)(ASIOSampleRate sampleRate) = 0;
- 52 virtual ASIOError (*getSampleRate)(ASIOSampleRate *sampleRate) = 0;
- 56 virtual ASIOError (*setSampleRate)(ASIOSampleRate sampleRate) = 0;
- 60 virtual ASIOError (*getClockSources)(ASIOClockSource *clocks, long *numSources) = 0;
- 64 virtual ASIOError (*setClockSource)(long reference) = 0;
- 68 virtual ASIOError (*getSamplePosition)(ASIOSamples *sPos, ASIOTimeStamp *tStamp) = 0;
- 72 virtual ASIOError (*getChannelInfo)(ASIOChannelInfo *info) = 0;
- 76 virtual ASIOError (*createBuffers)(ASIOBufferInfo *bufferInfos, long numChannels,
- long bufferSize, ASIOCallbacks *callbacks) = 0;
- 80 virtual ASIOError (*disposeBuffers)() = 0;
- 84 virtual ASIOError (*controlPanel)() = 0;
- 88 virtual ASIOError (*future)(long selector,void *opt) = 0;
- 92 virtual ASIOError (*outputReady)() = 0;
- };
-
- The numbers in the left column show the byte offset of each function ptr
- from the beginning of the vtbl. These numbers are used in the code below
- to select different functions.
-
- In order to find the address of a particular function, theAsioDriver
- must first be dereferenced to find the value of the vtbl pointer:
-
- mov eax, theAsioDriver
- mov edx, [theAsioDriver] // edx now points to vtbl[0]
-
- Then an offset must be added to the vtbl pointer to select a
- particular function, for example vtbl+44 points to the slot containing
- a pointer to the getBufferSize function.
-
- Finally vtbl+x must be dereferenced to obtain the value of the function
- pointer stored in that address:
-
- call [edx+44] // call the function pointed to by
- // the value in the getBufferSize field of the vtbl
-
-
- SEE ALSO
-
- Martin Fay's OpenASIO DLL at http://www.martinfay.com solves the same
- problem by providing a new COM interface which wraps IASIO with an
- interface that uses portable calling conventions. OpenASIO must be compiled
- with MSVC, and requires that you ship the OpenASIO DLL with your
- application.
-
-
- ACKNOWLEDGEMENTS
-
- Ross Bencina: worked out the thiscall details above, wrote the original
- Borland asm macros, and a patch for asio.cpp (which is no longer needed).
- Thanks to Martin Fay for introducing me to the issues discussed here,
- and to Rene G. Ceballos for assisting with asm dumps from MSVC++.
-
- Antti Silvast: converted the original calliasio to work with gcc and NASM
- by implementing the asm code in a separate file.
-
- Fraser Adams: modified the original calliasio containing the Borland inline
- asm to add inline asm for gcc i.e. Intel syntax for Borland and AT&T syntax
- for gcc. This seems a neater approach for gcc than to have a separate .asm
- file and it means that we only need one version of the thiscall patch.
-
- Fraser Adams: rewrote the original calliasio patch in the form of the
- IASIOThiscallResolver class in order to avoid modifications to files from
- the Steinberg SDK, which may have had potential licence issues.
-
- Andrew Baldwin: contributed fixes for compatibility problems with more
- recent versions of the gcc assembler.
- */
-
-
- // We only need IASIOThiscallResolver at all if we are on Win32. For other
- // platforms we simply bypass the IASIOThiscallResolver definition to allow us
- // to be safely #include'd whatever the platform to keep client code portable
- #if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) && !defined(_WIN64)
-
-
- // If microsoft compiler we can call IASIO directly so IASIOThiscallResolver
- // is not used.
- #if !defined(_MSC_VER)
-
-
- #include <new>
- #include <assert.h>
-
- // We have a mechanism in iasiothiscallresolver.h to ensure that asio.h is
- // #include'd before it in client code, we do NOT want to do this test here.
- #define iasiothiscallresolver_sourcefile 1
- #include "iasiothiscallresolver.h"
- #undef iasiothiscallresolver_sourcefile
-
- // iasiothiscallresolver.h redefines ASIOInit for clients, but we don't want
- // this macro defined in this translation unit.
- #undef ASIOInit
-
-
- // theAsioDriver is a global pointer to the current IASIO instance which the
- // ASIO SDK uses to perform all actions on the IASIO interface. We substitute
- // our own forwarding interface into this pointer.
- extern IASIO* theAsioDriver;
-
-
- // The following macros define the inline assembler for BORLAND first then gcc
-
- #if defined(__BCPLUSPLUS__) || defined(__BORLANDC__)
-
-
- #define CALL_THISCALL_0( resultName, thisPtr, funcOffset )\
- void *this_ = (thisPtr); \
- __asm { \
- mov ecx, this_ ; \
- mov eax, [ecx] ; \
- call [eax+funcOffset] ; \
- mov resultName, eax ; \
- }
-
-
- #define CALL_VOID_THISCALL_1( thisPtr, funcOffset, param1 )\
- void *this_ = (thisPtr); \
- __asm { \
- mov eax, param1 ; \
- push eax ; \
- mov ecx, this_ ; \
- mov eax, [ecx] ; \
- call [eax+funcOffset] ; \
- }
-
-
- #define CALL_THISCALL_1( resultName, thisPtr, funcOffset, param1 )\
- void *this_ = (thisPtr); \
- __asm { \
- mov eax, param1 ; \
- push eax ; \
- mov ecx, this_ ; \
- mov eax, [ecx] ; \
- call [eax+funcOffset] ; \
- mov resultName, eax ; \
- }
-
-
- #define CALL_THISCALL_1_DOUBLE( resultName, thisPtr, funcOffset, param1 )\
- void *this_ = (thisPtr); \
- void *doubleParamPtr_ (¶m1); \
- __asm { \
- mov eax, doubleParamPtr_ ; \
- push [eax+4] ; \
- push [eax] ; \
- mov ecx, this_ ; \
- mov eax, [ecx] ; \
- call [eax+funcOffset] ; \
- mov resultName, eax ; \
- }
-
-
- #define CALL_THISCALL_2( resultName, thisPtr, funcOffset, param1, param2 )\
- void *this_ = (thisPtr); \
- __asm { \
- mov eax, param2 ; \
- push eax ; \
- mov eax, param1 ; \
- push eax ; \
- mov ecx, this_ ; \
- mov eax, [ecx] ; \
- call [eax+funcOffset] ; \
- mov resultName, eax ; \
- }
-
-
- #define CALL_THISCALL_4( resultName, thisPtr, funcOffset, param1, param2, param3, param4 )\
- void *this_ = (thisPtr); \
- __asm { \
- mov eax, param4 ; \
- push eax ; \
- mov eax, param3 ; \
- push eax ; \
- mov eax, param2 ; \
- push eax ; \
- mov eax, param1 ; \
- push eax ; \
- mov ecx, this_ ; \
- mov eax, [ecx] ; \
- call [eax+funcOffset] ; \
- mov resultName, eax ; \
- }
-
-
- #elif defined(__GNUC__)
-
-
- #define CALL_THISCALL_0( resultName, thisPtr, funcOffset ) \
- __asm__ __volatile__ ("movl (%1), %%edx\n\t" \
- "call *"#funcOffset"(%%edx)\n\t" \
- :"=a"(resultName) /* Output Operands */ \
- :"c"(thisPtr) /* Input Operands */ \
- : "%edx" /* Clobbered Registers */ \
- ); \
-
-
- #define CALL_VOID_THISCALL_1( thisPtr, funcOffset, param1 ) \
- __asm__ __volatile__ ("pushl %0\n\t" \
- "movl (%1), %%edx\n\t" \
- "call *"#funcOffset"(%%edx)\n\t" \
- : /* Output Operands */ \
- :"r"(param1), /* Input Operands */ \
- "c"(thisPtr) \
- : "%edx" /* Clobbered Registers */ \
- ); \
-
-
- #define CALL_THISCALL_1( resultName, thisPtr, funcOffset, param1 ) \
- __asm__ __volatile__ ("pushl %1\n\t" \
- "movl (%2), %%edx\n\t" \
- "call *"#funcOffset"(%%edx)\n\t" \
- :"=a"(resultName) /* Output Operands */ \
- :"r"(param1), /* Input Operands */ \
- "c"(thisPtr) \
- : "%edx" /* Clobbered Registers */ \
- ); \
-
-
- #define CALL_THISCALL_1_DOUBLE( resultName, thisPtr, funcOffset, param1 ) \
- do { \
- double param1f64 = param1; /* Cast explicitly to double */ \
- double *param1f64Ptr = ¶m1f64; /* Make pointer to address */ \
- __asm__ __volatile__ ("pushl 4(%1)\n\t" \
- "pushl (%1)\n\t" \
- "movl (%2), %%edx\n\t" \
- "call *"#funcOffset"(%%edx);\n\t" \
- : "=a"(resultName) /* Output Operands */ \
- : "r"(param1f64Ptr), /* Input Operands */ \
- "c"(thisPtr), \
- "m"(*param1f64Ptr) /* Using address */ \
- : "%edx" /* Clobbered Registers */ \
- ); \
- } while (0); \
-
-
- #define CALL_THISCALL_2( resultName, thisPtr, funcOffset, param1, param2 ) \
- __asm__ __volatile__ ("pushl %1\n\t" \
- "pushl %2\n\t" \
- "movl (%3), %%edx\n\t" \
- "call *"#funcOffset"(%%edx)\n\t" \
- :"=a"(resultName) /* Output Operands */ \
- :"r"(param2), /* Input Operands */ \
- "r"(param1), \
- "c"(thisPtr) \
- : "%edx" /* Clobbered Registers */ \
- ); \
-
-
- #define CALL_THISCALL_4( resultName, thisPtr, funcOffset, param1, param2, param3, param4 )\
- __asm__ __volatile__ ("pushl %1\n\t" \
- "pushl %2\n\t" \
- "pushl %3\n\t" \
- "pushl %4\n\t" \
- "movl (%5), %%edx\n\t" \
- "call *"#funcOffset"(%%edx)\n\t" \
- :"=a"(resultName) /* Output Operands */ \
- :"r"(param4), /* Input Operands */ \
- "r"(param3), \
- "r"(param2), \
- "r"(param1), \
- "c"(thisPtr) \
- : "%edx" /* Clobbered Registers */ \
- ); \
-
- #endif
-
-
-
- // Our static singleton instance.
- IASIOThiscallResolver IASIOThiscallResolver::instance;
-
- // Constructor called to initialize static Singleton instance above. Note that
- // it is important not to clear that_ incase it has already been set by the call
- // to placement new in ASIOInit().
- IASIOThiscallResolver::IASIOThiscallResolver()
- {
- }
-
- // Constructor called from ASIOInit() below
- IASIOThiscallResolver::IASIOThiscallResolver(IASIO* that)
- : that_( that )
- {
- }
-
- // Implement IUnknown methods as assert(false). IASIOThiscallResolver is not
- // really a COM object, just a wrapper which will work with the ASIO SDK.
- // If you wanted to use ASIO without the SDK you might want to implement COM
- // aggregation in these methods.
- HRESULT STDMETHODCALLTYPE IASIOThiscallResolver::QueryInterface(REFIID riid, void **ppv)
- {
- (void)riid; // suppress unused variable warning
-
- assert( false ); // this function should never be called by the ASIO SDK.
-
- *ppv = NULL;
- return E_NOINTERFACE;
- }
-
- ULONG STDMETHODCALLTYPE IASIOThiscallResolver::AddRef()
- {
- assert( false ); // this function should never be called by the ASIO SDK.
-
- return 1;
- }
-
- ULONG STDMETHODCALLTYPE IASIOThiscallResolver::Release()
- {
- assert( false ); // this function should never be called by the ASIO SDK.
-
- return 1;
- }
-
-
- // Implement the IASIO interface methods by performing the vptr manipulation
- // described above then delegating to the real implementation.
- ASIOBool IASIOThiscallResolver::init(void *sysHandle)
- {
- ASIOBool result;
- CALL_THISCALL_1( result, that_, 12, sysHandle );
- return result;
- }
-
- void IASIOThiscallResolver::getDriverName(char *name)
- {
- CALL_VOID_THISCALL_1( that_, 16, name );
- }
-
- long IASIOThiscallResolver::getDriverVersion()
- {
- ASIOBool result;
- CALL_THISCALL_0( result, that_, 20 );
- return result;
- }
-
- void IASIOThiscallResolver::getErrorMessage(char *string)
- {
- CALL_VOID_THISCALL_1( that_, 24, string );
- }
-
- ASIOError IASIOThiscallResolver::start()
- {
- ASIOBool result;
- CALL_THISCALL_0( result, that_, 28 );
- return result;
- }
-
- ASIOError IASIOThiscallResolver::stop()
- {
- ASIOBool result;
- CALL_THISCALL_0( result, that_, 32 );
- return result;
- }
-
- ASIOError IASIOThiscallResolver::getChannels(long *numInputChannels, long *numOutputChannels)
- {
- ASIOBool result;
- CALL_THISCALL_2( result, that_, 36, numInputChannels, numOutputChannels );
- return result;
- }
-
- ASIOError IASIOThiscallResolver::getLatencies(long *inputLatency, long *outputLatency)
- {
- ASIOBool result;
- CALL_THISCALL_2( result, that_, 40, inputLatency, outputLatency );
- return result;
- }
-
- ASIOError IASIOThiscallResolver::getBufferSize(long *minSize, long *maxSize,
- long *preferredSize, long *granularity)
- {
- ASIOBool result;
- CALL_THISCALL_4( result, that_, 44, minSize, maxSize, preferredSize, granularity );
- return result;
- }
-
- ASIOError IASIOThiscallResolver::canSampleRate(ASIOSampleRate sampleRate)
- {
- ASIOBool result;
- CALL_THISCALL_1_DOUBLE( result, that_, 48, sampleRate );
- return result;
- }
-
- ASIOError IASIOThiscallResolver::getSampleRate(ASIOSampleRate *sampleRate)
- {
- ASIOBool result;
- CALL_THISCALL_1( result, that_, 52, sampleRate );
- return result;
- }
-
- ASIOError IASIOThiscallResolver::setSampleRate(ASIOSampleRate sampleRate)
- {
- ASIOBool result;
- CALL_THISCALL_1_DOUBLE( result, that_, 56, sampleRate );
- return result;
- }
-
- ASIOError IASIOThiscallResolver::getClockSources(ASIOClockSource *clocks, long *numSources)
- {
- ASIOBool result;
- CALL_THISCALL_2( result, that_, 60, clocks, numSources );
- return result;
- }
-
- ASIOError IASIOThiscallResolver::setClockSource(long reference)
- {
- ASIOBool result;
- CALL_THISCALL_1( result, that_, 64, reference );
- return result;
- }
-
- ASIOError IASIOThiscallResolver::getSamplePosition(ASIOSamples *sPos, ASIOTimeStamp *tStamp)
- {
- ASIOBool result;
- CALL_THISCALL_2( result, that_, 68, sPos, tStamp );
- return result;
- }
-
- ASIOError IASIOThiscallResolver::getChannelInfo(ASIOChannelInfo *info)
- {
- ASIOBool result;
- CALL_THISCALL_1( result, that_, 72, info );
- return result;
- }
-
- ASIOError IASIOThiscallResolver::createBuffers(ASIOBufferInfo *bufferInfos,
- long numChannels, long bufferSize, ASIOCallbacks *callbacks)
- {
- ASIOBool result;
- CALL_THISCALL_4( result, that_, 76, bufferInfos, numChannels, bufferSize, callbacks );
- return result;
- }
-
- ASIOError IASIOThiscallResolver::disposeBuffers()
- {
- ASIOBool result;
- CALL_THISCALL_0( result, that_, 80 );
- return result;
- }
-
- ASIOError IASIOThiscallResolver::controlPanel()
- {
- ASIOBool result;
- CALL_THISCALL_0( result, that_, 84 );
- return result;
- }
-
- ASIOError IASIOThiscallResolver::future(long selector,void *opt)
- {
- ASIOBool result;
- CALL_THISCALL_2( result, that_, 88, selector, opt );
- return result;
- }
-
- ASIOError IASIOThiscallResolver::outputReady()
- {
- ASIOBool result;
- CALL_THISCALL_0( result, that_, 92 );
- return result;
- }
-
-
- // Implement our substitute ASIOInit() method
- ASIOError IASIOThiscallResolver::ASIOInit(ASIODriverInfo *info)
- {
- // To ensure that our instance's vptr is correctly constructed, even if
- // ASIOInit is called prior to main(), we explicitly call its constructor
- // (potentially over the top of an existing instance). Note that this is
- // pretty ugly, and is only safe because IASIOThiscallResolver has no
- // destructor and contains no objects with destructors.
- new((void*)&instance) IASIOThiscallResolver( theAsioDriver );
-
- // Interpose between ASIO client code and the real driver.
- theAsioDriver = &instance;
-
- // Note that we never need to switch theAsioDriver back to point to the
- // real driver because theAsioDriver is reset to zero in ASIOExit().
-
- // Delegate to the real ASIOInit
- return ::ASIOInit(info);
- }
-
-
- #endif /* !defined(_MSC_VER) */
-
- #endif /* Win32 */
-
|