Recent

Author Topic: Linking a PortAudio static library with C++ objects into a Lazarus Win64 Exe  (Read 710 times)

Fred vS

  • Hero Member
  • *****
  • Posts: 3947
    • StrumPract is the musicians best friend
Re-hello.

Using wine + fpc.exe, and this in the demo code:

Code: Pascal  [Select][+][-]
  1. {$LinkLib libportaudio.a} // Pulls libportaudio.a from the local directory
  2.  
  3. {$IFDEF WINDOWS}
  4.   { Native Windows hooks required for WASAPI, DirectSound, and ASIO }
  5.   {$LinkLib ole32} {$LinkLib winmm} {$LinkLib setupapi} {$LinkLib advapi} {$LinkLib uuid}
  6.   { GCC Runtime compatibility hooks }
  7.   {$LinkLib gcc} {$LinkLib msvcrt} {$LinkLib mingwex}
  8. {$ENDIF}

and that fpc command on linux using wine emulator:

Code: Pascal  [Select][+][-]
  1. wine fpc.exe -FUunits -Fl. -Twin64 -B  portaudio_demo.pas

It compiles and links ok but I cannot try it on my linux machine (asio is not installed.)
I use Lazarus 2.2.0 32/64 and FPC 3.2.2 32/64 on Debian 11 64 bit, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt.

https://github.com/fredvs
https://gitlab.com/fredvs
https://codeberg.org/fredvs

Fred vS

  • Hero Member
  • *****
  • Posts: 3947
    • StrumPract is the musicians best friend
OK, because I'm a fighter who never surrenders:

In https://github.com/fredvs/portaudio-static/releases/tag/1 there is test-static-portaudio.zip

Unzip it and compile portaudio_demo.pas with

Code: Pascal  [Select][+][-]
  1. fpc.exe -FUunits -Fl. -Twin64 -B  portaudio_demo.pas

This should produce a sine-wave sound.

I confess that Gemini helped and in the code it added custom methods because asio is done in c++ and this is not compatible for fpc.
But it works here using wine so it must work too (I hope) in a real windows machine.

[EDIT] The binaries of libportaudio.a for windows are updated.

Have fun.

Fre;D
« Last Edit: May 20, 2026, 06:06:02 pm by Fred vS »
I use Lazarus 2.2.0 32/64 and FPC 3.2.2 32/64 on Debian 11 64 bit, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt.

https://github.com/fredvs
https://gitlab.com/fredvs
https://codeberg.org/fredvs

bbrx

  • New Member
  • *
  • Posts: 26
OK, because I'm a fighter who never surrenders:

In https://github.com/fredvs/portaudio-static/releases/tag/1 there is test-static-portaudio.zip

Unzip it and compile portaudio_demo.pas with

Code: Pascal  [Select][+][-]
  1. fpc.exe -FUunits -Fl. -Twin64 -B  portaudio_demo.pas

This should produce a sine-wave sound.

I confess that Gemini helped and in the code it added custom methods because asio is done in c++ and this is not compatible for fpc.
But it works here using wine so it must work too (I hope) in a real windows machine.

Have fun.

Fre;D


Thank you very much Fred, this is extremely helpful!!!

I looked at your approach and I now understand that you are not linking the full C++ runtime such as libstdc++.a / libsupc++.a / libgcc_eh.a.

Instead, you link only the minimal C / MinGW / Windows libraries:

- libportaudio.a
- libgcc.a
- libmingwex.a
- libmsvcrt.a
- ole32 / winmm / setupapi / advapi / uuid / kernel32 / user32

and then you provide a few lightweight C++ ABI stubs directly in Pascal, such as:

- operator new / delete
- __gxx_personality_seh0
- __cxa_begin_catch / __cxa_end_catch
- _Unwind_Resume
- a few RTTI / vtable symbols

That probably explains why your setup avoids the cascade of problems I get when linking libstdc++.a directly.

Also, do you consider these C++ stubs safe enough for PortAudio’s ASIO backend in practice, assuming the C++ code does not really throw exceptions at runtime?

In my case, linking libstdc++.a pulls many additional dependencies and creates conflicts with FPC/Lazarus, while using the MSYS2 ld.exe breaks on some Lazarus/LCL object files.
So your minimal-stub approach may be exactly the workaround I was looking for.

Now it's working with your method without linking libstdc++.a and with the embedded Pascal C++ stubs.

My concern is that this is not a real C++ runtime, but a lightweight emulation of only the symbols required by the linker.
So I wonder what the real-world risks are:

