'FFI: How to Create Native Handle from Which Native Pointer Can be Derived

I am trying to do some audio I/O using PortAudio accessed from Dart via FFI. I am running a current version of Ubuntu 20.04 on a late 2012 Mac Mini with 16GB of RAM. I am coding Dart using WebStorm and C using Eclipse. I have had similar problems attempting this task running macOS 10.15.7 (Catalina) on the same Mac Mini.

PortAudio has one interface quirk in that it uses a pointer to an input or output stream as an argument to many of its functions; however, when opening an input or output stream, you must pass a handle to the stream (&stream) rather than a pointer.

In addition, if called from inside C code, no special pointer initialization is needed prior to calling OpenStream, just declare the stream pointer (stream) and pass its address to OpenStream (&stream), and PortAudio is happy (that's just the way C works).

Unfortunately, I am really struggling with how to create and initialize the pointer and handle in Dart in a way that doesn't return a badStreamPtr error from OpenStream.

There does not seem to be a graceful way to do this in Dart (although I claim no high expertise in Dart, and my C skills have been mostly unused for 20+ years)

  1. The Handle class seems to truly be a marker class (i.e., if you get a fully formed handle from C, you can store it in Dart, and then pass it back to C, but there does not seem to be any way to instantiate or inspect it in Dart that I could tell. My PortAudio test with Handle failed).

  2. You can create a Pointer in Dart, then pass its address to the value of another Pointer. While super-clunky, this seemed to be the best course, but it consistently fails with a paBadStreamPtr error after being passed to PortAudio. I did note that none of the pointer/handle addresses were changed after the call to OpenStream. I do not know if this is relevant. I kind of expected that the stream itself might be relocated by PortAudio given the need to pass a handle rather than a pointer, but I do not know under what circumstances that might occur. Note that the stream is an opaque structure accessed via a void pointer. That makes debugging even less fun.

To make sure that I had a basic grip on FFI as well as PortAudio, I wrote a small shared library in C that served as a wrapper for a Dart application to access and manipulate PortAudio. That worked fine as I didn't have to deal with the pointer/handle creation/initialization inside Dart; however, I would really like to access PortAudio directly from Dart rather than the C wrapper.

I used ffigen to generate the C bindings to PortAudio.

Here's the C function API:

    PaError Pa_OpenStream (PaStream **stream, 
    const PaStreamParameters *inputParameters, 
    const PaStreamParameters *outputParameters, 
    double sampleRate, 
    unsigned long framesPerBuffer, 
    PaStreamFlags streamFlags, 
    PaStreamCallback *streamCallback, 
    void *userData)

ffigen has translated this into Dart as:

int Pa_OpenStream(
ffi.Pointer<ffi.Pointer<PaStream>> stream,
ffi.Pointer<PaStreamParameters> inputParameters,
ffi.Pointer<PaStreamParameters> outputParameters,
double sampleRate,
int framesPerBuffer,
int streamFlags,
ffi.Pointer<PaStreamCallback> streamCallback,
ffi.Pointer<ffi.Void> userData,)
  • inputParameters and outputParamters are structs - no problems.

  • No problems with the primitives and flags (bit masked integers).

  • I failed miserably with using callbacks. The Dart documentation for
    callbacks is all but non-existent, and even the sample callback
    code posted on GitHub doesn't seem to include the corresponding C code, so that makes a difficult task even harder. As such, I am using the PortAudio blocking option using polling to capture or deliver audio samples
    from/to PortAudio. As such, the callback pointer is NULL.

  • Because of using the blocking approach, userData is NULL.

So...the only challenge remaining (I hope) is figuring out how to get --PaStream** stream-- to work when the pointer and handle are created on the Dart side.

Any ideas, or do I just need to keep a C wrapper in place for the time being?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source