This is an automated email from the ASF dual-hosted git repository.

paleolimbot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-nanoarrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 48c65647 feat: Improve shared linkage support (#719)
48c65647 is described below

commit 48c65647bb5c849203204e84cdc0b31ce4ebcc04
Author: Dewey Dunnington <[email protected]>
AuthorDate: Thu Mar 20 09:53:08 2025 -0500

    feat: Improve shared linkage support (#719)
    
    This PR makes linking to the usual targets static linkage and adds a
    paired `_shared` target for users that want to opt in to shared
    nanoarrow linkage. This is intended to mirror what ADBC and Arrow do
    (with separated `_shared` and `_static` targets).
    
    This PR also adds the appropriate import/export attribute for Windows
    (most of the changed lines in this PR).
    
    While I had my Windows machine fired up I also fixed a few compiler
    warnings, and I added a Windows and MacOS run for the `examples`
    workflow.
    
    Closes #495, closes #718
---
 .github/workflows/build-and-test-device.yaml |   6 +-
 .github/workflows/build-and-test-ipc.yaml    |   5 +-
 .github/workflows/build-and-test.yaml        |   5 +-
 .github/workflows/examples.yaml              |  49 ++++--
 CMakeLists.txt                               | 237 ++++++++++++++++---------
 cmake/config.cmake.in                        |  10 ++
 docs/source/getting-started/cpp.md           |   2 +-
 examples/cmake-ipc/CMakeLists.txt            |   6 +-
 examples/cmake-minimal/CMakeLists.txt        |   2 +-
 examples/cmake-scenarios/CMakeLists.txt      |  25 ++-
 examples/cmake-scenarios/build.sh            |  42 +++--
 examples/cmake-scenarios/run.sh              |  42 +++++
 examples/linesplitter/CMakeLists.txt         |   7 +-
 python/bootstrap.py                          |   4 +-
 src/nanoarrow/common/inline_types.h          |  16 +-
 src/nanoarrow/device/cuda_test.cc            |   4 +-
 src/nanoarrow/hpp/view_test.cc               |   6 +-
 src/nanoarrow/ipc/decoder.c                  |   4 +-
 src/nanoarrow/nanoarrow.h                    | 253 +++++++++++++++------------
 src/nanoarrow/nanoarrow_device.h             |  79 +++++----
 src/nanoarrow/nanoarrow_ipc.h                | 185 ++++++++++----------
 src/nanoarrow/nanoarrow_testing.hpp          |   6 +-
 22 files changed, 595 insertions(+), 400 deletions(-)

diff --git a/.github/workflows/build-and-test-device.yaml 
b/.github/workflows/build-and-test-device.yaml
index 7fd2c388..42b0a001 100644
--- a/.github/workflows/build-and-test-device.yaml
+++ b/.github/workflows/build-and-test-device.yaml
@@ -48,10 +48,10 @@ jobs:
           - {runner: ubuntu-latest, label: default-build}
           - {runner: ubuntu-latest, label: namespaced-build, cmake_args: 
"-DNANOARROW_NAMESPACE=SomeUserNamespace"}
           - {runner: ubuntu-latest, label: bundled-build, cmake_args: 
"-DNANOARROW_DEVICE_BUNDLE=ON"}
+          - {label: shared-test-linkage, cmake_args: 
"-DNANOARROW_TEST_LINKAGE_SHARED=ON"}
           - {runner: macOS-latest, label: with-metal, cmake_args: 
"-DNANOARROW_DEVICE_WITH_METAL=ON"}
           - {runner: ["self-hosted", "cuda"], label: with-cuda, cmake_args: 
"-DNANOARROW_DEVICE_WITH_CUDA=ON"}
 
-
     steps:
       - uses: actions/checkout@v4
 
@@ -107,10 +107,10 @@ jobs:
         if: matrix.config.label == 'namespaced-build'
         run: |
           # Dump all symbols
-          nm --extern-only build/libnanoarrow_device.a
+          nm --extern-only build/libnanoarrow_device_static.a
 
           # Check for non-namespaced ones
-          ARROW_SYMBOLS=`nm --extern-only build/libnanoarrow_device.a | grep 
"T Arrow" || true`
+          ARROW_SYMBOLS=`nm --extern-only build/libnanoarrow_device_static.a | 
grep "T Arrow" || true`
           if [ -z "$ARROW_SYMBOLS" ]; then
             exit 0
           fi
diff --git a/.github/workflows/build-and-test-ipc.yaml 
b/.github/workflows/build-and-test-ipc.yaml
index ee4332d8..e36c3759 100644
--- a/.github/workflows/build-and-test-ipc.yaml
+++ b/.github/workflows/build-and-test-ipc.yaml
@@ -45,6 +45,7 @@ jobs:
         config:
           - {label: default-build, cmake_args: "-DNANOARROW_BUILD_APPS=ON 
-DNANOARROW_IPC_WITH_ZSTD=ON"}
           - {label: default-noatomics, cmake_args: 
"-DCMAKE_C_FLAGS='-DNANOARROW_IPC_USE_STDATOMIC=0'"}
+          - {label: shared-test-linkage, cmake_args: 
"-DNANOARROW_TEST_LINKAGE_SHARED=ON"}
           - {label: namespaced-build, cmake_args: 
"-DNANOARROW_NAMESPACE=SomeUserNamespace"}
           - {label: bundled-build, cmake_args: "-DNANOARROW_BUNDLE=ON"}
 
@@ -102,10 +103,10 @@ jobs:
           cd $SUBDIR
 
           # Dump all symbols
-          nm --extern-only build/libnanoarrow_ipc.a
+          nm --extern-only build/libnanoarrow_ipc_static.a
 
           # Check for non-namespaced ones
-          ARROW_SYMBOLS=`nm --extern-only build/libnanoarrow_ipc.a | grep "T 
Arrow" || true`
+          ARROW_SYMBOLS=`nm --extern-only build/libnanoarrow_ipc_static.a | 
grep "T Arrow" || true`
           if [ -z "$ARROW_SYMBOLS" ]; then
             exit 0
           fi
diff --git a/.github/workflows/build-and-test.yaml 
b/.github/workflows/build-and-test.yaml
index 46806aca..f420a68b 100644
--- a/.github/workflows/build-and-test.yaml
+++ b/.github/workflows/build-and-test.yaml
@@ -46,6 +46,7 @@ jobs:
         config:
           - {label: default-build, cmake_args: "-DCMAKE_BUILD_TYPE=Debug"}
           - {label: release-build}
+          - {label: shared-test-linkage, cmake_args: 
"-DNANOARROW_TEST_LINKAGE_SHARED=ON"}
           - {label: namespaced-build, cmake_args: 
"-DNANOARROW_NAMESPACE=SomeUserNamespace"}
           - {label: bundled-build, cmake_args: "-DNANOARROW_BUNDLE=ON"}
           - {label: bundled-cpp-build, cmake_args: "-DNANOARROW_BUNDLE=ON 
-DNANOARROW_BUNDLE_AS_CPP=ON"}
@@ -91,10 +92,10 @@ jobs:
         if: matrix.config.label == 'namespaced-build'
         run: |
           # Dump all symbols
-          nm --extern-only build/libnanoarrow.a
+          nm --extern-only build/libnanoarrow_static.a
 
           # Check for non-namespaced ones
-          ARROW_SYMBOLS=`nm --extern-only build/libnanoarrow.a | grep "T 
Arrow" || true`
+          ARROW_SYMBOLS=`nm --extern-only build/libnanoarrow_static.a | grep 
"T Arrow" || true`
           if [ -z "$ARROW_SYMBOLS" ]; then
             exit 0
           fi
diff --git a/.github/workflows/examples.yaml b/.github/workflows/examples.yaml
index c90e15a1..82bb54a0 100644
--- a/.github/workflows/examples.yaml
+++ b/.github/workflows/examples.yaml
@@ -29,38 +29,39 @@ on:
       - 'CMakeLists.txt'
       - '.github/workflows/examples.yaml'
       - 'src/nanoarrow/**'
-      - 'src/extensions/**'
 
 jobs:
   examples:
-
-    runs-on: ubuntu-latest
+    name: ${{ matrix.config.label }}
+    runs-on: ${{ matrix.config.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        config:
+          - {os: macOS-latest, label: macos}
+          - {os: windows-latest, label: windows}
+          - {os: ubuntu-latest, label: ubuntu}
     env:
       VERBOSE: 1
 
     steps:
-      - name: Checkout repo
-        uses: actions/checkout@v4
-        with:
-          fetch-depth: 0
+      - uses: actions/checkout@v4
       - uses: actions/setup-python@v5
         with:
           python-version: '3.x'
 
-      - name: Install dependencies
-        run: |
-          sudo apt-get install -y cmake
-
       - name: Minimal CMake Example
+        shell: bash
         run: |
           cd examples/cmake-minimal
           mkdir build && cd build
           cmake ..
           cmake --build .
 
-          ./example_cmake_minimal_app
+          ./example_cmake_minimal_app || ./Debug/example_cmake_minimal_app
 
       - name: Minimal Vendored Example
+        shell: bash
         run: |
           cd examples/vendored-minimal
           python3 ../../ci/scripts/bundle.py --source-output-dir=src 
--include-output-dir=src
@@ -70,18 +71,21 @@ jobs:
           ar rcs libexample_vendored_minimal_library.a library.o nanoarrow.o
           gcc -o example_vendored_minimal_app app.c 
libexample_vendored_minimal_library.a
 
-          ./example_vendored_minimal_app
+          ./example_vendored_minimal_app || 
./Debug/example_vendored_minimal_app
 
       - name: Ipc CMake Example
+        shell: bash
         run: |
           cd examples/cmake-ipc
           mkdir build && cd build
           cmake ..
           cmake --build .
 
-          cat ../schema-valid.arrows | ./example_cmake_ipc_app
+          cat ../schema-valid.arrows | ./example_cmake_ipc_app || \
+            cat ../schema-valid.arrows | ./Debug/example_cmake_ipc_app
 
       - name: Ipc Vendored Example
+        shell: bash
         run: |
           cd examples/vendored-ipc
           python3 ../../ci/scripts/bundle.py \
@@ -96,9 +100,11 @@ jobs:
           ar rcs libexample_vendored_ipc_library.a library.o nanoarrow.o 
nanoarrow_ipc.o flatcc.o
           gcc -o example_vendored_ipc_app app.c 
libexample_vendored_ipc_library.a
 
-          cat ../schema-valid.arrows | ./example_vendored_ipc_app
+          cat ../schema-valid.arrows | ./example_vendored_ipc_app || \
+            cat ../schema-valid.arrows | ./Debug/example_vendored_ipc_app
 
       - name: Getting Started Tutorial Example
+        shell: bash
         run: |
           cd examples/linesplitter
           mkdir build && cd build
@@ -107,15 +113,19 @@ jobs:
           ctest .
 
       - name: Comprehensive CMake example
+        shell: bash
+        env:
+          EXTRA_CMAKE_INSTALL: ${{ matrix.config.label == 'windows' && 
'--config=Debug' || '' }}
         run: |
           cd examples/cmake-scenarios
           ./build.sh
-          for dir in scratch/build*; do
-            ./${dir}/minimal_cpp_app;
-            echo ;
-          done
+          ./run.sh
 
       - name: Meson example
+        # This example doesn't seem to work on Windows
+        # https://github.com/apache/arrow-nanoarrow/issues/725
+        if: matrix.config.label != 'windows'
+        shell: bash
         run: |
           python3 -m pip install meson ninja
           cd examples/meson-minimal
@@ -125,6 +135,7 @@ jobs:
           ./builddir/example_meson_minimal_app
 
       - name: Python example
+        shell: bash
         run: |
           python3 -m pip install examples/python-package
           python3 -c "import schema_printer"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 17b63e1e..2f295b92 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -42,7 +42,7 @@ option(NANOARROW_IPC_WITH_ZSTD "Build nanoarrow with ZSTD 
compression support bu
        OFF)
 
 option(NANOARROW_DEVICE "Build device extension" OFF)
-option(NANOARROW_TESTING "Build testng extension" OFF)
+option(NANOARROW_TESTING "Build testing extension" OFF)
 option(NANOARROW_DEVICE_WITH_METAL "Build Apple metal libraries" OFF)
 option(NANOARROW_DEVICE_WITH_CUDA "Build CUDA libraries" OFF)
 
@@ -151,16 +151,32 @@ if(NOT NANOARROW_BUNDLE)
 
   list(APPEND NANOARROW_INSTALL_HEADERS
        "${CMAKE_CURRENT_BINARY_DIR}/src/nanoarrow/nanoarrow_config.h")
-endif()
 
-# Add the nanoarrow library target
-add_library(nanoarrow ${NANOARROW_BUILD_SOURCES})
+  install(FILES src/nanoarrow/hpp/array_stream.hpp
+                src/nanoarrow/hpp/buffer.hpp
+                src/nanoarrow/hpp/exception.hpp
+                src/nanoarrow/hpp/operators.hpp
+                src/nanoarrow/hpp/unique.hpp
+                src/nanoarrow/hpp/view.hpp
+          DESTINATION include/nanoarrow/hpp)
+endif()
 
-target_include_directories(nanoarrow
-                           PUBLIC 
$<BUILD_INTERFACE:${NANOARROW_BUILD_INCLUDE_DIR}>
-                                  
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/src>
-                                  $<INSTALL_INTERFACE:include>)
+# Add the nanoarrow library targets
+add_library(nanoarrow_static STATIC ${NANOARROW_BUILD_SOURCES})
+add_library(nanoarrow_shared SHARED ${NANOARROW_BUILD_SOURCES})
 install(FILES ${NANOARROW_INSTALL_HEADERS} DESTINATION include/nanoarrow)
+if(BUILD_SHARED_LIBS)
+  add_library(nanoarrow::nanoarrow ALIAS nanoarrow_shared)
+else()
+  add_library(nanoarrow::nanoarrow ALIAS nanoarrow_static)
+endif()
+
+foreach(target "nanoarrow_static" "nanoarrow_shared")
+  target_include_directories(${target}
+                             PUBLIC 
$<BUILD_INTERFACE:${NANOARROW_BUILD_INCLUDE_DIR}>
+                                    
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/src>
+                                    $<INSTALL_INTERFACE:include>)
+endforeach()
 
 if(NANOARROW_IPC)
   # Add the flatcc (runtime) dependency
@@ -189,6 +205,7 @@ if(NANOARROW_IPC)
     target_include_directories(flatccrt
                                PUBLIC 
$<BUILD_INTERFACE:${NANOARROW_FLATCC_INCLUDE_DIR}>
                                       $<INSTALL_INTERFACE:include>)
+    set_target_properties(flatccrt PROPERTIES POSITION_INDEPENDENT_CODE ON)
     install(TARGETS flatccrt
             EXPORT nanoarrow-exports
             RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
@@ -235,30 +252,33 @@ if(NANOARROW_IPC)
         src/nanoarrow/ipc/writer.c)
   endif()
 
-  add_library(nanoarrow_ipc ${NANOARROW_IPC_BUILD_SOURCES})
-  target_compile_definitions(nanoarrow_ipc PRIVATE 
${NANOARROW_IPC_EXTRA_FLAGS})
-  target_link_libraries(nanoarrow_ipc
+  add_library(nanoarrow_ipc_static STATIC ${NANOARROW_IPC_BUILD_SOURCES})
+  target_link_libraries(nanoarrow_ipc_static
                         PRIVATE flatccrt ${NANOARROW_IPC_EXTRA_LIBS}
-                        PUBLIC nanoarrow nanoarrow_coverage_config)
-  target_include_directories(nanoarrow_ipc
-                             PUBLIC 
$<BUILD_INTERFACE:${NANOARROW_BUILD_INCLUDE_DIR}>
-                                    
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/src>
-                                    
$<BUILD_INTERFACE:${NANOARROW_IPC_FLATCC_INCLUDE_DIR}>
-                                    $<INSTALL_INTERFACE:include>)
+                        PUBLIC nanoarrow_static nanoarrow_coverage_config)
+
+  add_library(nanoarrow_ipc_shared SHARED ${NANOARROW_IPC_BUILD_SOURCES})
+  target_link_libraries(nanoarrow_ipc_shared
+                        PRIVATE flatccrt ${NANOARROW_IPC_EXTRA_LIBS}
+                        PUBLIC nanoarrow_shared nanoarrow_coverage_config)
 
-  install(TARGETS nanoarrow_ipc
-          RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
-          LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
-          ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
   install(FILES src/nanoarrow/nanoarrow_ipc.h src/nanoarrow/nanoarrow_ipc.hpp
                 src/nanoarrow/ipc/flatcc_generated.h DESTINATION 
include/nanoarrow)
-  install(FILES src/nanoarrow/hpp/array_stream.hpp
-                src/nanoarrow/hpp/buffer.hpp
-                src/nanoarrow/hpp/exception.hpp
-                src/nanoarrow/hpp/operators.hpp
-                src/nanoarrow/hpp/unique.hpp
-                src/nanoarrow/hpp/view.hpp
-          DESTINATION include/nanoarrow/hpp)
+  if(BUILD_SHARED_LIBS)
+    add_library(nanoarrow::nanoarrow_ipc ALIAS nanoarrow_ipc_shared)
+  else()
+    add_library(nanoarrow::nanoarrow_ipc ALIAS nanoarrow_ipc_static)
+  endif()
+
+  foreach(target "nanoarrow_ipc_static" "nanoarrow_ipc_shared")
+    target_compile_definitions(${target} PRIVATE ${NANOARROW_IPC_EXTRA_FLAGS})
+    target_include_directories(${target}
+                               PUBLIC 
$<BUILD_INTERFACE:${NANOARROW_BUILD_INCLUDE_DIR}>
+                                      
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/src>
+                                      
$<BUILD_INTERFACE:${NANOARROW_IPC_FLATCC_INCLUDE_DIR}>
+                                      $<INSTALL_INTERFACE:include>)
+  endforeach()
+
 endif()
 
 if(NANOARROW_IPC AND (NANOARROW_BUILD_INTEGRATION_TESTS OR 
NANOARROW_BUILD_TESTS))
@@ -272,7 +292,7 @@ if(NANOARROW_IPC AND (NANOARROW_BUILD_INTEGRATION_TESTS OR 
NANOARROW_BUILD_TESTS
                                     
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/src>
                                     $<INSTALL_INTERFACE:include>)
   target_link_libraries(nanoarrow_ipc_integration
-                        PRIVATE nanoarrow_testing nanoarrow_ipc flatccrt
+                        PRIVATE nanoarrow_testing_static nanoarrow_ipc_static 
flatccrt
                                 nanoarrow_coverage_config)
 endif()
 
@@ -305,7 +325,7 @@ if(NANOARROW_DEVICE)
     message(STATUS "CoreFoundation framework found at 
'${QUARTZ_CORE_LIBRARY}'")
 
     set(NANOARROW_DEVICE_INCLUDE_METAL "${CMAKE_BINARY_DIR}/metal-cpp")
-    add_library(nanoarrow_metal_impl src/nanoarrow/device/metal_impl.cc)
+    add_library(nanoarrow_metal_impl STATIC src/nanoarrow/device/metal_impl.cc)
     target_link_libraries(nanoarrow_metal_impl
                           PRIVATE ${METAL_LIBRARY} ${FOUNDATION_LIBRARY}
                                   ${QUARTZ_CORE_LIBRARY})
@@ -335,25 +355,40 @@ if(NANOARROW_DEVICE)
                                        ${NANOARROW_DEVICE_SOURCES_CUDA})
   endif()
 
-  add_library(nanoarrow_device ${NANOARROW_DEVICE_BUILD_SOURCES}
-                               ${NANOARROW_DEVICE_SOURCES_METAL})
-
-  target_include_directories(nanoarrow_device
-                             PUBLIC 
$<BUILD_INTERFACE:${NANOARROW_BUILD_INCLUDE_DIR}>
-                                    
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/src>
-                                    
$<BUILD_INTERFACE:${NANOARROW_DEVICE_INCLUDE_METAL}>
-                                    $<INSTALL_INTERFACE:include>)
+  add_library(nanoarrow_device_static STATIC ${NANOARROW_DEVICE_BUILD_SOURCES}
+                                             ${NANOARROW_DEVICE_SOURCES_METAL})
+  target_link_libraries(nanoarrow_device_static
+                        PRIVATE ${NANOARROW_DEVICE_LIBS_CUDA}
+                                ${NANOARROW_DEVICE_LIBS_METAL}
+                        PUBLIC nanoarrow_static nanoarrow_coverage_config)
 
-  target_compile_definitions(nanoarrow_device PRIVATE 
${NANOARROW_DEVICE_DEFS_METAL}
-                                                      
${NANOARROW_DEVICE_DEFS_CUDA})
-  target_link_libraries(nanoarrow_device
+  add_library(nanoarrow_device_shared SHARED ${NANOARROW_DEVICE_BUILD_SOURCES}
+                                             ${NANOARROW_DEVICE_SOURCES_METAL})
+  target_link_libraries(nanoarrow_device_shared
                         PRIVATE ${NANOARROW_DEVICE_LIBS_CUDA}
                                 ${NANOARROW_DEVICE_LIBS_METAL}
-                        PUBLIC nanoarrow nanoarrow_coverage_config)
+                        PUBLIC nanoarrow_shared nanoarrow_coverage_config)
 
-  install(TARGETS nanoarrow_device DESTINATION lib)
   install(FILES src/nanoarrow/nanoarrow_device.h 
src/nanoarrow/nanoarrow_device.hpp
           DESTINATION include/nanoarrow)
+  if(BUILD_SHARED_LIBS)
+    add_library(nanoarrow::nanoarrow_device ALIAS nanoarrow_device_shared)
+  else()
+    add_library(nanoarrow::nanoarrow_device ALIAS nanoarrow_device_static)
+  endif()
+
+  foreach(target "nanoarrow_device_static" "nanoarrow_device_shared")
+    target_include_directories(${target}
+                               PUBLIC 
$<BUILD_INTERFACE:${NANOARROW_BUILD_INCLUDE_DIR}>
+                                      
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/src>
+                                      
$<BUILD_INTERFACE:${NANOARROW_DEVICE_INCLUDE_METAL}>
+                                      $<INSTALL_INTERFACE:include>)
+
+    target_compile_definitions(${target} PRIVATE ${NANOARROW_DEVICE_DEFS_METAL}
+                                                 ${NANOARROW_DEVICE_DEFS_CUDA})
+
+  endforeach()
+
 endif()
 
 if(NANOARROW_TESTING
@@ -375,38 +410,59 @@ if(NANOARROW_TESTING
           LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
           ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
 
-  add_library(nanoarrow_testing src/nanoarrow/testing/testing.cc)
-  target_include_directories(nanoarrow_testing
-                             PUBLIC 
$<BUILD_INTERFACE:${NANOARROW_BUILD_INCLUDE_DIR}>
-                                    
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/src>
-                                    $<INSTALL_INTERFACE:include>)
-  target_link_libraries(nanoarrow_testing
+  add_library(nanoarrow_testing_static STATIC src/nanoarrow/testing/testing.cc)
+  target_link_libraries(nanoarrow_testing_static
+                        PRIVATE nlohmann_json::nlohmann_json
+                        PUBLIC nanoarrow_static nanoarrow_coverage_config)
+
+  add_library(nanoarrow_testing_shared SHARED src/nanoarrow/testing/testing.cc)
+  target_link_libraries(nanoarrow_testing_shared
                         PRIVATE nlohmann_json::nlohmann_json
-                        PUBLIC nanoarrow nanoarrow_coverage_config)
-  set_target_properties(nanoarrow_testing PROPERTIES POSITION_INDEPENDENT_CODE 
ON)
+                        PUBLIC nanoarrow_shared nanoarrow_coverage_config)
+
   install(FILES src/nanoarrow/nanoarrow_testing.hpp DESTINATION 
include/nanoarrow)
+  if(BUILD_SHARED_LIBS)
+    add_library(nanoarrow::nanoarrow_testing ALIAS nanoarrow_testing_shared)
+  else()
+    add_library(nanoarrow::nanoarrow_testing ALIAS nanoarrow_testing_static)
+  endif()
+
+  foreach(target "nanoarrow_testing_static" "nanoarrow_testing_shared")
+    target_include_directories(${target}
+                               PUBLIC 
$<BUILD_INTERFACE:${NANOARROW_BUILD_INCLUDE_DIR}>
+                                      
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/src>
+                                      $<INSTALL_INTERFACE:include>)
+
+    set_target_properties(${target} PROPERTIES POSITION_INDEPENDENT_CODE ON)
+  endforeach()
 endif()
 
 # Always build integration test if building tests
 if(NANOARROW_BUILD_TESTS OR NANOARROW_BUILD_INTEGRATION_TESTS)
-  set_target_properties(nanoarrow PROPERTIES POSITION_INDEPENDENT_CODE ON)
+  set_target_properties(nanoarrow_static PROPERTIES POSITION_INDEPENDENT_CODE 
ON)
   add_library(nanoarrow_c_data_integration SHARED
               src/nanoarrow/integration/c_data_integration.cc)
   target_compile_definitions(nanoarrow_c_data_integration PRIVATE 
NANOARROW_BUILD_DLL
                                                                   
NANOARROW_EXPORT_DLL)
+  target_compile_definitions(nanoarrow_c_data_integration PUBLIC 
NANOARROW_BUILD_DLL)
+
   target_include_directories(nanoarrow_c_data_integration
                              PUBLIC 
$<BUILD_INTERFACE:${NANOARROW_BUILD_INCLUDE_DIR}>
                                     
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/src>
                                     $<INSTALL_INTERFACE:include>)
-  target_link_libraries(nanoarrow_c_data_integration PRIVATE nanoarrow_testing)
+  target_link_libraries(nanoarrow_c_data_integration PRIVATE 
nanoarrow_testing_static)
 endif()
 
 # Common configuration for all targets
 foreach(target
-        nanoarrow
-        nanoarrow_ipc
-        nanoarrow_device
-        nanoarrow_testing
+        nanoarrow_static
+        nanoarrow_shared
+        nanoarrow_ipc_static
+        nanoarrow_ipc_shared
+        nanoarrow_device_static
+        nanoarrow_device_shared
+        nanoarrow_testing_static
+        nanoarrow_testing_shared
         nanoarrow_c_data_integration
         nanoarrow_ipc_json_integration)
   if(TARGET ${target})
@@ -416,6 +472,14 @@ foreach(target
     # Ensure NANOARROW_DEBUG is defined for debug builds
     target_compile_definitions(${target} PUBLIC 
"$<$<CONFIG:Debug>:NANOARROW_DEBUG>")
 
+    # Ensure NANOARROW_DLL is set when building and linking to the library
+    get_target_property(target_type ${target} TYPE)
+    if(target_type STREQUAL "SHARED_LIBRARY")
+      target_compile_definitions(${target} PUBLIC NANOARROW_BUILD_DLL)
+      target_compile_definitions(${target} PRIVATE NANOARROW_BUILD_DLL
+                                                   NANOARROW_EXPORT_DLL)
+    endif()
+
     # Ensure target is added to nanoarrow-exports
     install(TARGETS ${target}
             EXPORT nanoarrow-exports
@@ -457,6 +521,18 @@ if(NANOARROW_BUILD_TESTS)
   )
   include(CTest)
 
+  if(NANOARROW_TEST_LINKAGE_SHARED)
+    set(NANOARROW_TEST_LIB nanoarrow_shared)
+    set(NANOARROW_TEST_IPC_LIB nanoarrow_ipc_shared)
+    set(NANOARROW_TEST_DEVICE_LIB nanoarrow_device_shared)
+    set(NANOARROW_TEST_TESTING_LIB nanoarrow_testing_shared)
+  else()
+    set(NANOARROW_TEST_LIB nanoarrow_static)
+    set(NANOARROW_TEST_IPC_LIB nanoarrow_ipc_static)
+    set(NANOARROW_TEST_DEVICE_LIB nanoarrow_device_static)
+    set(NANOARROW_TEST_TESTING_LIB nanoarrow_testing_static)
+  endif()
+
   if(NANOARROW_BUILD_TESTS_WITH_ARROW)
     find_package(Arrow REQUIRED)
     message(STATUS "Arrow version: ${ARROW_VERSION}")
@@ -499,57 +575,58 @@ if(NANOARROW_BUILD_TESTS)
   add_executable(hpp_view src/nanoarrow/hpp/view_test.cc)
 
   target_link_libraries(utils_test
-                        nanoarrow_testing
+                        ${NANOARROW_TEST_TESTING_LIB}
                         gtest_main
                         gmock_main
                         ${NANOARROW_ARROW_TARGET}
                         nanoarrow_coverage_config)
-  target_link_libraries(buffer_test nanoarrow gtest_main 
nanoarrow_coverage_config)
+  target_link_libraries(buffer_test ${NANOARROW_TEST_LIB} gtest_main
+                        nanoarrow_coverage_config)
   target_link_libraries(array_test
-                        nanoarrow
+                        ${NANOARROW_TEST_LIB}
                         gtest_main
                         gmock_main
                         ${NANOARROW_ARROW_TARGET}
                         nanoarrow_coverage_config)
   target_link_libraries(schema_test
-                        nanoarrow
+                        ${NANOARROW_TEST_LIB}
                         gtest_main
                         ${NANOARROW_ARROW_TARGET}
                         nanoarrow_coverage_config)
   target_link_libraries(array_stream_test
-                        nanoarrow
+                        ${NANOARROW_TEST_LIB}
                         gtest_main
                         gmock_main
                         nanoarrow_coverage_config)
-  target_link_libraries(nanoarrow_testing_test nanoarrow_testing gtest_main
+  target_link_libraries(nanoarrow_testing_test ${NANOARROW_TEST_TESTING_LIB} 
gtest_main
                         nanoarrow_coverage_config)
   target_link_libraries(c_data_integration_test
-                        nanoarrow
+                        ${NANOARROW_TEST_LIB}
                         nanoarrow_c_data_integration
                         gtest_main
                         nanoarrow_coverage_config)
   target_link_libraries(hpp_array_stream
-                        nanoarrow
+                        ${NANOARROW_TEST_LIB}
                         gtest_main
                         gmock_main
                         nanoarrow_coverage_config)
   target_link_libraries(hpp_buffer
-                        nanoarrow
+                        ${NANOARROW_TEST_LIB}
                         gtest_main
                         gmock_main
                         nanoarrow_coverage_config)
   target_link_libraries(hpp_exception
-                        nanoarrow
+                        ${NANOARROW_TEST_LIB}
                         gtest_main
                         gmock_main
                         nanoarrow_coverage_config)
   target_link_libraries(hpp_unique
-                        nanoarrow
+                        ${NANOARROW_TEST_LIB}
                         gtest_main
                         gmock_main
                         nanoarrow_coverage_config)
   target_link_libraries(hpp_view
-                        nanoarrow
+                        ${NANOARROW_TEST_LIB}
                         gtest_main
                         gmock_main
                         nanoarrow_coverage_config)
@@ -593,7 +670,6 @@ if(NANOARROW_BUILD_TESTS)
   gtest_discover_tests(hpp_view)
 
   if(NANOARROW_IPC)
-
     # zlib to decode gzipped integration testing JSON files
     # We don't use Arrow C++ for this because building Arrow C++ with zlib
     # is not trivial on Windows.
@@ -618,8 +694,7 @@ if(NANOARROW_BUILD_TESTS)
       add_executable(nanoarrow_ipc_${name}_test 
src/nanoarrow/ipc/${name}_test.cc)
 
       target_link_libraries(nanoarrow_ipc_${name}_test
-                            nanoarrow_ipc
-                            nanoarrow
+                            ${NANOARROW_TEST_IPC_LIB}
                             ${NANOARROW_ARROW_TARGET}
                             gtest_main
                             gmock_main
@@ -641,8 +716,8 @@ if(NANOARROW_BUILD_TESTS)
       gtest_discover_tests(nanoarrow_ipc_${name}_test)
     endforeach()
 
-    target_link_libraries(nanoarrow_ipc_files_test nanoarrow_testing ZLIB::ZLIB
-                          nanoarrow_coverage_config)
+    target_link_libraries(nanoarrow_ipc_files_test 
${NANOARROW_TEST_TESTING_LIB}
+                          ZLIB::ZLIB nanoarrow_coverage_config)
   endif()
 
   if(NANOARROW_DEVICE)
@@ -650,16 +725,10 @@ if(NANOARROW_BUILD_TESTS)
     add_executable(nanoarrow_device_test src/nanoarrow/device/device_test.cc)
     add_executable(nanoarrow_device_hpp_test 
src/nanoarrow/device/device_hpp_test.cc)
 
-    target_link_libraries(nanoarrow_device_test
-                          nanoarrow_device
-                          nanoarrow
-                          gtest_main
-                          nanoarrow_coverage_config)
-    target_link_libraries(nanoarrow_device_hpp_test
-                          nanoarrow_device
-                          nanoarrow
-                          gtest_main
+    target_link_libraries(nanoarrow_device_test ${NANOARROW_TEST_DEVICE_LIB} 
gtest_main
                           nanoarrow_coverage_config)
+    target_link_libraries(nanoarrow_device_hpp_test 
${NANOARROW_TEST_DEVICE_LIB}
+                          gtest_main nanoarrow_coverage_config)
 
     if(Arrow_FOUND)
       target_compile_definitions(nanoarrow_device_test
@@ -706,7 +775,7 @@ endif()
 if(NANOARROW_BUILD_APPS)
   if(NANOARROW_IPC)
     add_executable(dump_stream src/apps/dump_stream.c)
-    target_link_libraries(dump_stream nanoarrow_ipc nanoarrow)
+    target_link_libraries(dump_stream nanoarrow_ipc_static)
   endif()
 endif()
 
diff --git a/cmake/config.cmake.in b/cmake/config.cmake.in
index 021dc313..ba5f5e3b 100644
--- a/cmake/config.cmake.in
+++ b/cmake/config.cmake.in
@@ -23,6 +23,16 @@ cmake_minimum_required(VERSION 
@CMAKE_MINIMUM_REQUIRED_VERSION@)
 include("${CMAKE_CURRENT_LIST_DIR}/nanoarrow-targets.cmake" REQUIRED)
 include("${CMAKE_CURRENT_LIST_DIR}/nanoarrow-config-version.cmake" REQUIRED)
 
+foreach(target nanoarrow nanoarrow_ipc nanoarrow_device nanoarrow_testing)
+  if(TARGET nanoarrow::${target}_static)
+    if(BUILD_SHARED_LIBS)
+      add_library(nanoarrow::${target} ALIAS nanoarrow::${target}_shared)
+    else()
+      add_library(nanoarrow::${target} ALIAS nanoarrow::${target}_static)
+    endif()
+  endif()
+endforeach()
+
 set(${CMAKE_FIND_PACKAGE_NAME}_CONFIG "${CMAKE_CURRENT_LIST_FILE}")
 include(FindPackageHandleStandardArgs)
 find_package_handle_standard_args(${CMAKE_FIND_PACKAGE_NAME} CONFIG_MODE)
diff --git a/docs/source/getting-started/cpp.md 
b/docs/source/getting-started/cpp.md
index 427d0cfb..17c87912 100644
--- a/docs/source/getting-started/cpp.md
+++ b/docs/source/getting-started/cpp.md
@@ -303,7 +303,7 @@ FetchContent_Declare(
 FetchContent_MakeAvailable(nanoarrow)
 
 add_library(linesplitter linesplitter.cc)
-target_link_libraries(linesplitter PRIVATE nanoarrow)
+target_link_libraries(linesplitter PRIVATE nanoarrow_static)
 ```
 
 After saving `CMakeLists.txt`, you may have to close and re-open the 
`linesplitter`
diff --git a/examples/cmake-ipc/CMakeLists.txt 
b/examples/cmake-ipc/CMakeLists.txt
index 2ae64c88..a0458152 100644
--- a/examples/cmake-ipc/CMakeLists.txt
+++ b/examples/cmake-ipc/CMakeLists.txt
@@ -33,7 +33,7 @@ fetchcontent_declare(# The name 'nanoarrow' is important 
here: it allows the IPC
                      # See examples/cmake-minimal for how to specify a GIT 
repository or URL
                      SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
 fetchcontent_makeavailable(nanoarrow)
-target_compile_options(nanoarrow PUBLIC -DNANOARROW_IPC=ON)
+target_compile_options(nanoarrow_static PUBLIC -DNANOARROW_IPC=ON)
 unset(NANOARROW_IPC)
 
 # Add the library and link it against nanoarrow and nanoarrow_ipc
@@ -45,8 +45,8 @@ add_library(example_cmake_ipc_library src/library.c)
 # Always use PRIVATE to hide nanoarrow's headers from a
 # target that in turn uses your library.
 target_link_libraries(example_cmake_ipc_library
-                      PRIVATE nanoarrow_ipc
-                      PRIVATE nanoarrow)
+                      PRIVATE nanoarrow_ipc_static
+                      PRIVATE nanoarrow_static)
 
 # Add the executable and link it against the library
 add_executable(example_cmake_ipc_app src/app.c)
diff --git a/examples/cmake-minimal/CMakeLists.txt 
b/examples/cmake-minimal/CMakeLists.txt
index 6c064604..a74d6b91 100644
--- a/examples/cmake-minimal/CMakeLists.txt
+++ b/examples/cmake-minimal/CMakeLists.txt
@@ -46,7 +46,7 @@ add_library(example_cmake_minimal_library src/library.c)
 
 # Always use PRIVATE when linking to nanoarrow to hide nanoarrow's headers 
from a
 # target that in turn uses your library.
-target_link_libraries(example_cmake_minimal_library PRIVATE nanoarrow)
+target_link_libraries(example_cmake_minimal_library PRIVATE nanoarrow_static)
 
 # Add the executable and link it against the library
 add_executable(example_cmake_minimal_app src/app.c)
diff --git a/examples/cmake-scenarios/CMakeLists.txt 
b/examples/cmake-scenarios/CMakeLists.txt
index 3a98a081..57379967 100644
--- a/examples/cmake-scenarios/CMakeLists.txt
+++ b/examples/cmake-scenarios/CMakeLists.txt
@@ -52,8 +52,23 @@ else()
 endif()
 
 add_executable(minimal_cpp_app src/app.cpp)
-target_link_libraries(minimal_cpp_app
-                      nanoarrow::nanoarrow
-                      nanoarrow::nanoarrow_device
-                      nanoarrow::nanoarrow_ipc
-                      nanoarrow::nanoarrow_testing)
+
+if(TEST_BUILD_TYPE STREQUAL "static")
+  target_link_libraries(minimal_cpp_app
+                        nanoarrow::nanoarrow_static
+                        nanoarrow::nanoarrow_device_static
+                        nanoarrow::nanoarrow_ipc_static
+                        nanoarrow::nanoarrow_testing_static)
+elseif(TEST_BUILD_TYPE STREQUAL "shared")
+  target_link_libraries(minimal_cpp_app
+                        nanoarrow::nanoarrow_shared
+                        nanoarrow::nanoarrow_device_shared
+                        nanoarrow::nanoarrow_ipc_shared
+                        nanoarrow::nanoarrow_testing_shared)
+else()
+  target_link_libraries(minimal_cpp_app
+                        nanoarrow::nanoarrow
+                        nanoarrow::nanoarrow_device
+                        nanoarrow::nanoarrow_ipc
+                        nanoarrow::nanoarrow_testing)
+endif()
diff --git a/examples/cmake-scenarios/build.sh 
b/examples/cmake-scenarios/build.sh
index 47b5592e..6a0fdfba 100755
--- a/examples/cmake-scenarios/build.sh
+++ b/examples/cmake-scenarios/build.sh
@@ -17,34 +17,40 @@
 # specific language governing permissions and limitations
 # under the License.
 
-set -exuo pipefail
+if [ -z "${EXTRA_CMAKE_CONFIGURE}" ]; then
+    EXTRA_CMAKE_CONFIGURE=""
+fi
+
+if [ -z "${EXTRA_CMAKE_INSTALL}" ]; then
+    EXTRA_CMAKE_INSTALL=""
+fi
 
-# Build nanoarrow statically.
-cmake -S ../.. -B scratch/nanoarrow_build_static/ \
-    -DCMAKE_INSTALL_PREFIX=scratch/nanoarrow_install_static/ \
-    -DNANOARROW_IPC=ON -DNANOARROW_DEVICE=ON -DNANOARROW_TESTING=ON
-cmake --build scratch/nanoarrow_build_static/
-cmake --install scratch/nanoarrow_build_static/
+set -exuo pipefail
 
-# Build nanoarrow dynamically.
-cmake -S ../.. -B scratch/nanoarrow_build_shared/ \
-    -DCMAKE_INSTALL_PREFIX=scratch/nanoarrow_install_shared/ \
-    -DBUILD_SHARED_LIBS=ON \
+# Build nanoarrow
+cmake -S ../.. -B scratch/nanoarrow_build/ \
+    -DCMAKE_INSTALL_PREFIX=scratch/nanoarrow_install/ \
     -DNANOARROW_IPC=ON -DNANOARROW_DEVICE=ON -DNANOARROW_TESTING=ON \
-    -DCMAKE_POSITION_INDEPENDENT_CODE=ON
-cmake --build scratch/nanoarrow_build_shared/
-cmake --install scratch/nanoarrow_build_shared/
+    $EXTRA_CMAKE_CONFIGURE
+cmake --build scratch/nanoarrow_build/
+cmake --install scratch/nanoarrow_build/ $EXTRA_CMAKE_INSTALL
 
-for nanoarrow_build_type in static shared; do
+for nanoarrow_build_type in static shared auto; do
     # Build the project against the built nanoarrow.
-    cmake -S . -B scratch/build_${nanoarrow_build_type}/ 
-Dnanoarrow_ROOT=scratch/nanoarrow_build_${nanoarrow_build_type}/
+    cmake -S . -B scratch/build_${nanoarrow_build_type}/ \
+        -Dnanoarrow_ROOT=scratch/nanoarrow_build \
+        -DTEST_BUILD_TYPE=${nanoarrow_build_type}
     cmake --build scratch/build_${nanoarrow_build_type}/
 
     # Build the project against the installed nanoarrow.
-    cmake -S . -B scratch/build_against_install_${nanoarrow_build_type}/ 
-Dnanoarrow_ROOT=scratch/nanoarrow_install_${nanoarrow_build_type}/
+    cmake -S . -B scratch/build_against_install_${nanoarrow_build_type}/ \
+        -Dnanoarrow_ROOT=scratch/nanoarrow_install \
+        -DTEST_BUILD_TYPE=${nanoarrow_build_type}
     cmake --build scratch/build_against_install_${nanoarrow_build_type}/
 
     # Now try using FetchContent to get nanoarrow from remote.
-    cmake -S . -B scratch/build_against_fetched_${nanoarrow_build_type}/ 
-DFIND_NANOARROW=OFF
+    cmake -S . -B scratch/build_against_fetched_${nanoarrow_build_type}/ \
+        -DFIND_NANOARROW=OFF \
+        -DTEST_BUILD_TYPE=${nanoarrow_build_type}
     cmake --build scratch/build_against_fetched_${nanoarrow_build_type}/
 done
diff --git a/examples/cmake-scenarios/run.sh b/examples/cmake-scenarios/run.sh
new file mode 100755
index 00000000..c8e8aa8c
--- /dev/null
+++ b/examples/cmake-scenarios/run.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+set -exuo pipefail
+
+# For Windows, the PATH needs to be set for it to be able to find the right 
DLLs
+WIN_DLL_NANOARROW_FETCHED="$(pwd)/scratch/build_against_fetched_shared/_deps/nanoarrow-build/Debug"
+WIN_DLL_NANOARROW_BUILT="$(pwd)/scratch/nanoarrow_build/Debug"
+WIN_DLL_NANOARROW_INSTALLED="$(pwd)/scratch/nanoarrow_install/bin"
+
+for dir in scratch/build*; do
+    # Special cases where we have to set PATH on Windows
+    if [ "${dir}" = "scratch/build_against_fetched_shared" ] && [ "${OSTYPE}" 
= "msys" ]; then
+        PATH="${PATH}:${WIN_DLL_NANOARROW_FETCHED}"  
./${dir}/Debug/minimal_cpp_app
+    elif [ "${dir}" = "scratch/build_shared" ] && [ "${OSTYPE}" = "msys" ]; 
then
+        PATH="${PATH}:${WIN_DLL_NANOARROW_BUILT}" 
./${dir}/Debug/minimal_cpp_app
+    elif [ "${dir}" = "scratch/build_against_install_shared" ] && [ 
"${OSTYPE}" = "msys" ]; then
+        PATH="${PATH}:${WIN_DLL_NANOARROW_INSTALLED}" 
./${dir}/Debug/minimal_cpp_app
+    elif [ "${OSTYPE}" = "msys" ]; then
+        ./${dir}/Debug/minimal_cpp_app
+    else
+        ./${dir}/minimal_cpp_app
+    fi
+done
+
+echo "Success!"
diff --git a/examples/linesplitter/CMakeLists.txt 
b/examples/linesplitter/CMakeLists.txt
index 29d3a74b..4faa3f54 100644
--- a/examples/linesplitter/CMakeLists.txt
+++ b/examples/linesplitter/CMakeLists.txt
@@ -39,8 +39,11 @@ fetchcontent_declare(nanoarrow
 fetchcontent_makeavailable(nanoarrow)
 
 add_library(linesplitter linesplitter.cc)
-target_link_libraries(linesplitter PRIVATE nanoarrow)
+target_link_libraries(linesplitter PRIVATE nanoarrow_static)
 
+set(gtest_force_shared_crt
+    ON
+    CACHE BOOL "" FORCE)
 fetchcontent_declare(googletest
                      URL 
https://github.com/google/googletest/archive/refs/tags/v1.13.0.zip
 )
@@ -48,7 +51,7 @@ fetchcontent_makeavailable(googletest)
 
 enable_testing()
 add_executable(linesplitter_test linesplitter_test.cc)
-target_link_libraries(linesplitter_test linesplitter nanoarrow 
GTest::gtest_main)
+target_link_libraries(linesplitter_test linesplitter nanoarrow_static 
GTest::gtest_main)
 
 include(GoogleTest)
 gtest_discover_tests(linesplitter_test)
diff --git a/python/bootstrap.py b/python/bootstrap.py
index 19729e07..798070c8 100644
--- a/python/bootstrap.py
+++ b/python/bootstrap.py
@@ -92,8 +92,8 @@ class PxdGenerator:
             r"(?P<type>struct|union|enum) (?P<name>Arrow[^ ]+) 
{(?P<body>[^}]*)}"
         )
         self.re_func_def = re.compile(
-            r"\n(static inline )?(?P<const>const )?(struct |enum )?"
-            r"(?P<return_type>[A-Za-z0-9_*]+) "
+            r"\n(static inline |NANOARROW_DLL )(?P<const>const )?(struct |enum 
)?"
+            r"(?P<return_type>[A-Za-z0-9_*]+)\s+"
             r"(?P<name>Arrow[A-Za-z0-9]+)\((?P<args>[^\)]*)\);"
         )
         self.re_tagged_type = re.compile(
diff --git a/src/nanoarrow/common/inline_types.h 
b/src/nanoarrow/common/inline_types.h
index 3c0d9579..603d566e 100644
--- a/src/nanoarrow/common/inline_types.h
+++ b/src/nanoarrow/common/inline_types.h
@@ -149,14 +149,14 @@ struct ArrowArrayStream {
   NANOARROW_RETURN_NOT_OK((x_ <= max_) ? NANOARROW_OK : EINVAL)
 
 #if defined(NANOARROW_DEBUG)
-#define _NANOARROW_RETURN_NOT_OK_WITH_ERROR_IMPL(NAME, EXPR, ERROR_PTR_EXPR, 
EXPR_STR)  \
-  do {                                                                         
         \
-    const int NAME = (EXPR);                                                   
         \
-    if (NAME) {                                                                
         \
-      ArrowErrorSet((ERROR_PTR_EXPR), "%s failed with errno %d(%s)\n* %s:%d", 
EXPR_STR, \
-                    NAME, strerror(NAME), __FILE__, __LINE__);                 
         \
-      return NAME;                                                             
         \
-    }                                                                          
         \
+#define _NANOARROW_RETURN_NOT_OK_WITH_ERROR_IMPL(NAME, EXPR, ERROR_PTR_EXPR, 
EXPR_STR) \
+  do {                                                                         
        \
+    const int NAME = (EXPR);                                                   
        \
+    if (NAME) {                                                                
        \
+      ArrowErrorSet((ERROR_PTR_EXPR), "%s failed with errno %d\n* %s:%d", 
EXPR_STR,    \
+                    NAME, __FILE__, __LINE__);                                 
        \
+      return NAME;                                                             
        \
+    }                                                                          
        \
   } while (0)
 #else
 #define _NANOARROW_RETURN_NOT_OK_WITH_ERROR_IMPL(NAME, EXPR, ERROR_PTR_EXPR, 
EXPR_STR) \
diff --git a/src/nanoarrow/device/cuda_test.cc 
b/src/nanoarrow/device/cuda_test.cc
index 4b66d43b..b273a627 100644
--- a/src/nanoarrow/device/cuda_test.cc
+++ b/src/nanoarrow/device/cuda_test.cc
@@ -24,8 +24,8 @@
 
 class CudaTemporaryContext {
  public:
-  CudaTemporaryContext(int device_id) : initialized_(false) {
-    CUresult err = cuDeviceGet(&device_, device_id);
+  CudaTemporaryContext(int64_t device_id) : initialized_(false) {
+    CUresult err = cuDeviceGet(&device_, static_cast<int>(device_id));
     if (err != CUDA_SUCCESS) {
       return;
     }
diff --git a/src/nanoarrow/hpp/view_test.cc b/src/nanoarrow/hpp/view_test.cc
index 37a9342e..282b21e1 100644
--- a/src/nanoarrow/hpp/view_test.cc
+++ b/src/nanoarrow/hpp/view_test.cc
@@ -92,7 +92,9 @@ TEST_P(BinaryViewTypeTestFixture, 
NanoarrowHppViewBinaryViewArrayAsBytesTest) {
   using namespace nanoarrow::literals;
 
   nanoarrow::UniqueArray array{};
-  const auto [offset, type] = GetParam();
+  const auto param = GetParam();
+  int64_t offset = std::get<0>(param);
+  enum ArrowType type = std::get<1>(param);
   ASSERT_EQ(ArrowArrayInitFromType(array.get(), type), NANOARROW_OK);
   ASSERT_EQ(ArrowArrayStartAppending(array.get()), NANOARROW_OK);
 
@@ -113,7 +115,7 @@ TEST_P(BinaryViewTypeTestFixture, 
NanoarrowHppViewBinaryViewArrayAsBytesTest) {
   ArrowArrayViewInitFromType(array_view.get(), type);
   ASSERT_EQ(ArrowArrayViewSetArray(array_view.get(), array.get(), nullptr), 
NANOARROW_OK);
 
-  int i = offset;
+  int64_t i = offset;
   for (auto slot : nanoarrow::ViewBinaryViewArrayAsBytes(array.get())) {
     if (i == 1 || i == 3) {
       EXPECT_EQ(slot, nanoarrow::NA);
diff --git a/src/nanoarrow/ipc/decoder.c b/src/nanoarrow/ipc/decoder.c
index 606bea81..cd5de917 100644
--- a/src/nanoarrow/ipc/decoder.c
+++ b/src/nanoarrow/ipc/decoder.c
@@ -1157,7 +1157,7 @@ ArrowErrorCode ArrowIpcDecoderVerifyFooter(struct 
ArrowIpcDecoder* decoder,
   // Check that data contains at least the entire footer (return ESPIPE to 
signal
   // that reading more data may help).
   int32_t footer_and_size_and_magic_size =
-      decoder->header_size_bytes + sizeof(int32_t) + 
strlen(NANOARROW_IPC_MAGIC);
+      decoder->header_size_bytes + sizeof(int32_t) + 
(int)strlen(NANOARROW_IPC_MAGIC);
   if (data.size_bytes < footer_and_size_and_magic_size) {
     ArrowErrorSet(error,
                   "Expected >= %d bytes of data but only %" PRId64
@@ -1316,7 +1316,7 @@ ArrowErrorCode ArrowIpcDecoderDecodeFooter(struct 
ArrowIpcDecoder* decoder,
       (struct ArrowIpcDecoderPrivate*)decoder->private_data;
 
   int32_t footer_and_size_and_magic_size =
-      decoder->header_size_bytes + sizeof(int32_t) + 
strlen(NANOARROW_IPC_MAGIC);
+      decoder->header_size_bytes + sizeof(int32_t) + 
(int)strlen(NANOARROW_IPC_MAGIC);
   const uint8_t* footer_data =
       data.data.as_uint8 + data.size_bytes - footer_and_size_and_magic_size;
   ns(Footer_table_t) footer = ns(Footer_as_root(footer_data));
diff --git a/src/nanoarrow/nanoarrow.h b/src/nanoarrow/nanoarrow.h
index 24be9684..7abb0dfc 100644
--- a/src/nanoarrow/nanoarrow.h
+++ b/src/nanoarrow/nanoarrow.h
@@ -141,6 +141,20 @@
 
 #endif
 
+#if (defined _WIN32 || defined __CYGWIN__) && defined(NANOARROW_BUILD_DLL)
+#if defined(NANOARROW_EXPORT_DLL)
+#define NANOARROW_DLL __declspec(dllexport)
+#else
+#define NANOARROW_DLL __declspec(dllimport)
+#endif  // defined(NANOARROW_EXPORT_DLL)
+#elif !defined(NANOARROW_DLL)
+#if __GNUC__ >= 4
+#define NANOARROW_DLL __attribute__((visibility("default")))
+#else
+#define NANOARROW_DLL
+#endif  // __GNUC__ >= 4
+#endif
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -164,19 +178,19 @@ extern "C" {
 /// @{
 
 /// \brief Allocate like malloc()
-void* ArrowMalloc(int64_t size);
+NANOARROW_DLL void* ArrowMalloc(int64_t size);
 
 /// \brief Reallocate like realloc()
-void* ArrowRealloc(void* ptr, int64_t size);
+NANOARROW_DLL void* ArrowRealloc(void* ptr, int64_t size);
 
 /// \brief Free a pointer allocated using ArrowMalloc() or ArrowRealloc().
-void ArrowFree(void* ptr);
+NANOARROW_DLL void ArrowFree(void* ptr);
 
 /// \brief Return the default allocator
 ///
 /// The default allocator uses ArrowMalloc(), ArrowRealloc(), and
 /// ArrowFree().
-struct ArrowBufferAllocator ArrowBufferAllocatorDefault(void);
+NANOARROW_DLL struct ArrowBufferAllocator ArrowBufferAllocatorDefault(void);
 
 /// \brief Create a custom deallocator
 ///
@@ -184,8 +198,8 @@ struct ArrowBufferAllocator 
ArrowBufferAllocatorDefault(void);
 /// attach a custom deallocator to an ArrowBuffer. This may be used to
 /// avoid copying an existing buffer that was not allocated using the
 /// infrastructure provided here (e.g., by an R or Python object).
-struct ArrowBufferAllocator 
ArrowBufferDeallocator(ArrowBufferDeallocatorCallback,
-                                                   void* private_data);
+NANOARROW_DLL struct ArrowBufferAllocator ArrowBufferDeallocator(
+    ArrowBufferDeallocatorCallback, void* private_data);
 
 /// @}
 
@@ -265,8 +279,8 @@ static inline void ArrowArrayStreamRelease(struct 
ArrowArrayStream* array_stream
 /// \brief Set the contents of an error using printf syntax.
 ///
 /// If error is NULL, this function does nothing and returns NANOARROW_OK.
-NANOARROW_CHECK_PRINTF_ATTRIBUTE int ArrowErrorSet(struct ArrowError* error,
-                                                   const char* fmt, ...);
+NANOARROW_DLL NANOARROW_CHECK_PRINTF_ATTRIBUTE int ArrowErrorSet(struct 
ArrowError* error,
+                                                                 const char* 
fmt, ...);
 
 /// @}
 
@@ -275,24 +289,25 @@ NANOARROW_CHECK_PRINTF_ATTRIBUTE int ArrowErrorSet(struct 
ArrowError* error,
 /// @{
 
 /// \brief Return a version string in the form "major.minor.patch"
-const char* ArrowNanoarrowVersion(void);
+NANOARROW_DLL const char* ArrowNanoarrowVersion(void);
 
 /// \brief Return an integer that can be used to compare versions sequentially
-int ArrowNanoarrowVersionInt(void);
+NANOARROW_DLL int ArrowNanoarrowVersionInt(void);
 
 /// \brief Initialize a description of buffer arrangements from a storage type
-void ArrowLayoutInit(struct ArrowLayout* layout, enum ArrowType storage_type);
+NANOARROW_DLL void ArrowLayoutInit(struct ArrowLayout* layout,
+                                   enum ArrowType storage_type);
 
 /// \brief Create a string view from a null-terminated string
 static inline struct ArrowStringView ArrowCharView(const char* value);
 
 /// \brief Sets the integer value of an ArrowDecimal from a string
-ArrowErrorCode ArrowDecimalSetDigits(struct ArrowDecimal* decimal,
-                                     struct ArrowStringView value);
+NANOARROW_DLL ArrowErrorCode ArrowDecimalSetDigits(struct ArrowDecimal* 
decimal,
+                                                   struct ArrowStringView 
value);
 
 /// \brief Get the integer value of an ArrowDecimal as string
-ArrowErrorCode ArrowDecimalAppendDigitsToBuffer(const struct ArrowDecimal* 
decimal,
-                                                struct ArrowBuffer* buffer);
+NANOARROW_DLL ArrowErrorCode ArrowDecimalAppendDigitsToBuffer(
+    const struct ArrowDecimal* decimal, struct ArrowBuffer* buffer);
 
 /// \brief Get the decimal value of an ArrowDecimal as a string
 ArrowErrorCode ArrowDecimalAppendStringToBuffer(const struct ArrowDecimal* 
decimal,
@@ -325,7 +340,7 @@ static inline int64_t ArrowResolveChunk64(int64_t index, 
const int64_t* offsets,
 /// Initializes the fields and release callback of schema_out. Caller
 /// is responsible for calling the schema->release callback if
 /// NANOARROW_OK is returned.
-void ArrowSchemaInit(struct ArrowSchema* schema);
+NANOARROW_DLL void ArrowSchemaInit(struct ArrowSchema* schema);
 
 /// \brief Initialize an ArrowSchema from an ArrowType
 ///
@@ -333,7 +348,8 @@ void ArrowSchemaInit(struct ArrowSchema* schema);
 /// ArrowSchemaSetType() for the common case of constructing an
 /// unparameterized type. The caller is responsible for calling the 
schema->release
 /// callback if NANOARROW_OK is returned.
-ArrowErrorCode ArrowSchemaInitFromType(struct ArrowSchema* schema, enum 
ArrowType type);
+NANOARROW_DLL ArrowErrorCode ArrowSchemaInitFromType(struct ArrowSchema* 
schema,
+                                                     enum ArrowType type);
 
 /// \brief Get a human-readable summary of a Schema
 ///
@@ -341,8 +357,8 @@ ArrowErrorCode ArrowSchemaInitFromType(struct ArrowSchema* 
schema, enum ArrowTyp
 /// and returns the number of characters required for the output if
 /// n were sufficiently large. If recursive is non-zero, the result will
 /// also include children.
-int64_t ArrowSchemaToString(const struct ArrowSchema* schema, char* out, 
int64_t n,
-                            char recursive);
+NANOARROW_DLL int64_t ArrowSchemaToString(const struct ArrowSchema* schema, 
char* out,
+                                          int64_t n, char recursive);
 
 /// \brief Set the format field of a schema from an ArrowType
 ///
@@ -352,14 +368,16 @@ int64_t ArrowSchemaToString(const struct ArrowSchema* 
schema, char* out, int64_t
 /// allocated, initialized, and named; however, the caller must
 /// ArrowSchemaSetType() on the preinitialized children. Schema must have been 
initialized
 /// using ArrowSchemaInit() or ArrowSchemaDeepCopy().
-ArrowErrorCode ArrowSchemaSetType(struct ArrowSchema* schema, enum ArrowType 
type);
+NANOARROW_DLL ArrowErrorCode ArrowSchemaSetType(struct ArrowSchema* schema,
+                                                enum ArrowType type);
 
 /// \brief Set the format field and initialize children of a struct schema
 ///
 /// The specified number of children are initialized; however, the caller is 
responsible
 /// for calling ArrowSchemaSetType() and ArrowSchemaSetName() on each child.
 /// Schema must have been initialized using ArrowSchemaInit() or 
ArrowSchemaDeepCopy().
-ArrowErrorCode ArrowSchemaSetTypeStruct(struct ArrowSchema* schema, int64_t 
n_children);
+NANOARROW_DLL ArrowErrorCode ArrowSchemaSetTypeStruct(struct ArrowSchema* 
schema,
+                                                      int64_t n_children);
 
 /// \brief Set the format field of a fixed-size schema
 ///
@@ -369,8 +387,9 @@ ArrowErrorCode ArrowSchemaSetTypeStruct(struct ArrowSchema* 
schema, int64_t n_ch
 /// allocated, initialized, and named; however, the caller must
 /// ArrowSchemaSetType() the first child. Schema must have been initialized 
using
 /// ArrowSchemaInit() or ArrowSchemaDeepCopy().
-ArrowErrorCode ArrowSchemaSetTypeFixedSize(struct ArrowSchema* schema,
-                                           enum ArrowType type, int32_t 
fixed_size);
+NANOARROW_DLL ArrowErrorCode ArrowSchemaSetTypeFixedSize(struct ArrowSchema* 
schema,
+                                                         enum ArrowType type,
+                                                         int32_t fixed_size);
 
 /// \brief Set the format field of a decimal schema
 ///
@@ -378,9 +397,10 @@ ArrowErrorCode ArrowSchemaSetTypeFixedSize(struct 
ArrowSchema* schema,
 /// NANOARROW_TYPE_DECIMAL32, NANOARROW_TYPE_DECIMAL64, 
NANOARROW_TYPE_DECIMAL128 or
 /// NANOARROW_TYPE_DECIMAL256. Schema must have been initialized using
 /// ArrowSchemaInit() or ArrowSchemaDeepCopy().
-ArrowErrorCode ArrowSchemaSetTypeDecimal(struct ArrowSchema* schema, enum 
ArrowType type,
-                                         int32_t decimal_precision,
-                                         int32_t decimal_scale);
+NANOARROW_DLL ArrowErrorCode ArrowSchemaSetTypeDecimal(struct ArrowSchema* 
schema,
+                                                       enum ArrowType type,
+                                                       int32_t 
decimal_precision,
+                                                       int32_t decimal_scale);
 
 /// \brief Set the format field of a run-end encoded schema
 ///
@@ -390,8 +410,8 @@ ArrowErrorCode ArrowSchemaSetTypeDecimal(struct 
ArrowSchema* schema, enum ArrowT
 /// The caller must call `ArrowSchemaSetTypeXXX(schema->children[1])` to
 /// set the value type. Note that when building arrays using the 
`ArrowArrayAppendXXX()`
 /// functions, the run-end encoded array's logical length must be updated 
manually.
-ArrowErrorCode ArrowSchemaSetTypeRunEndEncoded(struct ArrowSchema* schema,
-                                               enum ArrowType run_end_type);
+NANOARROW_DLL ArrowErrorCode ArrowSchemaSetTypeRunEndEncoded(struct 
ArrowSchema* schema,
+                                                             enum ArrowType 
run_end_type);
 
 /// \brief Set the format field of a time, timestamp, or duration schema
 ///
@@ -400,55 +420,60 @@ ArrowErrorCode ArrowSchemaSetTypeRunEndEncoded(struct 
ArrowSchema* schema,
 /// NANOARROW_TYPE_TIMESTAMP, or NANOARROW_TYPE_DURATION. The
 /// timezone parameter must be NULL for a non-timestamp type. Schema must have 
been
 /// initialized using ArrowSchemaInit() or ArrowSchemaDeepCopy().
-ArrowErrorCode ArrowSchemaSetTypeDateTime(struct ArrowSchema* schema, enum 
ArrowType type,
-                                          enum ArrowTimeUnit time_unit,
-                                          const char* timezone);
+NANOARROW_DLL ArrowErrorCode ArrowSchemaSetTypeDateTime(struct ArrowSchema* 
schema,
+                                                        enum ArrowType type,
+                                                        enum ArrowTimeUnit 
time_unit,
+                                                        const char* timezone);
 
 /// \brief Set the format field of a union schema
 ///
 /// Returns EINVAL for a type that is not NANOARROW_TYPE_DENSE_UNION
 /// or NANOARROW_TYPE_SPARSE_UNION. The specified number of children are
 /// allocated, and initialized.
-ArrowErrorCode ArrowSchemaSetTypeUnion(struct ArrowSchema* schema, enum 
ArrowType type,
-                                       int64_t n_children);
+NANOARROW_DLL ArrowErrorCode ArrowSchemaSetTypeUnion(struct ArrowSchema* 
schema,
+                                                     enum ArrowType type,
+                                                     int64_t n_children);
 
 /// \brief Make a (recursive) copy of a schema
 ///
 /// Allocates and copies fields of schema into schema_out.
-ArrowErrorCode ArrowSchemaDeepCopy(const struct ArrowSchema* schema,
-                                   struct ArrowSchema* schema_out);
+NANOARROW_DLL ArrowErrorCode ArrowSchemaDeepCopy(const struct ArrowSchema* 
schema,
+                                                 struct ArrowSchema* 
schema_out);
 
 /// \brief Copy format into schema->format
 ///
 /// schema must have been allocated using ArrowSchemaInitFromType() or
 /// ArrowSchemaDeepCopy().
-ArrowErrorCode ArrowSchemaSetFormat(struct ArrowSchema* schema, const char* 
format);
+NANOARROW_DLL ArrowErrorCode ArrowSchemaSetFormat(struct ArrowSchema* schema,
+                                                  const char* format);
 
 /// \brief Copy name into schema->name
 ///
 /// schema must have been allocated using ArrowSchemaInitFromType() or
 /// ArrowSchemaDeepCopy().
-ArrowErrorCode ArrowSchemaSetName(struct ArrowSchema* schema, const char* 
name);
+NANOARROW_DLL ArrowErrorCode ArrowSchemaSetName(struct ArrowSchema* schema,
+                                                const char* name);
 
 /// \brief Copy metadata into schema->metadata
 ///
 /// schema must have been allocated using ArrowSchemaInitFromType() or
 /// ArrowSchemaDeepCopy.
-ArrowErrorCode ArrowSchemaSetMetadata(struct ArrowSchema* schema, const char* 
metadata);
+NANOARROW_DLL ArrowErrorCode ArrowSchemaSetMetadata(struct ArrowSchema* schema,
+                                                    const char* metadata);
 
 /// \brief Allocate the schema->children array
 ///
 /// Includes the memory for each child struct ArrowSchema.
 /// schema must have been allocated using ArrowSchemaInitFromType() or
 /// ArrowSchemaDeepCopy().
-ArrowErrorCode ArrowSchemaAllocateChildren(struct ArrowSchema* schema,
-                                           int64_t n_children);
+NANOARROW_DLL ArrowErrorCode ArrowSchemaAllocateChildren(struct ArrowSchema* 
schema,
+                                                         int64_t n_children);
 
 /// \brief Allocate the schema->dictionary member
 ///
 /// schema must have been allocated using ArrowSchemaInitFromType() or
 /// ArrowSchemaDeepCopy().
-ArrowErrorCode ArrowSchemaAllocateDictionary(struct ArrowSchema* schema);
+NANOARROW_DLL ArrowErrorCode ArrowSchemaAllocateDictionary(struct ArrowSchema* 
schema);
 
 /// @}
 
@@ -472,49 +497,51 @@ struct ArrowMetadataReader {
 };
 
 /// \brief Initialize an ArrowMetadataReader
-ArrowErrorCode ArrowMetadataReaderInit(struct ArrowMetadataReader* reader,
-                                       const char* metadata);
+NANOARROW_DLL ArrowErrorCode ArrowMetadataReaderInit(struct 
ArrowMetadataReader* reader,
+                                                     const char* metadata);
 
 /// \brief Read the next key/value pair from an ArrowMetadataReader
-ArrowErrorCode ArrowMetadataReaderRead(struct ArrowMetadataReader* reader,
-                                       struct ArrowStringView* key_out,
-                                       struct ArrowStringView* value_out);
+NANOARROW_DLL ArrowErrorCode ArrowMetadataReaderRead(struct 
ArrowMetadataReader* reader,
+                                                     struct ArrowStringView* 
key_out,
+                                                     struct ArrowStringView* 
value_out);
 
 /// \brief The number of bytes in in a key/value metadata string
-int64_t ArrowMetadataSizeOf(const char* metadata);
+NANOARROW_DLL int64_t ArrowMetadataSizeOf(const char* metadata);
 
 /// \brief Check for a key in schema metadata
-char ArrowMetadataHasKey(const char* metadata, struct ArrowStringView key);
+NANOARROW_DLL char ArrowMetadataHasKey(const char* metadata, struct 
ArrowStringView key);
 
 /// \brief Extract a value from schema metadata
 ///
 /// If key does not exist in metadata, value_out is unmodified
-ArrowErrorCode ArrowMetadataGetValue(const char* metadata, struct 
ArrowStringView key,
-                                     struct ArrowStringView* value_out);
+NANOARROW_DLL ArrowErrorCode ArrowMetadataGetValue(const char* metadata,
+                                                   struct ArrowStringView key,
+                                                   struct ArrowStringView* 
value_out);
 
 /// \brief Initialize a builder for schema metadata from key/value pairs
 ///
 /// metadata can be an existing metadata string or NULL to initialize
 /// an empty metadata string.
-ArrowErrorCode ArrowMetadataBuilderInit(struct ArrowBuffer* buffer, const 
char* metadata);
+NANOARROW_DLL ArrowErrorCode ArrowMetadataBuilderInit(struct ArrowBuffer* 
buffer,
+                                                      const char* metadata);
 
 /// \brief Append a key/value pair to a buffer containing serialized metadata
-ArrowErrorCode ArrowMetadataBuilderAppend(struct ArrowBuffer* buffer,
-                                          struct ArrowStringView key,
-                                          struct ArrowStringView value);
+NANOARROW_DLL ArrowErrorCode ArrowMetadataBuilderAppend(struct ArrowBuffer* 
buffer,
+                                                        struct ArrowStringView 
key,
+                                                        struct ArrowStringView 
value);
 
 /// \brief Set a key/value pair to a buffer containing serialized metadata
 ///
 /// Ensures that the only entry for key in the metadata is set to value.
 /// This function maintains the existing position of (the first instance of)
 /// key if present in the data.
-ArrowErrorCode ArrowMetadataBuilderSet(struct ArrowBuffer* buffer,
-                                       struct ArrowStringView key,
-                                       struct ArrowStringView value);
+NANOARROW_DLL ArrowErrorCode ArrowMetadataBuilderSet(struct ArrowBuffer* 
buffer,
+                                                     struct ArrowStringView 
key,
+                                                     struct ArrowStringView 
value);
 
 /// \brief Remove a key from a buffer containing serialized metadata
-ArrowErrorCode ArrowMetadataBuilderRemove(struct ArrowBuffer* buffer,
-                                          struct ArrowStringView key);
+NANOARROW_DLL ArrowErrorCode ArrowMetadataBuilderRemove(struct ArrowBuffer* 
buffer,
+                                                        struct ArrowStringView 
key);
 
 /// @}
 
@@ -612,9 +639,9 @@ struct ArrowSchemaView {
 };
 
 /// \brief Initialize an ArrowSchemaView
-ArrowErrorCode ArrowSchemaViewInit(struct ArrowSchemaView* schema_view,
-                                   const struct ArrowSchema* schema,
-                                   struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowSchemaViewInit(struct ArrowSchemaView* 
schema_view,
+                                                 const struct ArrowSchema* 
schema,
+                                                 struct ArrowError* error);
 
 /// @}
 
@@ -830,24 +857,24 @@ static inline void ArrowBitmapReset(struct ArrowBitmap* 
bitmap);
 /// Initializes the fields and release callback of array. Caller
 /// is responsible for calling the array->release callback if
 /// NANOARROW_OK is returned.
-ArrowErrorCode ArrowArrayInitFromType(struct ArrowArray* array,
-                                      enum ArrowType storage_type);
+NANOARROW_DLL ArrowErrorCode ArrowArrayInitFromType(struct ArrowArray* array,
+                                                    enum ArrowType 
storage_type);
 
 /// \brief Initialize the contents of an ArrowArray from an ArrowSchema
 ///
 /// Caller is responsible for calling the array->release callback if
 /// NANOARROW_OK is returned.
-ArrowErrorCode ArrowArrayInitFromSchema(struct ArrowArray* array,
-                                        const struct ArrowSchema* schema,
-                                        struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowArrayInitFromSchema(struct ArrowArray* array,
+                                                      const struct 
ArrowSchema* schema,
+                                                      struct ArrowError* 
error);
 
 /// \brief Initialize the contents of an ArrowArray from an ArrowArrayView
 ///
 /// Caller is responsible for calling the array->release callback if
 /// NANOARROW_OK is returned.
-ArrowErrorCode ArrowArrayInitFromArrayView(struct ArrowArray* array,
-                                           const struct ArrowArrayView* 
array_view,
-                                           struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowArrayInitFromArrayView(
+    struct ArrowArray* array, const struct ArrowArrayView* array_view,
+    struct ArrowError* error);
 
 /// \brief Allocate the array->children array
 ///
@@ -855,7 +882,8 @@ ArrowErrorCode ArrowArrayInitFromArrayView(struct 
ArrowArray* array,
 /// whose members are marked as released and may be subsequently initialized
 /// with ArrowArrayInitFromType() or moved from an existing ArrowArray.
 /// schema must have been allocated using ArrowArrayInitFromType().
-ArrowErrorCode ArrowArrayAllocateChildren(struct ArrowArray* array, int64_t 
n_children);
+NANOARROW_DLL ArrowErrorCode ArrowArrayAllocateChildren(struct ArrowArray* 
array,
+                                                        int64_t n_children);
 
 /// \brief Allocate the array->dictionary member
 ///
@@ -863,18 +891,19 @@ ArrowErrorCode ArrowArrayAllocateChildren(struct 
ArrowArray* array, int64_t n_ch
 /// is marked as released and may be subsequently initialized
 /// with ArrowArrayInitFromType() or moved from an existing ArrowArray.
 /// array must have been allocated using ArrowArrayInitFromType()
-ArrowErrorCode ArrowArrayAllocateDictionary(struct ArrowArray* array);
+NANOARROW_DLL ArrowErrorCode ArrowArrayAllocateDictionary(struct ArrowArray* 
array);
 
 /// \brief Set the validity bitmap of an ArrowArray
 ///
 /// array must have been allocated using ArrowArrayInitFromType()
-void ArrowArraySetValidityBitmap(struct ArrowArray* array, struct ArrowBitmap* 
bitmap);
+NANOARROW_DLL void ArrowArraySetValidityBitmap(struct ArrowArray* array,
+                                               struct ArrowBitmap* bitmap);
 
 /// \brief Set a buffer of an ArrowArray
 ///
 /// array must have been allocated using ArrowArrayInitFromType()
-ArrowErrorCode ArrowArraySetBuffer(struct ArrowArray* array, int64_t i,
-                                   struct ArrowBuffer* buffer);
+NANOARROW_DLL ArrowErrorCode ArrowArraySetBuffer(struct ArrowArray* array, 
int64_t i,
+                                                 struct ArrowBuffer* buffer);
 
 /// \brief Get the validity bitmap of an ArrowArray
 ///
@@ -900,8 +929,8 @@ static inline ArrowErrorCode 
ArrowArrayStartAppending(struct ArrowArray* array);
 /// child array sizes for non-fixed-size arrays), recursively reserve space for
 /// additional elements. This is useful for reducing the number of 
reallocations
 /// that occur using the item-wise appenders.
-ArrowErrorCode ArrowArrayReserve(struct ArrowArray* array,
-                                 int64_t additional_size_elements);
+NANOARROW_DLL ArrowErrorCode ArrowArrayReserve(struct ArrowArray* array,
+                                               int64_t 
additional_size_elements);
 
 /// \brief Append a null value to an array
 static inline ArrowErrorCode ArrowArrayAppendNull(struct ArrowArray* array, 
int64_t n);
@@ -999,8 +1028,8 @@ static inline ArrowErrorCode ArrowArrayShrinkToFit(struct 
ArrowArray* array);
 /// into array->buffers and checks the actual size of the buffers
 /// against the expected size based on the final length.
 /// array must have been allocated using ArrowArrayInitFromType()
-ArrowErrorCode ArrowArrayFinishBuildingDefault(struct ArrowArray* array,
-                                               struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowArrayFinishBuildingDefault(struct 
ArrowArray* array,
+                                                             struct 
ArrowError* error);
 
 /// \brief Finish building an ArrowArray with explicit validation
 ///
@@ -1009,9 +1038,9 @@ ArrowErrorCode ArrowArrayFinishBuildingDefault(struct 
ArrowArray* array,
 /// buffer data access is not possible or more validation (i.e.,
 /// NANOARROW_VALIDATION_LEVEL_FULL) if buffer content was obtained from an 
untrusted or
 /// corruptible source.
-ArrowErrorCode ArrowArrayFinishBuilding(struct ArrowArray* array,
-                                        enum ArrowValidationLevel 
validation_level,
-                                        struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowArrayFinishBuilding(
+    struct ArrowArray* array, enum ArrowValidationLevel validation_level,
+    struct ArrowError* error);
 
 /// @}
 
@@ -1022,8 +1051,8 @@ ArrowErrorCode ArrowArrayFinishBuilding(struct 
ArrowArray* array,
 /// @{
 
 /// \brief Initialize the contents of an ArrowArrayView
-void ArrowArrayViewInitFromType(struct ArrowArrayView* array_view,
-                                enum ArrowType storage_type);
+NANOARROW_DLL void ArrowArrayViewInitFromType(struct ArrowArrayView* 
array_view,
+                                              enum ArrowType storage_type);
 
 /// \brief Move an ArrowArrayView
 ///
@@ -1033,32 +1062,34 @@ static inline void ArrowArrayViewMove(struct 
ArrowArrayView* src,
                                       struct ArrowArrayView* dst);
 
 /// \brief Initialize the contents of an ArrowArrayView from an ArrowSchema
-ArrowErrorCode ArrowArrayViewInitFromSchema(struct ArrowArrayView* array_view,
-                                            const struct ArrowSchema* schema,
-                                            struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode
+ArrowArrayViewInitFromSchema(struct ArrowArrayView* array_view,
+                             const struct ArrowSchema* schema, struct 
ArrowError* error);
 
 /// \brief Allocate the array_view->children array
 ///
 /// Includes the memory for each child struct ArrowArrayView
-ArrowErrorCode ArrowArrayViewAllocateChildren(struct ArrowArrayView* 
array_view,
-                                              int64_t n_children);
+NANOARROW_DLL ArrowErrorCode
+ArrowArrayViewAllocateChildren(struct ArrowArrayView* array_view, int64_t 
n_children);
 
 /// \brief Allocate array_view->dictionary
-ArrowErrorCode ArrowArrayViewAllocateDictionary(struct ArrowArrayView* 
array_view);
+NANOARROW_DLL ArrowErrorCode
+ArrowArrayViewAllocateDictionary(struct ArrowArrayView* array_view);
 
 /// \brief Set data-independent buffer sizes from length
-void ArrowArrayViewSetLength(struct ArrowArrayView* array_view, int64_t 
length);
+NANOARROW_DLL void ArrowArrayViewSetLength(struct ArrowArrayView* array_view,
+                                           int64_t length);
 
 /// \brief Set buffer sizes and data pointers from an ArrowArray
-ArrowErrorCode ArrowArrayViewSetArray(struct ArrowArrayView* array_view,
-                                      const struct ArrowArray* array,
-                                      struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowArrayViewSetArray(struct ArrowArrayView* 
array_view,
+                                                    const struct ArrowArray* 
array,
+                                                    struct ArrowError* error);
 
 /// \brief Set buffer sizes and data pointers from an ArrowArray except for 
those
 /// that require dereferencing buffer content.
-ArrowErrorCode ArrowArrayViewSetArrayMinimal(struct ArrowArrayView* array_view,
-                                             const struct ArrowArray* array,
-                                             struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode
+ArrowArrayViewSetArrayMinimal(struct ArrowArrayView* array_view,
+                              const struct ArrowArray* array, struct 
ArrowError* error);
 
 /// \brief Get the number of buffers
 ///
@@ -1110,9 +1141,9 @@ static inline int64_t 
ArrowArrayViewGetBufferElementSizeBits(
 /// and sizes otherwise, you may wish to perform checks at a different level. 
See
 /// documentation for ArrowValidationLevel for the details of checks performed
 /// at each level.
-ArrowErrorCode ArrowArrayViewValidate(struct ArrowArrayView* array_view,
-                                      enum ArrowValidationLevel 
validation_level,
-                                      struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowArrayViewValidate(
+    struct ArrowArrayView* array_view, enum ArrowValidationLevel 
validation_level,
+    struct ArrowError* error);
 
 /// \brief Compare two ArrowArrayView objects for equality
 ///
@@ -1122,13 +1153,13 @@ ArrowErrorCode ArrowArrayViewValidate(struct 
ArrowArrayView* array_view,
 /// error if error is non-NULL.
 ///
 /// Returns NANOARROW_OK if the comparison completed successfully.
-ArrowErrorCode ArrowArrayViewCompare(const struct ArrowArrayView* actual,
-                                     const struct ArrowArrayView* expected,
-                                     enum ArrowCompareLevel level, int* out,
-                                     struct ArrowError* reason);
+NANOARROW_DLL ArrowErrorCode ArrowArrayViewCompare(const struct 
ArrowArrayView* actual,
+                                                   const struct 
ArrowArrayView* expected,
+                                                   enum ArrowCompareLevel 
level, int* out,
+                                                   struct ArrowError* reason);
 
 /// \brief Reset the contents of an ArrowArrayView and frees resources
-void ArrowArrayViewReset(struct ArrowArrayView* array_view);
+NANOARROW_DLL void ArrowArrayViewReset(struct ArrowArrayView* array_view);
 
 /// \brief Check for a null element in an ArrowArrayView
 static inline int8_t ArrowArrayViewIsNull(const struct ArrowArrayView* 
array_view,
@@ -1207,8 +1238,8 @@ static inline void ArrowArrayViewGetDecimalUnsafe(const 
struct ArrowArrayView* a
 /// This function moves the ownership of schema to the array_stream. If
 /// this function returns NANOARROW_OK, the caller is responsible for
 /// releasing the ArrowArrayStream.
-ArrowErrorCode ArrowBasicArrayStreamInit(struct ArrowArrayStream* array_stream,
-                                         struct ArrowSchema* schema, int64_t 
n_arrays);
+NANOARROW_DLL ArrowErrorCode ArrowBasicArrayStreamInit(
+    struct ArrowArrayStream* array_stream, struct ArrowSchema* schema, int64_t 
n_arrays);
 
 /// \brief Set the ith ArrowArray in this ArrowArrayStream.
 ///
@@ -1217,16 +1248,16 @@ ArrowErrorCode ArrowBasicArrayStreamInit(struct 
ArrowArrayStream* array_stream,
 /// be greater than zero and less than the value of n_arrays passed in
 /// ArrowBasicArrayStreamInit(). Callers are not required to fill all
 /// n_arrays members (i.e., n_arrays is a maximum bound).
-void ArrowBasicArrayStreamSetArray(struct ArrowArrayStream* array_stream, 
int64_t i,
-                                   struct ArrowArray* array);
+NANOARROW_DLL void ArrowBasicArrayStreamSetArray(struct ArrowArrayStream* 
array_stream,
+                                                 int64_t i, struct ArrowArray* 
array);
 
 /// \brief Validate the contents of this ArrowArrayStream
 ///
 /// array_stream must have been initialized with ArrowBasicArrayStreamInit().
 /// This function uses ArrowArrayStreamInitFromSchema() and 
ArrowArrayStreamSetArray()
 /// to validate the contents of the arrays.
-ArrowErrorCode ArrowBasicArrayStreamValidate(const struct ArrowArrayStream* 
array_stream,
-                                             struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowBasicArrayStreamValidate(
+    const struct ArrowArrayStream* array_stream, struct ArrowError* error);
 
 /// @}
 
diff --git a/src/nanoarrow/nanoarrow_device.h b/src/nanoarrow/nanoarrow_device.h
index ef5d3cca..5ea0c609 100644
--- a/src/nanoarrow/nanoarrow_device.h
+++ b/src/nanoarrow/nanoarrow_device.h
@@ -167,7 +167,7 @@ static inline void ArrowDeviceArrayMove(struct 
ArrowDeviceArray* src,
 /// @{
 
 /// \brief Checks the nanoarrow runtime to make sure the run/build versions 
match
-ArrowErrorCode ArrowDeviceCheckRuntime(struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowDeviceCheckRuntime(struct ArrowError* error);
 
 struct ArrowDeviceArrayView {
   struct ArrowDevice* device;
@@ -261,10 +261,10 @@ struct ArrowDevice {
 };
 
 /// \brief Pointer to a statically-allocated CPU device singleton
-struct ArrowDevice* ArrowDeviceCpu(void);
+NANOARROW_DLL struct ArrowDevice* ArrowDeviceCpu(void);
 
 /// \brief Initialize a user-allocated device struct with a CPU device
-void ArrowDeviceInitCpu(struct ArrowDevice* device);
+NANOARROW_DLL void ArrowDeviceInitCpu(struct ArrowDevice* device);
 
 /// \brief Resolve a device pointer from a type + identifier
 ///
@@ -272,7 +272,8 @@ void ArrowDeviceInitCpu(struct ArrowDevice* device);
 /// some device types may or may not be supported. The CPU type is always 
supported.
 /// Returns NULL for device that does not exist or cannot be returned as a 
singleton.
 /// Callers must not release the pointed-to device.
-struct ArrowDevice* ArrowDeviceResolve(ArrowDeviceType device_type, int64_t 
device_id);
+NANOARROW_DLL struct ArrowDevice* ArrowDeviceResolve(ArrowDeviceType 
device_type,
+                                                     int64_t device_id);
 
 /// \brief Initialize an ArrowDeviceArray
 ///
@@ -282,10 +283,9 @@ struct ArrowDevice* ArrowDeviceResolve(ArrowDeviceType 
device_type, int64_t devi
 /// it captures the work done on stream. If NANOARROW_OK is returned, 
ownership of array
 /// and sync_event is transferred to device_array. The caller retains 
ownership of
 /// stream.
-ArrowErrorCode ArrowDeviceArrayInitAsync(struct ArrowDevice* device,
-                                         struct ArrowDeviceArray* device_array,
-                                         struct ArrowArray* array, void* 
sync_event,
-                                         void* stream);
+NANOARROW_DLL ArrowErrorCode ArrowDeviceArrayInitAsync(
+    struct ArrowDevice* device, struct ArrowDeviceArray* device_array,
+    struct ArrowArray* array, void* sync_event, void* stream);
 
 /// \brief Initialize an ArrowDeviceArray without a stream
 ///
@@ -301,7 +301,7 @@ static inline ArrowErrorCode ArrowDeviceArrayInit(struct 
ArrowDevice* device,
 /// specified device as an ArrowDeviceArrayStream. This function moves the 
ownership
 /// of array_stream to the device_array_stream. If this function returns 
NANOARROW_OK,
 /// the caller is responsible for releasing the ArrowDeviceArrayStream.
-ArrowErrorCode ArrowDeviceBasicArrayStreamInit(
+NANOARROW_DLL ArrowErrorCode ArrowDeviceBasicArrayStreamInit(
     struct ArrowDeviceArrayStream* device_array_stream,
     struct ArrowArrayStream* array_stream, struct ArrowDevice* device);
 
@@ -310,16 +310,18 @@ ArrowErrorCode ArrowDeviceBasicArrayStreamInit(
 /// Zeroes memory for the device array view struct. Callers must initialize the
 /// array_view member using nanoarrow core functions that can initialize from
 /// a type identifier or schema.
-void ArrowDeviceArrayViewInit(struct ArrowDeviceArrayView* device_array_view);
+NANOARROW_DLL void ArrowDeviceArrayViewInit(
+    struct ArrowDeviceArrayView* device_array_view);
 
 /// \brief Release the underlying ArrowArrayView
-void ArrowDeviceArrayViewReset(struct ArrowDeviceArrayView* device_array_view);
+NANOARROW_DLL void ArrowDeviceArrayViewReset(
+    struct ArrowDeviceArrayView* device_array_view);
 
 /// \brief Set minimal ArrowArrayView buffer information from a device array
 ///
 /// A thin wrapper around ArrowArrayViewSetArrayMinimal() that does not attempt
 /// to resolve buffer sizes of variable-length buffers by copying data from 
the device.
-ArrowErrorCode ArrowDeviceArrayViewSetArrayMinimal(
+NANOARROW_DLL ArrowErrorCode ArrowDeviceArrayViewSetArrayMinimal(
     struct ArrowDeviceArrayView* device_array_view, struct ArrowDeviceArray* 
device_array,
     struct ArrowError* error);
 
@@ -328,7 +330,7 @@ ArrowErrorCode ArrowDeviceArrayViewSetArrayMinimal(
 /// Runs ArrowDeviceArrayViewSetArrayMinimal() but also sets buffer sizes for
 /// variable-length buffers by copying data from the device if needed. If 
stream
 /// is provided it will be used to do any copying required to resolve buffer 
sizes.
-ArrowErrorCode ArrowDeviceArrayViewSetArrayAsync(
+NANOARROW_DLL ArrowErrorCode ArrowDeviceArrayViewSetArrayAsync(
     struct ArrowDeviceArrayView* device_array_view, struct ArrowDeviceArray* 
device_array,
     void* stream, struct ArrowError* error);
 
@@ -344,9 +346,9 @@ static inline ArrowErrorCode ArrowDeviceArrayViewSetArray(
 /// If stream is provided, it will be used to launch copies asynchronously.
 /// Note that this implies that all pointers in src will remain valid until
 /// the stream is synchronized.
-ArrowErrorCode ArrowDeviceArrayViewCopyAsync(struct ArrowDeviceArrayView* src,
-                                             struct ArrowDevice* device_dst,
-                                             struct ArrowDeviceArray* dst, 
void* stream);
+NANOARROW_DLL ArrowErrorCode ArrowDeviceArrayViewCopyAsync(
+    struct ArrowDeviceArrayView* src, struct ArrowDevice* device_dst,
+    struct ArrowDeviceArray* dst, void* stream);
 
 /// \brief Copy an ArrowDeviceArrayView to a device without a stream
 ///
@@ -361,19 +363,20 @@ static inline ArrowErrorCode 
ArrowDeviceArrayViewCopy(struct ArrowDeviceArrayVie
 /// This may result in a device array with different performance charateristics
 /// than an array that was copied. Returns ENOTSUP if a zero-copy move between 
devices is
 /// not possible.
-ArrowErrorCode ArrowDeviceArrayMoveToDevice(struct ArrowDeviceArray* src,
-                                            struct ArrowDevice* device_dst,
-                                            struct ArrowDeviceArray* dst);
+NANOARROW_DLL ArrowErrorCode ArrowDeviceArrayMoveToDevice(struct 
ArrowDeviceArray* src,
+                                                          struct ArrowDevice* 
device_dst,
+                                                          struct 
ArrowDeviceArray* dst);
 
 /// \brief Allocate a device buffer and copying existing content
 ///
 /// If stream is provided, it will be used to launch copies asynchronously.
 /// Note that this implies that src will remain valid until the stream is
 /// synchronized.
-ArrowErrorCode ArrowDeviceBufferInitAsync(struct ArrowDevice* device_src,
-                                          struct ArrowBufferView src,
-                                          struct ArrowDevice* device_dst,
-                                          struct ArrowBuffer* dst, void* 
stream);
+NANOARROW_DLL ArrowErrorCode ArrowDeviceBufferInitAsync(struct ArrowDevice* 
device_src,
+                                                        struct ArrowBufferView 
src,
+                                                        struct ArrowDevice* 
device_dst,
+                                                        struct ArrowBuffer* 
dst,
+                                                        void* stream);
 
 /// \brief Allocate a device buffer and copying existing content without a 
stream
 ///
@@ -386,20 +389,21 @@ static inline ArrowErrorCode ArrowDeviceBufferInit(struct 
ArrowDevice* device_sr
 /// \brief Move a buffer to a device without copying if possible
 ///
 /// Returns ENOTSUP if a zero-copy move between devices is not possible.
-ArrowErrorCode ArrowDeviceBufferMove(struct ArrowDevice* device_src,
-                                     struct ArrowBuffer* src,
-                                     struct ArrowDevice* device_dst,
-                                     struct ArrowBuffer* dst);
+NANOARROW_DLL ArrowErrorCode ArrowDeviceBufferMove(struct ArrowDevice* 
device_src,
+                                                   struct ArrowBuffer* src,
+                                                   struct ArrowDevice* 
device_dst,
+                                                   struct ArrowBuffer* dst);
 
 /// \brief Copy a buffer into preallocated device memory
 ///
 /// If stream is provided, it will be used to launch copies asynchronously.
 /// Note that this implies that src will remain valid until the stream is
 /// synchronized.
-ArrowErrorCode ArrowDeviceBufferCopyAsync(struct ArrowDevice* device_src,
-                                          struct ArrowBufferView src,
-                                          struct ArrowDevice* device_dst,
-                                          struct ArrowBufferView dst, void* 
stream);
+NANOARROW_DLL ArrowErrorCode ArrowDeviceBufferCopyAsync(struct ArrowDevice* 
device_src,
+                                                        struct ArrowBufferView 
src,
+                                                        struct ArrowDevice* 
device_dst,
+                                                        struct ArrowBufferView 
dst,
+                                                        void* stream);
 
 /// \brief Copy a buffer into preallocated devie memory
 ///
@@ -421,7 +425,8 @@ static inline ArrowErrorCode ArrowDeviceBufferCopy(struct 
ArrowDevice* device_sr
 ///
 /// device_type must be one of ARROW_DEVICE_CUDA or ARROW_DEVICE_CUDA_HOST;
 /// device_id must be between 0 and cudaGetDeviceCount - 1.
-struct ArrowDevice* ArrowDeviceCuda(ArrowDeviceType device_type, int64_t 
device_id);
+NANOARROW_DLL struct ArrowDevice* ArrowDeviceCuda(ArrowDeviceType device_type,
+                                                  int64_t device_id);
 
 /// @}
 
@@ -440,11 +445,11 @@ struct ArrowDevice* ArrowDeviceCuda(ArrowDeviceType 
device_type, int64_t device_
 /// @{
 
 /// \brief A pointer to a default metal device singleton
-struct ArrowDevice* ArrowDeviceMetalDefaultDevice(void);
+NANOARROW_DLL struct ArrowDevice* ArrowDeviceMetalDefaultDevice(void);
 
 /// \brief Initialize a preallocated device struct with the default metal 
device
-ArrowErrorCode ArrowDeviceMetalInitDefaultDevice(struct ArrowDevice* device,
-                                                 struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowDeviceMetalInitDefaultDevice(struct 
ArrowDevice* device,
+                                                               struct 
ArrowError* error);
 
 /// \brief Initialize a buffer with the Metal allocator
 ///
@@ -453,7 +458,7 @@ ArrowErrorCode ArrowDeviceMetalInitDefaultDevice(struct 
ArrowDevice* device,
 /// This buffer's allocator uses the Metal API so that it is cheaper to send
 /// buffers to the GPU later. You can use, append to, or move this buffer just
 /// like a normal ArrowBuffer.
-ArrowErrorCode ArrowDeviceMetalInitBuffer(struct ArrowBuffer* buffer);
+NANOARROW_DLL ArrowErrorCode ArrowDeviceMetalInitBuffer(struct ArrowBuffer* 
buffer);
 
 /// \brief Convert an ArrowArray to buffers that use the Metal allocator
 ///
@@ -462,7 +467,7 @@ ArrowErrorCode ArrowDeviceMetalInitBuffer(struct 
ArrowBuffer* buffer);
 /// valid to use just like a normal ArrowArray that was initialized with
 /// ArrowArrayInitFromType() (i.e., it can be appended to and finished with
 /// validation).
-ArrowErrorCode ArrowDeviceMetalAlignArrayBuffers(struct ArrowArray* array);
+NANOARROW_DLL ArrowErrorCode ArrowDeviceMetalAlignArrayBuffers(struct 
ArrowArray* array);
 
 /// @}
 
diff --git a/src/nanoarrow/nanoarrow_ipc.h b/src/nanoarrow/nanoarrow_ipc.h
index 4cb4c05e..ea2c8ae4 100644
--- a/src/nanoarrow/nanoarrow_ipc.h
+++ b/src/nanoarrow/nanoarrow_ipc.h
@@ -185,22 +185,22 @@ struct ArrowIpcSharedBuffer {
 ///
 /// If NANOARROW_OK is returned, the ArrowIpcSharedBuffer takes ownership of
 /// src.
-ArrowErrorCode ArrowIpcSharedBufferInit(struct ArrowIpcSharedBuffer* shared,
-                                        struct ArrowBuffer* src);
+NANOARROW_DLL ArrowErrorCode ArrowIpcSharedBufferInit(struct 
ArrowIpcSharedBuffer* shared,
+                                                      struct ArrowBuffer* src);
 
 /// \brief Release the caller's copy of the shared buffer
 ///
 /// When finished, the caller must relinquish its own copy of the shared data
 /// using this function. The original buffer will continue to exist until all
 /// ArrowArray objects that refer to it have also been released.
-void ArrowIpcSharedBufferReset(struct ArrowIpcSharedBuffer* shared);
+NANOARROW_DLL void ArrowIpcSharedBufferReset(struct ArrowIpcSharedBuffer* 
shared);
 
 /// \brief Check for shared buffer thread safety
 ///
 /// Thread-safe shared buffers require C11 and the stdatomic.h header.
 /// If either are unavailable, shared buffers are still possible but
 /// the resulting arrays must not be passed to other threads to be released.
-int ArrowIpcSharedBufferIsThreadSafe(void);
+NANOARROW_DLL int ArrowIpcSharedBufferIsThreadSafe(void);
 
 /// \brief A user-extensible decompressor
 ///
@@ -251,19 +251,20 @@ typedef ArrowErrorCode 
(*ArrowIpcDecompressFunction)(struct ArrowBufferView src,
 /// \brief Get the decompression function for ZSTD
 ///
 /// The result will be NULL if nanoarrow was not built with 
NANOARROW_IPC_WITH_ZSTD.
-ArrowIpcDecompressFunction ArrowIpcGetZstdDecompressionFunction(void);
+NANOARROW_DLL ArrowIpcDecompressFunction 
ArrowIpcGetZstdDecompressionFunction(void);
 
 /// \brief An ArrowIpcDecompressor implementation that performs decompression 
in serial
-ArrowErrorCode ArrowIpcSerialDecompressor(struct ArrowIpcDecompressor* 
decompressor);
+NANOARROW_DLL ArrowErrorCode
+ArrowIpcSerialDecompressor(struct ArrowIpcDecompressor* decompressor);
 
 /// \brief Override the ArrowIpcDecompressFunction used for a specific 
compression type
 ///
 /// This may be used to inject support for a particular type of decompression 
if used
 /// with a version of nanoarrow with unknown or minimal capabilities.
-ArrowErrorCode ArrowIpcSerialDecompressorSetFunction(
-    struct ArrowIpcDecompressor* decompressor,
-    enum ArrowIpcCompressionType compression_type,
-    ArrowIpcDecompressFunction decompress_function);
+NANOARROW_DLL ArrowErrorCode
+ArrowIpcSerialDecompressorSetFunction(struct ArrowIpcDecompressor* 
decompressor,
+                                      enum ArrowIpcCompressionType 
compression_type,
+                                      ArrowIpcDecompressFunction 
decompress_function);
 
 /// \brief Decoder for Arrow IPC messages
 ///
@@ -310,14 +311,14 @@ struct ArrowIpcDecoder {
 };
 
 /// \brief Initialize a decoder
-ArrowErrorCode ArrowIpcDecoderInit(struct ArrowIpcDecoder* decoder);
+NANOARROW_DLL ArrowErrorCode ArrowIpcDecoderInit(struct ArrowIpcDecoder* 
decoder);
 
 /// \brief Release all resources attached to a decoder
-void ArrowIpcDecoderReset(struct ArrowIpcDecoder* decoder);
+NANOARROW_DLL void ArrowIpcDecoderReset(struct ArrowIpcDecoder* decoder);
 
 /// \brief Set the decompressor implementation used by this decoder
-ArrowErrorCode ArrowIpcDecoderSetDecompressor(struct ArrowIpcDecoder* decoder,
-                                              struct ArrowIpcDecompressor* 
decompressor);
+NANOARROW_DLL ArrowErrorCode ArrowIpcDecoderSetDecompressor(
+    struct ArrowIpcDecoder* decoder, struct ArrowIpcDecompressor* 
decompressor);
 
 /// \brief Peek at a message header
 ///
@@ -330,10 +331,10 @@ ArrowErrorCode ArrowIpcDecoderSetDecompressor(struct 
ArrowIpcDecoder* decoder,
 /// Pre-1.0 messages were not prefixed with 0xFFFFFFFF. For these messages, a 
value
 /// of 4 will be placed into prefix_size_bytes; otherwise a value of 8 will be 
placed
 /// into prefix_size_bytes.
-ArrowErrorCode ArrowIpcDecoderPeekHeader(struct ArrowIpcDecoder* decoder,
-                                         struct ArrowBufferView data,
-                                         int32_t* prefix_size_bytes,
-                                         struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowIpcDecoderPeekHeader(struct ArrowIpcDecoder* 
decoder,
+                                                       struct ArrowBufferView 
data,
+                                                       int32_t* 
prefix_size_bytes,
+                                                       struct ArrowError* 
error);
 
 /// \brief Verify a message header
 ///
@@ -345,9 +346,9 @@ ArrowErrorCode ArrowIpcDecoderPeekHeader(struct 
ArrowIpcDecoder* decoder,
 ///
 /// Returns as ArrowIpcDecoderPeekHeader() and additionally will
 /// return EINVAL if flatbuffer verification fails.
-ArrowErrorCode ArrowIpcDecoderVerifyHeader(struct ArrowIpcDecoder* decoder,
-                                           struct ArrowBufferView data,
-                                           struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowIpcDecoderVerifyHeader(struct 
ArrowIpcDecoder* decoder,
+                                                         struct 
ArrowBufferView data,
+                                                         struct ArrowError* 
error);
 
 /// \brief Decode a message header
 ///
@@ -364,9 +365,9 @@ ArrowErrorCode ArrowIpcDecoderVerifyHeader(struct 
ArrowIpcDecoder* decoder,
 ///
 /// Returns EINVAL if the content of the message cannot be decoded or ENOTSUP 
if the
 /// content of the message uses features not supported by this library.
-ArrowErrorCode ArrowIpcDecoderDecodeHeader(struct ArrowIpcDecoder* decoder,
-                                           struct ArrowBufferView data,
-                                           struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowIpcDecoderDecodeHeader(struct 
ArrowIpcDecoder* decoder,
+                                                         struct 
ArrowBufferView data,
+                                                         struct ArrowError* 
error);
 
 /// \brief Decode an ArrowSchema
 ///
@@ -375,9 +376,9 @@ ArrowErrorCode ArrowIpcDecoderDecodeHeader(struct 
ArrowIpcDecoder* decoder,
 ///
 /// Returns EINVAL if the decoder did not just decode a schema message or
 /// NANOARROW_OK otherwise.
-ArrowErrorCode ArrowIpcDecoderDecodeSchema(struct ArrowIpcDecoder* decoder,
-                                           struct ArrowSchema* out,
-                                           struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowIpcDecoderDecodeSchema(struct 
ArrowIpcDecoder* decoder,
+                                                         struct ArrowSchema* 
out,
+                                                         struct ArrowError* 
error);
 
 /// \brief Set the ArrowSchema used to decode future record batch messages
 ///
@@ -388,9 +389,9 @@ ArrowErrorCode ArrowIpcDecoderDecodeSchema(struct 
ArrowIpcDecoder* decoder,
 /// schema message applies to future record batch messages).
 ///
 /// Returns EINVAL if schema validation fails or NANOARROW_OK otherwise.
-ArrowErrorCode ArrowIpcDecoderSetSchema(struct ArrowIpcDecoder* decoder,
-                                        struct ArrowSchema* schema,
-                                        struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowIpcDecoderSetSchema(struct ArrowIpcDecoder* 
decoder,
+                                                      struct ArrowSchema* 
schema,
+                                                      struct ArrowError* 
error);
 
 /// \brief Set the endianness used to decode future record batch messages
 ///
@@ -400,8 +401,8 @@ ArrowErrorCode ArrowIpcDecoderSetSchema(struct 
ArrowIpcDecoder* decoder,
 /// schema message applies to future record batch messages).
 ///
 /// Returns NANOARROW_OK on success.
-ArrowErrorCode ArrowIpcDecoderSetEndianness(struct ArrowIpcDecoder* decoder,
-                                            enum ArrowIpcEndianness 
endianness);
+NANOARROW_DLL ArrowErrorCode ArrowIpcDecoderSetEndianness(
+    struct ArrowIpcDecoder* decoder, enum ArrowIpcEndianness endianness);
 
 /// \brief Decode an ArrowArrayView
 ///
@@ -415,10 +416,9 @@ ArrowErrorCode ArrowIpcDecoderSetEndianness(struct 
ArrowIpcDecoder* decoder,
 /// will not perform any heap allocations; however, the buffers referred to by 
the
 /// returned ArrowArrayView are only valid as long as the buffer referred to 
by body stays
 /// valid.
-ArrowErrorCode ArrowIpcDecoderDecodeArrayView(struct ArrowIpcDecoder* decoder,
-                                              struct ArrowBufferView body, 
int64_t i,
-                                              struct ArrowArrayView** out,
-                                              struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowIpcDecoderDecodeArrayView(
+    struct ArrowIpcDecoder* decoder, struct ArrowBufferView body, int64_t i,
+    struct ArrowArrayView** out, struct ArrowError* error);
 
 /// \brief Decode an ArrowArray
 ///
@@ -431,11 +431,10 @@ ArrowErrorCode ArrowIpcDecoderDecodeArrayView(struct 
ArrowIpcDecoder* decoder,
 /// Returns EINVAL if the decoder did not just decode a record batch message, 
ENOTSUP
 /// if the message uses features not supported by this library, or or 
NANOARROW_OK
 /// otherwise.
-ArrowErrorCode ArrowIpcDecoderDecodeArray(struct ArrowIpcDecoder* decoder,
-                                          struct ArrowBufferView body, int64_t 
i,
-                                          struct ArrowArray* out,
-                                          enum ArrowValidationLevel 
validation_level,
-                                          struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowIpcDecoderDecodeArray(
+    struct ArrowIpcDecoder* decoder, struct ArrowBufferView body, int64_t i,
+    struct ArrowArray* out, enum ArrowValidationLevel validation_level,
+    struct ArrowError* error);
 
 /// \brief Decode an ArrowArray from an owned buffer
 ///
@@ -444,7 +443,7 @@ ArrowErrorCode ArrowIpcDecoderDecodeArray(struct 
ArrowIpcDecoder* decoder,
 /// more calls to ArrowIpcDecoderDecodeArrayFromShared(). If
 /// ArrowIpcSharedBufferIsThreadSafe() returns 0, out must not be released by 
another
 /// thread.
-ArrowErrorCode ArrowIpcDecoderDecodeArrayFromShared(
+NANOARROW_DLL ArrowErrorCode ArrowIpcDecoderDecodeArrayFromShared(
     struct ArrowIpcDecoder* decoder, struct ArrowIpcSharedBuffer* shared, 
int64_t i,
     struct ArrowArray* out, enum ArrowValidationLevel validation_level,
     struct ArrowError* error);
@@ -471,22 +470,22 @@ struct ArrowIpcInputStream {
 };
 
 /// \brief Transfer ownership of an ArrowIpcInputStream
-void ArrowIpcInputStreamMove(struct ArrowIpcInputStream* src,
-                             struct ArrowIpcInputStream* dst);
+NANOARROW_DLL void ArrowIpcInputStreamMove(struct ArrowIpcInputStream* src,
+                                           struct ArrowIpcInputStream* dst);
 
 /// \brief Create an input stream from an ArrowBuffer
 ///
 /// The stream takes ownership of the buffer and reads bytes from it.
-ArrowErrorCode ArrowIpcInputStreamInitBuffer(struct ArrowIpcInputStream* 
stream,
-                                             struct ArrowBuffer* input);
+NANOARROW_DLL ArrowErrorCode ArrowIpcInputStreamInitBuffer(
+    struct ArrowIpcInputStream* stream, struct ArrowBuffer* input);
 
 /// \brief Create an input stream from a C FILE* pointer
 ///
 /// Note that the ArrowIpcInputStream has no mechanism to communicate an error
 /// if file_ptr fails to close. If this behaviour is needed, pass false to
 /// close_on_release and handle closing the file independently from stream.
-ArrowErrorCode ArrowIpcInputStreamInitFile(struct ArrowIpcInputStream* stream,
-                                           void* file_ptr, int 
close_on_release);
+NANOARROW_DLL ArrowErrorCode ArrowIpcInputStreamInitFile(
+    struct ArrowIpcInputStream* stream, void* file_ptr, int close_on_release);
 
 /// \brief Options for ArrowIpcArrayStreamReaderInit()
 struct ArrowIpcArrayStreamReaderOptions {
@@ -513,7 +512,7 @@ struct ArrowIpcArrayStreamReaderOptions {
 /// format specification. Returns NANOARROW_OK on success. If NANOARROW_OK
 /// is returned, the ArrowArrayStream takes ownership of input_stream and
 /// the caller is responsible for releasing out.
-ArrowErrorCode ArrowIpcArrayStreamReaderInit(
+NANOARROW_DLL ArrowErrorCode ArrowIpcArrayStreamReaderInit(
     struct ArrowArrayStream* out, struct ArrowIpcInputStream* input_stream,
     struct ArrowIpcArrayStreamReaderOptions* options);
 
@@ -531,7 +530,7 @@ struct ArrowIpcEncoder {
 ///
 /// If NANOARROW_OK is returned, the caller must call ArrowIpcEncoderReset()
 /// to release resources allocated by this function.
-ArrowErrorCode ArrowIpcEncoderInit(struct ArrowIpcEncoder* encoder);
+NANOARROW_DLL ArrowErrorCode ArrowIpcEncoderInit(struct ArrowIpcEncoder* 
encoder);
 
 /// \brief Release all resources attached to an encoder
 void ArrowIpcEncoderReset(struct ArrowIpcEncoder* encoder);
@@ -542,15 +541,15 @@ void ArrowIpcEncoderReset(struct ArrowIpcEncoder* 
encoder);
 /// marker and the header size and 0-padded to a multiple of 8 bytes).
 ///
 /// The bytes of the encoded message will be appended to the provided buffer.
-ArrowErrorCode ArrowIpcEncoderFinalizeBuffer(struct ArrowIpcEncoder* encoder,
-                                             char encapsulate, struct 
ArrowBuffer* out);
+NANOARROW_DLL ArrowErrorCode ArrowIpcEncoderFinalizeBuffer(
+    struct ArrowIpcEncoder* encoder, char encapsulate, struct ArrowBuffer* 
out);
 
 /// \brief Encode an ArrowSchema
 ///
 /// Returns ENOMEM if allocation fails, NANOARROW_OK otherwise.
-ArrowErrorCode ArrowIpcEncoderEncodeSchema(struct ArrowIpcEncoder* encoder,
-                                           const struct ArrowSchema* schema,
-                                           struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowIpcEncoderEncodeSchema(struct 
ArrowIpcEncoder* encoder,
+                                                         const struct 
ArrowSchema* schema,
+                                                         struct ArrowError* 
error);
 
 /// \brief Encode a struct typed ArrayView to a flatbuffer RecordBatch, 
embedded in a
 /// Message.
@@ -558,7 +557,7 @@ ArrowErrorCode ArrowIpcEncoderEncodeSchema(struct 
ArrowIpcEncoder* encoder,
 /// Body buffers are concatenated into a contiguous, padded body_buffer.
 ///
 /// Returns ENOMEM if allocation fails, NANOARROW_OK otherwise.
-ArrowErrorCode ArrowIpcEncoderEncodeSimpleRecordBatch(
+NANOARROW_DLL ArrowErrorCode ArrowIpcEncoderEncodeSimpleRecordBatch(
     struct ArrowIpcEncoder* encoder, const struct ArrowArrayView* array_view,
     struct ArrowBuffer* body_buffer, struct ArrowError* error);
 
@@ -584,28 +583,28 @@ struct ArrowIpcOutputStream {
 };
 
 /// \brief Transfer ownership of an ArrowIpcOutputStream
-void ArrowIpcOutputStreamMove(struct ArrowIpcOutputStream* src,
-                              struct ArrowIpcOutputStream* dst);
+NANOARROW_DLL void ArrowIpcOutputStreamMove(struct ArrowIpcOutputStream* src,
+                                            struct ArrowIpcOutputStream* dst);
 
 /// \brief Create an output stream from an ArrowBuffer
 ///
 /// All bytes witten to the stream will be appended to the buffer.
 /// The stream does not take ownership of the buffer.
-ArrowErrorCode ArrowIpcOutputStreamInitBuffer(struct ArrowIpcOutputStream* 
stream,
-                                              struct ArrowBuffer* output);
+NANOARROW_DLL ArrowErrorCode ArrowIpcOutputStreamInitBuffer(
+    struct ArrowIpcOutputStream* stream, struct ArrowBuffer* output);
 
 /// \brief Create an output stream from a C FILE* pointer
 ///
 /// Note that the ArrowIpcOutputStream has no mechanism to communicate an error
 /// if file_ptr fails to close. If this behaviour is needed, pass false to
 /// close_on_release and handle closing the file independently from stream.
-ArrowErrorCode ArrowIpcOutputStreamInitFile(struct ArrowIpcOutputStream* 
stream,
-                                            void* file_ptr, int 
close_on_release);
+NANOARROW_DLL ArrowErrorCode ArrowIpcOutputStreamInitFile(
+    struct ArrowIpcOutputStream* stream, void* file_ptr, int close_on_release);
 
 /// \brief Write to a stream, trying again until all are written or the stream 
errors.
-ArrowErrorCode ArrowIpcOutputStreamWrite(struct ArrowIpcOutputStream* stream,
-                                         struct ArrowBufferView data,
-                                         struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode
+ArrowIpcOutputStreamWrite(struct ArrowIpcOutputStream* stream,
+                          struct ArrowBufferView data, struct ArrowError* 
error);
 
 /// \brief A stream writer which encodes Schemas and ArrowArrays into an IPC 
byte stream
 ///
@@ -622,18 +621,18 @@ struct ArrowIpcWriter {
 /// Returns NANOARROW_OK on success. If NANOARROW_OK is returned the writer
 /// takes ownership of the output byte stream, and the caller is
 /// responsible for releasing the writer by calling ArrowIpcWriterReset().
-ArrowErrorCode ArrowIpcWriterInit(struct ArrowIpcWriter* writer,
-                                  struct ArrowIpcOutputStream* output_stream);
+NANOARROW_DLL ArrowErrorCode ArrowIpcWriterInit(
+    struct ArrowIpcWriter* writer, struct ArrowIpcOutputStream* output_stream);
 
 /// \brief Release all resources attached to a writer
-void ArrowIpcWriterReset(struct ArrowIpcWriter* writer);
+NANOARROW_DLL void ArrowIpcWriterReset(struct ArrowIpcWriter* writer);
 
 /// \brief Write a schema to the output byte stream
 ///
 /// Errors are propagated from the underlying encoder and output byte stream.
-ArrowErrorCode ArrowIpcWriterWriteSchema(struct ArrowIpcWriter* writer,
-                                         const struct ArrowSchema* in,
-                                         struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowIpcWriterWriteSchema(struct ArrowIpcWriter* 
writer,
+                                                       const struct 
ArrowSchema* in,
+                                                       struct ArrowError* 
error);
 
 /// \brief Write an array view to the output byte stream
 ///
@@ -641,17 +640,17 @@ ArrowErrorCode ArrowIpcWriterWriteSchema(struct 
ArrowIpcWriter* writer,
 /// The writer does not check that a schema was already written.
 ///
 /// Errors are propagated from the underlying encoder and output byte stream,
-ArrowErrorCode ArrowIpcWriterWriteArrayView(struct ArrowIpcWriter* writer,
-                                            const struct ArrowArrayView* in,
-                                            struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowIpcWriterWriteArrayView(struct 
ArrowIpcWriter* writer,
+                                                          const struct 
ArrowArrayView* in,
+                                                          struct ArrowError* 
error);
 
 /// \brief Write an entire stream (including EOS) to the output byte stream
 ///
 /// Errors are propagated from the underlying encoder, array stream, and 
output byte
 /// stream.
-ArrowErrorCode ArrowIpcWriterWriteArrayStream(struct ArrowIpcWriter* writer,
-                                              struct ArrowArrayStream* in,
-                                              struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowIpcWriterWriteArrayStream(struct 
ArrowIpcWriter* writer,
+                                                            struct 
ArrowArrayStream* in,
+                                                            struct ArrowError* 
error);
 
 /// \brief Start writing an IPC file
 ///
@@ -662,8 +661,8 @@ ArrowErrorCode ArrowIpcWriterStartFile(struct 
ArrowIpcWriter* writer,
 /// \brief Finish writing an IPC file
 ///
 /// Writes the IPC file's footer, footer size, and ending magic.
-ArrowErrorCode ArrowIpcWriterFinalizeFile(struct ArrowIpcWriter* writer,
-                                          struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowIpcWriterFinalizeFile(struct ArrowIpcWriter* 
writer,
+                                                        struct ArrowError* 
error);
 /// @}
 
 // Internal APIs:
@@ -699,13 +698,13 @@ struct ArrowIpcFooter {
 ///
 /// \warning This API is currently only public for use in integration testing;
 ///          use at your own risk.
-void ArrowIpcFooterInit(struct ArrowIpcFooter* footer);
+NANOARROW_DLL void ArrowIpcFooterInit(struct ArrowIpcFooter* footer);
 
 /// \brief Release all resources attached to an footer
 ///
 /// \warning This API is currently only public for use in integration testing;
 ///          use at your own risk.
-void ArrowIpcFooterReset(struct ArrowIpcFooter* footer);
+NANOARROW_DLL void ArrowIpcFooterReset(struct ArrowIpcFooter* footer);
 
 /// \brief Encode a footer for use in an IPC file
 ///
@@ -713,9 +712,9 @@ void ArrowIpcFooterReset(struct ArrowIpcFooter* footer);
 ///          use at your own risk.
 ///
 /// Returns ENOMEM if allocation fails, NANOARROW_OK otherwise.
-ArrowErrorCode ArrowIpcEncoderEncodeFooter(struct ArrowIpcEncoder* encoder,
-                                           const struct ArrowIpcFooter* footer,
-                                           struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowIpcEncoderEncodeFooter(
+    struct ArrowIpcEncoder* encoder, const struct ArrowIpcFooter* footer,
+    struct ArrowError* error);
 
 /// \brief Peek at a footer
 ///
@@ -729,9 +728,9 @@ ArrowErrorCode ArrowIpcEncoderEncodeFooter(struct 
ArrowIpcEncoder* encoder,
 ///
 /// \warning This API is currently only public for use in integration testing;
 ///          use at your own risk.
-ArrowErrorCode ArrowIpcDecoderPeekFooter(struct ArrowIpcDecoder* decoder,
-                                         struct ArrowBufferView data,
-                                         struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowIpcDecoderPeekFooter(struct ArrowIpcDecoder* 
decoder,
+                                                       struct ArrowBufferView 
data,
+                                                       struct ArrowError* 
error);
 
 /// \brief Verify a footer
 ///
@@ -745,9 +744,9 @@ ArrowErrorCode ArrowIpcDecoderPeekFooter(struct 
ArrowIpcDecoder* decoder,
 ///
 /// \warning This API is currently only public for use in integration testing;
 ///          use at your own risk.
-ArrowErrorCode ArrowIpcDecoderVerifyFooter(struct ArrowIpcDecoder* decoder,
-                                           struct ArrowBufferView data,
-                                           struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowIpcDecoderVerifyFooter(struct 
ArrowIpcDecoder* decoder,
+                                                         struct 
ArrowBufferView data,
+                                                         struct ArrowError* 
error);
 
 /// \brief Decode a footer
 ///
@@ -762,9 +761,9 @@ ArrowErrorCode ArrowIpcDecoderVerifyFooter(struct 
ArrowIpcDecoder* decoder,
 ///
 /// \warning This API is currently only public for use in integration testing;
 ///          use at your own risk.
-ArrowErrorCode ArrowIpcDecoderDecodeFooter(struct ArrowIpcDecoder* decoder,
-                                           struct ArrowBufferView data,
-                                           struct ArrowError* error);
+NANOARROW_DLL ArrowErrorCode ArrowIpcDecoderDecodeFooter(struct 
ArrowIpcDecoder* decoder,
+                                                         struct 
ArrowBufferView data,
+                                                         struct ArrowError* 
error);
 
 #ifdef __cplusplus
 }
diff --git a/src/nanoarrow/nanoarrow_testing.hpp 
b/src/nanoarrow/nanoarrow_testing.hpp
index b26e857d..490928d0 100644
--- a/src/nanoarrow/nanoarrow_testing.hpp
+++ b/src/nanoarrow/nanoarrow_testing.hpp
@@ -46,7 +46,7 @@ class DictionaryContext;
 /// @{
 
 /// \brief Writer for the Arrow integration testing JSON format
-class TestingJSONWriter {
+class NANOARROW_DLL TestingJSONWriter {
  public:
   TestingJSONWriter();
   ~TestingJSONWriter();
@@ -127,7 +127,7 @@ class TestingJSONWriter {
 };
 
 /// \brief Reader for the Arrow integration testing JSON format
-class TestingJSONReader {
+class NANOARROW_DLL TestingJSONReader {
  public:
   TestingJSONReader(ArrowBufferAllocator allocator);
   TestingJSONReader();
@@ -190,7 +190,7 @@ class TestingJSONReader {
 /// - Map types are considered equal regardless of the child names "entries",
 ///   "key", and "value".
 /// - Float32 and Float64 values are compared according to their JSON 
serialization.
-class TestingJSONComparison {
+class NANOARROW_DLL TestingJSONComparison {
  private:
   // Internal representation of a human-readable inequality
   struct Difference {

Reply via email to