- crash if a real C++ exception is thrown
- undefined behavior if RTTI / vtables are actually used
- memory issues if new/delete variants are incomplete
- driver-specific behavior with some ASIO drivers
- long-term maintenance risk if PortAudio or the ASIO SDK changes

Thanks again for sharing this.
« Last Edit: May 20, 2026, 06:10:55 pm by bbrx »

Fred vS

  • Hero Member
  • *****
  • Posts: 3947
    • StrumPract is the musicians best friend
I am happy that you are happy.  ;D

About avoiding c++ using the workaround, of course I am not sure it is safe but it works.

And for ASIO, I did fight for the fun but they are very strict in their license and they dont like static linking of their lib.
I use Lazarus 2.2.0 32/64 and FPC 3.2.2 32/64 on Debian 11 64 bit, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt.

https://github.com/fredvs
https://gitlab.com/fredvs
https://codeberg.org/fredvs

Fred vS

  • Hero Member
  • *****
  • Posts: 3947
    • StrumPract is the musicians best friend
May I ask why you prefer linking static libraries rather than dynamic libraries?

With DynLibs.LoadLibrary(dir-of-libs), you can choose the directory for the DDL file.

[EDIT] And if you prefer to use external you my add this parameter at compil:
Code: Pascal  [Select][+][-]
  1. -Fl./dir-of-libs
« Last Edit: May 20, 2026, 07:33:56 pm by Fred vS »
I use Lazarus 2.2.0 32/64 and FPC 3.2.2 32/64 on Debian 11 64 bit, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt.

https://github.com/fredvs
https://gitlab.com/fredvs
https://codeberg.org/fredvs

Fred vS

  • Hero Member
  • *****
  • Posts: 3947
    • StrumPract is the musicians best friend
Here the code of the demo, maybe somebody could check the c++ workaround:
LIGHTWEIGHT EMBEDDED C++ RUNTIME EMULATION STUBS

Code: Pascal  [Select][+][-]
  1. program portaudio_demo;
  2.  
  3. {$mode objfpc}{$H+}
  4. {$PACKRECORDS C}
  5.  
  6. uses
  7.   ctypes, Math, SysUtils;
  8.  
  9. const
  10.   NUM_CHANNELS    = 2;
  11.   SAMPLE_RATE     = 44100;
  12.   FRAMES_PER_BUFF = 256;
  13.   PI2             = 6.283185307179586476925286766559;
  14.  
  15. type
  16.   PaError = cint;
  17.   PaStreamParameters = record
  18.     device: cint;
  19.     channelCount: cint;
  20.     sampleFormat: culong;
  21.     suggestedLatency: double;
  22.     hostApiSpecificStreamInfo: Pointer;
  23.   end;
  24.   PPaStreamParameters = ^PaStreamParameters;
  25.   PaStream = Pointer;
  26.   PPaStream = ^PaStream;
  27.   PaStreamFlags = culong;
  28.  
  29.   OscillatorData = record
  30.     phase: double;
  31.     phaseIncrement: double;
  32.   end;
  33.   POscillatorData = ^OscillatorData;
  34.  
  35. { ====================================================================
  36.   PORTAUDIO FUNCTION DECLARATIONS (DUAL MULTI-OS LAYOUT)
  37.   ==================================================================== }
  38. {$IFDEF WINDOWS}
  39.   function Pa_Initialize: PaError; cdecl; external name 'Pa_Initialize';
  40.   function Pa_Terminate: PaError; cdecl; external name 'Pa_Terminate';
  41.   function Pa_GetDefaultOutputDevice: cint; cdecl; external name 'Pa_GetDefaultOutputDevice';
  42.   function Pa_OpenStream(stream: PPaStream; inputParameters: PPaStreamParameters; outputParameters: PPaStreamParameters; sampleRate: double; framesPerBuffer: culong; streamFlags: PaStreamFlags; streamCallback: Pointer; userData: Pointer): PaError; cdecl; external name 'Pa_OpenStream';
  43.   function Pa_StartStream(stream: PaStream): PaError; cdecl; external name 'Pa_StartStream';
  44.   function Pa_StopStream(stream: PaStream): PaError; cdecl; external name 'Pa_StopStream';
  45.   function Pa_CloseStream(stream: PaStream): PaError; cdecl; external name 'Pa_CloseStream';
  46.   procedure Pa_Sleep(msec: clong); cdecl; external name 'Pa_Sleep';
  47. {$ELSE}
  48.   function Pa_Initialize: PaError; cdecl; external 'portaudio' name 'Pa_Initialize';
  49.   function Pa_Terminate: PaError; cdecl; external 'portaudio' name 'Pa_Terminate';
  50.   function Pa_GetDefaultOutputDevice: cint; cdecl; external 'portaudio' name 'Pa_GetDefaultOutputDevice';
  51.   function Pa_OpenStream(stream: PPaStream; inputParameters: PPaStreamParameters; outputParameters: PPaStreamParameters; sampleRate: double; framesPerBuffer: culong; streamFlags: PaStreamFlags; streamCallback: Pointer; userData: Pointer): PaError; cdecl; external 'portaudio' name 'Pa_OpenStream';
  52.   function Pa_StartStream(stream: PaStream): PaError; cdecl; external 'portaudio' name 'Pa_StartStream';
  53.   function Pa_StopStream(stream: PaStream): PaError; cdecl; external 'portaudio' name 'Pa_StopStream';
  54.   function Pa_CloseStream(stream: PaStream): PaError; cdecl; external 'portaudio' name 'Pa_CloseStream';
  55.   procedure Pa_Sleep(msec: clong); cdecl; external 'portaudio' name 'Pa_Sleep';
  56. {$ENDIF}
  57.  
  58. { ====================================================================
  59.   STATIC LINKING REGION (ZERO C++ ARCHIVES REQUIRED)
  60.   ==================================================================== }
  61. {$LinkLib libportaudio.a}
  62.  
  63. {$IFDEF WINDOWS}
  64.   {$LinkLib libgcc.a}
  65.   {$LinkLib libmingwex.a}
  66.   {$LinkLib libmsvcrt.a}
  67.  
  68.   {$LinkLib libole32.a}
  69.   {$LinkLib libwinmm.a}
  70.   {$LinkLib libsetupapi.a}
  71.   {$LinkLib libadvapi.a}
  72.   {$LinkLib libuuid.a}
  73.   {$LinkLib libkernel32.a}
  74.   {$LinkLib libuser32.a}
  75. {$ENDIF}
  76.  
  77. { ====================================================================
  78.   LIGHTWEIGHT EMBEDDED C++ RUNTIME EMULATION STUBS
  79.   ==================================================================== }
  80. {$IFDEF WINDOWS}
  81. procedure C_free(p: Pointer); cdecl; external 'msvcrt' name 'free';
  82. function C_malloc(size: ptruint): Pointer; cdecl; external 'msvcrt' name 'malloc';
  83.  
  84. { Note: public modifier paired with alias name string directly satisfies internal ld lookups }
  85. function operator_new(size: ptruint): Pointer; cdecl; [public, alias: '_Znwy'];
  86. begin
  87.   Result := C_malloc(size);
  88. end;
  89.  
  90. function operator_new_arr(size: ptruint): Pointer; cdecl; [public, alias: '_Znay'];
  91. begin
  92.   Result := C_malloc(size);
  93. end;
  94.  
  95. procedure operator_delete(p: Pointer); cdecl; [public, alias: '_ZdlPvy'];
  96. begin
  97.   C_free(p);
  98. end;
  99.  
  100. procedure operator_delete_arr(p: Pointer); cdecl; [public, alias: '_ZdaPv'];
  101. begin
  102.   C_free(p);
  103. end;
  104.  
  105. procedure __gxx_personality_seh0; cdecl; [public, alias: '__gxx_personality_seh0']; begin end;
  106. procedure __cxa_begin_catch; cdecl; [public, alias: '__cxa_begin_catch']; begin end;
  107. procedure __cxa_end_catch; cdecl; [public, alias: '__cxa_end_catch']; begin end;
  108. procedure _Unwind_Resume; cdecl; [public, alias: '_Unwind_Resume']; begin end;
  109. procedure _ZNSt9bad_allocD1Ev; cdecl; [public, alias: '_ZNSt9bad_allocD1Ev']; begin end;
  110.  
  111. var
  112.   _ZTVN10__cxxabiv117__class_type_infoE: array[0..3] of Pointer; cvar; public;
  113.   _ZTVN10__cxxabiv120__si_class_type_infoE: array[0..3] of Pointer; cvar; public;
  114.   _ZTVSt9bad_alloc: array[0..3] of Pointer; cvar; public;
  115. {$ENDIF}
  116.  
  117. { ====================================================================
  118.   REAL-TIME AUDIO CALLBACK
  119.   ==================================================================== }
  120. function AudioCallback(inputBuffer: Pointer; outputBuffer: Pointer;
  121.                        framesPerBuffer: culong; timeInfo: Pointer;
  122.                        statusFlags: culong; userData: Pointer): cint; cdecl;
  123. var
  124.   outPtr: PSingle;
  125.   data: POscillatorData;
  126.   i: culong;
  127.   sample: Single;
  128. begin
  129.   outPtr := PSingle(outputBuffer);
  130.   data := POscillatorData(userData);
  131.   for i := 0 to framesPerBuffer - 1 do
  132.   begin
  133.     sample := Single(Sin(data^.phase));
  134.     outPtr^ := sample; Inc(outPtr);
  135.     outPtr^ := sample; Inc(outPtr);
  136.     data^.phase := data^.phase + data^.phaseIncrement;
  137.     if data^.phase >= PI2 then data^.phase := data^.phase - PI2;
  138.   end;
  139.   Result := 0;
  140. end;
  141.  
  142. { ====================================================================
  143.   MAIN APPLICATION EXECUTIVE
  144.   ==================================================================== }
  145. var
  146.   err: PaError;
  147.   stream: PaStream;
  148.   outputParams: PaStreamParameters;
  149.   sineData: OscillatorData;
  150. begin
  151.   WriteLn('--- uos Clean Static PortAudio Test ---');
  152.   sineData.phase := 0.0;
  153.   sineData.phaseIncrement := (440.0 * PI2) / SAMPLE_RATE;
  154.  
  155.   err := Pa_Initialize;
  156.   if err <> 0 then begin WriteLn('Initialization Failure!'); Exit; end;
  157.  
  158.   outputParams.device := Pa_GetDefaultOutputDevice;
  159.   if outputParams.device = -1 then begin WriteLn('No default audio device found!'); Pa_Terminate; Exit; end;
  160.  
  161.   outputParams.channelCount := NUM_CHANNELS;
  162.   outputParams.sampleFormat := $00000001;
  163.   outputParams.suggestedLatency := 0.050;
  164.   outputParams.hostApiSpecificStreamInfo := nil;
  165.  
  166.   err := Pa_OpenStream(@stream, nil, @outputParams, SAMPLE_RATE, FRAMES_PER_BUFF, 0, @AudioCallback, @sineData);
  167.   if err <> 0 then begin WriteLn('Failed to open audio stream!'); Pa_Terminate; Exit; end;
  168.  
  169.   Pa_StartStream(stream);
  170.   Pa_Sleep(4000);
  171.  
  172.   Pa_StopStream(stream); Pa_CloseStream(stream); Pa_Terminate;
  173.   WriteLn('Done!');
  174. end.
I use Lazarus 2.2.0 32/64 and FPC 3.2.2 32/64 on Debian 11 64 bit, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt.

https://github.com/fredvs
https://gitlab.com/fredvs
https://codeberg.org/fredvs

bbrx

  • New Member
  • *
  • Posts: 26
May I ask why you prefer linking static libraries rather than dynamic libraries?

With DynLibs.LoadLibrary(dir-of-libs), you can choose the directory for the DDL file.

[EDIT] And if you prefer to use external you my add this parameter at compil:
Code: Pascal  [Select][+][-]
  1. -Fl./dir-of-libs

[EDIT] And if you prefer to use external you my add this parameter at compil:
Code: Pascal  [Select][+][-]
  1. -Fl./dir-of-libs
[/quote]

Well, since October 2025, my understanding is that ASIO SDK is available under a dual-license model:

- GPLv3 for open-source projects that can comply with GPLv3 or
- the proprietary Steinberg ASIO license for closed-source / commercial products.

In my case, my application is proprietary, so the GPLv3 route is not suitable for my product.
I therefore need to rely on the proprietary Steinberg ASIO agreement.

This is why I am cautious about distributing a standalone reusable PortAudio+ASIO DLL.
My goal is not to redistribute the ASIO SDK or an ASIO-related developer component, but to keep the ASIO-enabled implementation as part of my own application distribution.
I still need to confirm the exact agreement terms for my case, but this is the reason I am avoiding a standalone reusable PortAudio+ASIO DLL for now.

That said, it is also possible that I decide not to use ASIO at all if it becomes too constraining, too hard to maintain, or not stable enough in the long term.

Even if your workaround works, I still need to test it carefully over time, especially because the Pascal C++ ABI stubs are not a full C++ runtime.
I need to check how it behaves with different ASIO drivers, error cases, device changes, stream open/close cycles, and whether any real C++ exception or RTTI-related behavior is ever triggered.

So for now my goal is to evaluate:

- whether PortAudio + ASIO can be safely embedded statically in my application
- whether the lightweight C++ stub approach is stable enough in practice
- whether ASIO is really worth the extra maintenance cost for my use case

Thanks again, your example helped me understand a possible path forward.
 
« Last Edit: May 20, 2026, 08:31:25 pm by bbrx »

 

TinyPortal © 2005-2018