From 1dbb48f338056b544fb9e304fc2bbd459ad73b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Lindstr=C3=B6m?= Date: Mon, 15 Sep 2025 13:44:23 +0200 Subject: [PATCH 01/15] Make Logger and nativecsharpextension compile on Linux. --- .../dotnet-core-CSharp/include/DotnetEnvironment.h | 10 ++++++++++ .../dotnet-core-CSharp/include/Logger.h | 1 - .../dotnet-core-CSharp/include/nativecsharpextension.h | 3 +++ .../dotnet-core-CSharp/src/native/Logger.cpp | 2 -- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h b/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h index c84b6cd2..755141ae 100644 --- a/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h +++ b/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h @@ -10,13 +10,23 @@ //********************************************************************* #pragma once +#if defined(__WIN32) || defined(WINDOWS) #include "Windows.h" +#else +#define E_FAIL 0x80004005 +#endif + #include #include #include +#if defined(__WIN32) || defined(WINDOWS) #define STR(s) L ## s #define CH(c) L ## c +#else +#define STR(s) s +#define CH(c) c +#endif using namespace std; using string_t = std::basic_string; diff --git a/language-extensions/dotnet-core-CSharp/include/Logger.h b/language-extensions/dotnet-core-CSharp/include/Logger.h index 2a96ec0a..fa43d0b8 100644 --- a/language-extensions/dotnet-core-CSharp/include/Logger.h +++ b/language-extensions/dotnet-core-CSharp/include/Logger.h @@ -10,7 +10,6 @@ //********************************************************************* #include #include -#include using namespace std; #define LOG(msg) Logger::Log(msg) diff --git a/language-extensions/dotnet-core-CSharp/include/nativecsharpextension.h b/language-extensions/dotnet-core-CSharp/include/nativecsharpextension.h index 8a1a3412..ad2b0c7e 100644 --- a/language-extensions/dotnet-core-CSharp/include/nativecsharpextension.h +++ b/language-extensions/dotnet-core-CSharp/include/nativecsharpextension.h @@ -20,7 +20,10 @@ #include #include +#if defined(__WIN32) || defined(WINDOWS) #include +#endif + #include #include "sqlexternallanguage.h" #include "sqlexternallibrary.h" diff --git a/language-extensions/dotnet-core-CSharp/src/native/Logger.cpp b/language-extensions/dotnet-core-CSharp/src/native/Logger.cpp index 89d85852..c44b011b 100644 --- a/language-extensions/dotnet-core-CSharp/src/native/Logger.cpp +++ b/language-extensions/dotnet-core-CSharp/src/native/Logger.cpp @@ -10,8 +10,6 @@ //********************************************************************* #include #include -#include -#include #include "Logger.h" using namespace std; From 704e59219f3016fb63dd2f823de3300ec978ec62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Lindstr=C3=B6m?= Date: Mon, 15 Sep 2025 14:28:40 +0200 Subject: [PATCH 02/15] Make DotnetEnvironment compile on Linux. --- .../include/DotnetEnvironment.h | 3 +- .../src/native/DotnetEnvironment.cpp | 35 +++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h b/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h index 755141ae..93edcab7 100644 --- a/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h +++ b/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h @@ -13,7 +13,8 @@ #if defined(__WIN32) || defined(WINDOWS) #include "Windows.h" #else -#define E_FAIL 0x80004005 +#define E_FAIL -1 +#define S_OK 0 #endif #include diff --git a/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp b/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp index bc2f8471..6d9c25a2 100644 --- a/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp +++ b/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp @@ -10,8 +10,15 @@ //********************************************************************* #include "DotnetEnvironment.h" #include "Logger.h" + +#if defined(_WIN32) || defined(WINDOWS) #include "Windows.h" +#else +#include +#endif + #include +#include #include #include #include @@ -19,8 +26,13 @@ #include #include +#if defined(__WIN32) || defined(WINDOWS) #define STR(s) L ## s #define CH(c) L ## c +#else +#define STR(s) s +#define CH(c) c +#endif using namespace std; using string_t = std::basic_string; @@ -35,7 +47,12 @@ DotnetEnvironment::DotnetEnvironment( std::string language_params, std::string language_path, std::string public_library_path, - std::string private_library_path) : m_root_path(to_utf16_str(language_path)) + std::string private_library_path) : +#if defined(_WIN32) || defined(WINDOWS) + m_root_path(to_utf16_str(language_path)) +#else + m_root_path(language_path) +#endif { } @@ -74,6 +91,7 @@ short DotnetEnvironment::Init() return S_OK; } +#if defined(_WIN32) || defined(WINDOWS) //-------------------------------------------------------------------------------------------------- // Name: DotnetEnvironment::to_utf16_str // @@ -88,6 +106,7 @@ string_t DotnetEnvironment::to_utf16_str(const std::string& utf8str) MultiByteToWideChar(CP_UTF8, 0, utf8str.c_str(), -1, wstr.get(), wchars_num); return string_t(wstr.get()); } +#endif //-------------------------------------------------------------------------------------------------- // Name: DotnetEnvironment::to_hex_string @@ -112,7 +131,11 @@ string DotnetEnvironment::to_hex_string(int value) void* DotnetEnvironment::load_library(const char_t *path) { LOG("DotnetEnvironment::load_library"); +#if defined(_WIN32) || defined(WINDOWS) HMODULE h = ::LoadLibraryW(path); +#else + void *h = dlopen(path, RTLD_LAZY); +#endif assert(h != nullptr); return (void*)h; } @@ -126,13 +149,17 @@ void* DotnetEnvironment::load_library(const char_t *path) void* DotnetEnvironment::get_export(void *h, const char *name) { LOG("DotnetEnvironment::get_export"); +#if defined(_WIN32) || defined(WINDOWS) void *f = ::GetProcAddress((HMODULE)h, name); +#else + void *f = dlsym(h, name); +#endif assert(f != nullptr); return f; } //-------------------------------------------------------------------------------------------------- -// Name: DotnetEnvironment::get_export +// Name: DotnetEnvironment::load_hostfxr // // Description: // Load hostfxr and get desired exports @@ -140,7 +167,11 @@ void* DotnetEnvironment::get_export(void *h, const char *name) bool DotnetEnvironment::load_hostfxr() { LOG("DotnetEnvironment::load_hostfxr"); +#if defined(_WIN32) || defined(WINDOWS) string_t hostfxr_location = m_root_path + STR("\\hostfxr.dll"); +#else + string_t hostfxr_location = STR("libhostfxr.so"); +#endif void *lib = load_library(hostfxr_location.c_str()); m_init_fptr = (hostfxr_initialize_for_runtime_config_fn)get_export(lib, "hostfxr_initialize_for_runtime_config"); m_get_delegate_fptr = (hostfxr_get_runtime_delegate_fn)get_export(lib, "hostfxr_get_runtime_delegate"); From 2dd039b6da78c2ce2900e000fae8e91f196a40c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Lindstr=C3=B6m?= Date: Mon, 15 Sep 2025 15:07:15 +0200 Subject: [PATCH 03/15] Add build script for Linux. --- .../build-dotnet-core-CSharp-extension.ps1 | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100755 language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 diff --git a/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 b/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 new file mode 100755 index 00000000..4f2c09e3 --- /dev/null +++ b/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 @@ -0,0 +1,106 @@ +#!/usr/bin/env pwsh + +# Set root and working directories +$ENL_ROOT = Join-Path $PSScriptRoot "../../../.." +$DOTNET_EXTENSION_HOME = Join-Path $ENL_ROOT "language-extensions/dotnet-core-CSharp" +$DOTNET_EXTENSION_WORKING_DIR = Join-Path $ENL_ROOT "build-output/dotnet-core-CSharp-extension/linux" + +# Clean and create working directory +if (Test-Path $DOTNET_EXTENSION_WORKING_DIR) { + Remove-Item $DOTNET_EXTENSION_WORKING_DIR -Recurse -Force +} +New-Item -ItemType Directory -Path $DOTNET_EXTENSION_WORKING_DIR | Out-Null + +$i = 0 + +# Process each build configuration +while($true) { + $BUILD_CONFIGURATION = "" + if ($i -lt $args.Count) { + $BUILD_CONFIGURATION = $args[$i] + } + # Default to release if not debug + if ($BUILD_CONFIGURATION -ne "debug") { + $BUILD_CONFIGURATION = "release" + } + + # Set target directory + $TARGET = Join-Path $ENL_ROOT "build-output/dotnet-core-CSharp-extension/target/$BUILD_CONFIGURATION" + + # Clean and create target directory + if (Test-Path $TARGET) { + Remove-Item $TARGET -Recurse -Force + } + New-Item -ItemType Directory -Path $TARGET | Out-Null + + Write-Host "[Info] Building dotnet-core-CSharp-extension nativecsharpextension dll..." + + # Set build output directory + $BUILD_OUTPUT = Join-Path $DOTNET_EXTENSION_WORKING_DIR $BUILD_CONFIGURATION + if (Test-Path $BUILD_OUTPUT) { + Remove-Item $BUILD_OUTPUT -Recurse -Force + } + New-Item -ItemType Directory -Path $BUILD_OUTPUT | Out-Null + Push-Location $BUILD_OUTPUT + + # Set source and include paths + $DOTNET_NATIVE_SRC = Join-Path $DOTNET_EXTENSION_HOME "src/native" + $DOTNET_NATIVE_INCLUDE = Join-Path $DOTNET_EXTENSION_HOME "include" + $EXTENSION_HOST_INCLUDE = Join-Path $ENL_ROOT "extension-host/include" + $DOTNET_NATIVE_LIB = Join-Path $DOTNET_EXTENSION_HOME "lib" + + # Build native code + $ccArgs = @( + "-shared", + "-fPIC", + "-o", "libnativecsharpextension.so", + "-I", $DOTNET_NATIVE_INCLUDE, + "-I", $EXTENSION_HOST_INCLUDE, + "-D", "LINUX" + ) + $ccArgs += Get-ChildItem -Path $DOTNET_NATIVE_SRC -Filter "*.cpp" | ForEach-Object { $_.FullName } + if ($BUILD_CONFIGURATION -eq "debug") { + $ccArgs += "-D", "DEBUG" + } + + if ($null -ne $env:CXX) { + $cxx = $env:CXX + } else { + $cxx = "c++" + } + + $proc = Start-Process -FilePath $cxx -ArgumentList $ccArgs -NoNewWindow -Wait -PassThru + if ($proc.ExitCode -ne 0) { + Write-Host "Error: Failed to build nativecsharpextension for configuration=$BUILD_CONFIGURATION" + exit $proc.ExitCode + } + + Write-Host "[Info] Copying dependent libraries..." + Copy-Item (Join-Path $DOTNET_NATIVE_LIB "libhostfxr.so") $BUILD_OUTPUT -Force + + # Build managed code + Write-Host "[Info] Building Microsoft.SqlServer.CSharpExtension dll..." + $DOTNET_MANAGED_SRC = Join-Path $DOTNET_EXTENSION_HOME "src/managed" + $dotnetProc = Start-Process -FilePath "dotnet" -ArgumentList @( + "build", + (Join-Path $DOTNET_MANAGED_SRC "Microsoft.SqlServer.CSharpExtension.csproj"), + "-m", + "-c", $BUILD_CONFIGURATION, + "-o", $BUILD_OUTPUT, + "--no-dependencies" + ) -NoNewWindow -Wait -PassThru + + if ($dotnetProc.ExitCode -ne 0) { + Write-Host "Error: Failed to build for Microsoft.SqlServer.CSharpExtension.dll for configuration=$BUILD_CONFIGURATION" + exit $dotnetProc.ExitCode + } + + Write-Host "Success: Built dotnet-core-CSharp-extension for $BUILD_CONFIGURATION configuration." + + $i++ + if ($i -ge $args.Count) { + break + } +} + +exit 0 From 222d4f30e604a66bade6fb64abbb32e403ce2962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Lindstr=C3=B6m?= Date: Mon, 15 Sep 2025 15:32:44 +0200 Subject: [PATCH 04/15] Add archive script for Linux. SQL Server on Linux expects a .tar.gz file instead of a .zip when using CREATE EXTERNAL LANGUAGE. --- .../build-dotnet-core-CSharp-extension.ps1 | 6 +- ...reate-dotnet-core-CSharp-extension-zip.ps1 | 65 +++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) create mode 100755 language-extensions/dotnet-core-CSharp/build/linux/create-dotnet-core-CSharp-extension-zip.ps1 diff --git a/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 b/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 index 4f2c09e3..092ecd12 100755 --- a/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 +++ b/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 @@ -14,11 +14,12 @@ New-Item -ItemType Directory -Path $DOTNET_EXTENSION_WORKING_DIR | Out-Null $i = 0 # Process each build configuration -while($true) { +while ($true) { $BUILD_CONFIGURATION = "" if ($i -lt $args.Count) { $BUILD_CONFIGURATION = $args[$i] } + $BUILD_CONFIGURATION = $BUILD_CONFIGURATION.ToLower() # Default to release if not debug if ($BUILD_CONFIGURATION -ne "debug") { $BUILD_CONFIGURATION = "release" @@ -65,7 +66,8 @@ while($true) { if ($null -ne $env:CXX) { $cxx = $env:CXX - } else { + } + else { $cxx = "c++" } diff --git a/language-extensions/dotnet-core-CSharp/build/linux/create-dotnet-core-CSharp-extension-zip.ps1 b/language-extensions/dotnet-core-CSharp/build/linux/create-dotnet-core-CSharp-extension-zip.ps1 new file mode 100755 index 00000000..47c33386 --- /dev/null +++ b/language-extensions/dotnet-core-CSharp/build/linux/create-dotnet-core-CSharp-extension-zip.ps1 @@ -0,0 +1,65 @@ +#!/usr/bin/env pwsh + +$ENL_ROOT = Join-Path $PSScriptRoot "../../../.." +$DOTNET_EXTENSION_WORKING_DIR = Join-Path -Path $ENL_ROOT -ChildPath "build-output/dotnet-core-CSharp-extension/linux" + +function CheckError { + param( + [int]$errorLevel, + [string]$errorMessage + ) + if ($errorLevel -ne 0) { + Write-Error $errorMessage + exit $errorLevel + } +} + + +$i = 0 + +# Process each build configuration +while ($true) { + $BUILD_CONFIGURATION = "" + if ($i -lt $args.Count) { + $BUILD_CONFIGURATION = $args[$i] + } + $BUILD_CONFIGURATION = $BUILD_CONFIGURATION.ToLower() + # Default to release if not debug + if ($BUILD_CONFIGURATION -ne "debug") { + $BUILD_CONFIGURATION = "release" + } + + $BUILD_OUTPUT = Join-Path -Path $DOTNET_EXTENSION_WORKING_DIR -ChildPath $BUILD_CONFIGURATION + New-Item -ItemType Directory -Force -Path "$BUILD_OUTPUT/packages" | Out-Null + + # Delete the ref folder so that the zip can be loaded by the SPEES + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue "$BUILD_OUTPUT/ref" + + # Define files to compress, conditionally including .pdb files if BUILD_CONFIGURATION is "debug" + $FILES_TO_COMPRESS = @( + "Microsoft.SqlServer.CSharpExtension.runtimeconfig.json", + "Microsoft.SqlServer.CSharpExtension.deps.json" + ) + $FILES_TO_COMPRESS += Get-ChildItem -Path $BUILD_OUTPUT -Filter "*.dll" | ForEach-Object { $_.Name } + $FILES_TO_COMPRESS += Get-ChildItem -Path $BUILD_OUTPUT -Filter "*.so" | ForEach-Object { $_.Name } + if ($BUILD_CONFIGURATION -ieq "debug") { + $FILES_TO_COMPRESS += Get-ChildItem -Path $BUILD_OUTPUT -Filter "*.pdb" | ForEach-Object { $_.Name } + } + + # Package the signed binaries. + try { + Push-Location $BUILD_OUTPUT + & tar -czvf "$BUILD_OUTPUT/packages/dotnet-core-CSharp-lang-extension.tar.gz" $FILES_TO_COMPRESS + CheckError $LASTEXITCODE "Error: Failed to create zip for dotnet-core-CSharp-extension for configuration=$BUILD_CONFIGURATION" + Pop-Location + Write-Host "Success: Compressed dotnet-core-CSharp-extension for $BUILD_CONFIGURATION configuration." + } + catch { + CheckError 1 "Error: Failed to create zip for dotnet-core-CSharp-extension for configuration=$BUILD_CONFIGURATION" + } + + $i++ + if ($i -ge $args.Count) { + break + } +} From ba10c7d56304ca656d245d184806545968b3c621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Lindstr=C3=B6m?= Date: Wed, 17 Sep 2025 08:40:12 +0200 Subject: [PATCH 05/15] Add static nethost library from .NET 8.0 Linux runtime. We will use the nethost to locate the system's installed hostfxr, as using a local libhostfxr.so breaks the .NET runtime lookup. nethost contains the logic needed to find the .NET root based on the DOTNET_ROOT environment variable or the install_location configuration file, which we will require for .NET runtime lookup on diverging Linux distributions (e.g. RHEL and Debian packages not installing .NET in the same base directories). --- .../dotnet-core-CSharp/include/nethost.h | 99 ++++++++++++++++++ .../dotnet-core-CSharp/lib/libnethost.a | Bin 0 -> 178378 bytes 2 files changed, 99 insertions(+) create mode 100644 language-extensions/dotnet-core-CSharp/include/nethost.h create mode 100644 language-extensions/dotnet-core-CSharp/lib/libnethost.a diff --git a/language-extensions/dotnet-core-CSharp/include/nethost.h b/language-extensions/dotnet-core-CSharp/include/nethost.h new file mode 100644 index 00000000..eaca1753 --- /dev/null +++ b/language-extensions/dotnet-core-CSharp/include/nethost.h @@ -0,0 +1,99 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef __NETHOST_H__ +#define __NETHOST_H__ + +#include + +#ifdef _WIN32 + #ifdef NETHOST_EXPORT + #define NETHOST_API __declspec(dllexport) + #else + // Consuming the nethost as a static library + // Shouldn't export attempt to dllimport. + #ifdef NETHOST_USE_AS_STATIC + #define NETHOST_API + #else + #define NETHOST_API __declspec(dllimport) + #endif + #endif + + #define NETHOST_CALLTYPE __stdcall + #ifdef _WCHAR_T_DEFINED + typedef wchar_t char_t; + #else + typedef unsigned short char_t; + #endif +#else + #ifdef NETHOST_EXPORT + #define NETHOST_API __attribute__((__visibility__("default"))) + #else + #define NETHOST_API + #endif + + #define NETHOST_CALLTYPE + typedef char char_t; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// Parameters for get_hostfxr_path +// +// Fields: +// size +// Size of the struct. This is used for versioning. +// +// assembly_path +// Path to the component's assembly. +// If specified, hostfxr is located as if the assembly_path is the apphost +// +// dotnet_root +// Path to directory containing the dotnet executable. +// If specified, hostfxr is located as if an application is started using +// 'dotnet app.dll', which means it will be searched for under the dotnet_root +// path and the assembly_path is ignored. +// +struct get_hostfxr_parameters { + size_t size; + const char_t *assembly_path; + const char_t *dotnet_root; +}; + +// +// Get the path to the hostfxr library +// +// Parameters: +// buffer +// Buffer that will be populated with the hostfxr path, including a null terminator. +// +// buffer_size +// [in] Size of buffer in char_t units. +// [out] Size of buffer used in char_t units. If the input value is too small +// or buffer is nullptr, this is populated with the minimum required size +// in char_t units for a buffer to hold the hostfxr path +// +// get_hostfxr_parameters +// Optional. Parameters that modify the behaviour for locating the hostfxr library. +// If nullptr, hostfxr is located using the environment variable or global registration +// +// Return value: +// 0 on success, otherwise failure +// 0x80008098 - buffer is too small (HostApiBufferTooSmall) +// +// Remarks: +// The full search for the hostfxr library is done on every call. To minimize the need +// to call this function multiple times, pass a large buffer (e.g. PATH_MAX). +// +NETHOST_API int NETHOST_CALLTYPE get_hostfxr_path( + char_t * buffer, + size_t * buffer_size, + const struct get_hostfxr_parameters *parameters); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __NETHOST_H__ diff --git a/language-extensions/dotnet-core-CSharp/lib/libnethost.a b/language-extensions/dotnet-core-CSharp/lib/libnethost.a new file mode 100644 index 0000000000000000000000000000000000000000..9b333a48619ed1002570e148f9a916d5ab2c9dd6 GIT binary patch literal 178378 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 literal 0 HcmV?d00001 From 774d4dccf2d4b267f19a4aa365cfe6e292ce0697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Lindstr=C3=B6m?= Date: Wed, 17 Sep 2025 09:00:17 +0200 Subject: [PATCH 06/15] Use nethost to lookup hostfxr on Linux. --- .../build/linux/build-dotnet-core-CSharp-extension.ps1 | 10 ++++------ .../src/native/DotnetEnvironment.cpp | 8 +++++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 b/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 index 092ecd12..2bafbee1 100755 --- a/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 +++ b/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 @@ -55,11 +55,12 @@ while ($true) { "-shared", "-fPIC", "-o", "libnativecsharpextension.so", - "-I", $DOTNET_NATIVE_INCLUDE, - "-I", $EXTENSION_HOST_INCLUDE, - "-D", "LINUX" + "-I$DOTNET_NATIVE_INCLUDE", + "-I$EXTENSION_HOST_INCLUDE", + "-DLINUX" ) $ccArgs += Get-ChildItem -Path $DOTNET_NATIVE_SRC -Filter "*.cpp" | ForEach-Object { $_.FullName } + $ccArgs += Join-Path -Path $DOTNET_NATIVE_LIB -ChildPath "libnethost.a" if ($BUILD_CONFIGURATION -eq "debug") { $ccArgs += "-D", "DEBUG" } @@ -77,9 +78,6 @@ while ($true) { exit $proc.ExitCode } - Write-Host "[Info] Copying dependent libraries..." - Copy-Item (Join-Path $DOTNET_NATIVE_LIB "libhostfxr.so") $BUILD_OUTPUT -Force - # Build managed code Write-Host "[Info] Building Microsoft.SqlServer.CSharpExtension dll..." $DOTNET_MANAGED_SRC = Join-Path $DOTNET_EXTENSION_HOME "src/managed" diff --git a/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp b/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp index 6d9c25a2..3d6b2a83 100644 --- a/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp +++ b/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #if defined(__WIN32) || defined(WINDOWS) #define STR(s) L ## s @@ -170,7 +171,12 @@ bool DotnetEnvironment::load_hostfxr() #if defined(_WIN32) || defined(WINDOWS) string_t hostfxr_location = m_root_path + STR("\\hostfxr.dll"); #else - string_t hostfxr_location = STR("libhostfxr.so"); + char buffer[4096]; + size_t buffer_size = sizeof(buffer); + if (get_hostfxr_path(buffer, &buffer_size, nullptr) != 0) { + return false; + } + string_t hostfxr_location(buffer); #endif void *lib = load_library(hostfxr_location.c_str()); m_init_fptr = (hostfxr_initialize_for_runtime_config_fn)get_export(lib, "hostfxr_initialize_for_runtime_config"); From d4afec864695c0d82188e317346d3d22531ef234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Lindstr=C3=B6m?= Date: Wed, 17 Sep 2025 09:07:16 +0200 Subject: [PATCH 07/15] Add debug compiler flag when building in debug. --- .../build/linux/build-dotnet-core-CSharp-extension.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 b/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 index 2bafbee1..228df9b5 100755 --- a/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 +++ b/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 @@ -62,7 +62,7 @@ while ($true) { $ccArgs += Get-ChildItem -Path $DOTNET_NATIVE_SRC -Filter "*.cpp" | ForEach-Object { $_.FullName } $ccArgs += Join-Path -Path $DOTNET_NATIVE_LIB -ChildPath "libnethost.a" if ($BUILD_CONFIGURATION -eq "debug") { - $ccArgs += "-D", "DEBUG" + $ccArgs += "-D", "DEBUG", "-g" } if ($null -ne $env:CXX) { From f7456f27ecfa4776472e8a0e0603f55af7a8d711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Lindstr=C3=B6m?= Date: Wed, 17 Sep 2025 09:50:31 +0200 Subject: [PATCH 08/15] Fix Linux/Windows portability issues. --- .../dotnet-core-CSharp/include/DotnetEnvironment.h | 10 ++++++++-- .../src/native/DotnetEnvironment.cpp | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h b/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h index 93edcab7..f58b30e1 100644 --- a/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h +++ b/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h @@ -24,9 +24,11 @@ #if defined(__WIN32) || defined(WINDOWS) #define STR(s) L ## s #define CH(c) L ## c +#define PATH_SEPARATOR CH('\\') #else #define STR(s) s #define CH(c) c +#define PATH_SEPARATOR CH('/') #endif using namespace std; @@ -57,10 +59,14 @@ class DotnetEnvironment // Load managed assembly and get function pointer to a managed method // const string_t ManagedExtensionName = STR("Microsoft.SqlServer.CSharpExtension"); - const string_t ManagedExtensionPath = m_root_path + STR("\\") + ManagedExtensionName + STR(".dll"); + const string_t ManagedExtensionPath = m_root_path + PATH_SEPARATOR + ManagedExtensionName + STR(".dll"); const string_t ManagedExtensionType = ManagedExtensionName + STR(".CSharpExtension, ") + ManagedExtensionName; +#if defined(_WIN32) || defined(WINDOWS) const string_t ManagedExtensionMethod = to_utf16_str(method_name); - const string_t DelegateTypeName = ManagedExtensionName + STR(".CSharpExtension+") + to_utf16_str(method_name) + STR("Delegate, ") + ManagedExtensionName; +#else + const string_t ManagedExtensionMethod = method_name; +#endif + const string_t DelegateTypeName = ManagedExtensionName + STR(".CSharpExtension+") + ManagedExtensionMethod + STR("Delegate, ") + ManagedExtensionName; int rc = m_load_assembly_and_get_function_pointer( ManagedExtensionPath.c_str(), ManagedExtensionType.c_str(), diff --git a/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp b/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp index 3d6b2a83..031a9725 100644 --- a/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp +++ b/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp @@ -75,7 +75,7 @@ short DotnetEnvironment::Init() // STEP 2: Initialize and start the .NET Core runtime // - const string_t config_path = m_root_path + STR("\\Microsoft.SqlServer.CSharpExtension.runtimeconfig.json"); + const string_t config_path = m_root_path + PATH_SEPARATOR + STR("Microsoft.SqlServer.CSharpExtension.runtimeconfig.json"); hostfxr_handle cxt = get_dotnet(config_path.c_str()); if (cxt == nullptr) { From 8ab502598ee815879992625d87464bf9b9899cf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Lindstr=C3=B6m?= Date: Wed, 17 Sep 2025 18:11:29 +0200 Subject: [PATCH 09/15] Add documentation about C# on Linux. --- .../dotnet-core-CSharp/README.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/language-extensions/dotnet-core-CSharp/README.md b/language-extensions/dotnet-core-CSharp/README.md index dbfd1c25..8fca9b04 100644 --- a/language-extensions/dotnet-core-CSharp/README.md +++ b/language-extensions/dotnet-core-CSharp/README.md @@ -5,11 +5,11 @@ Language Extensions is a feature of SQL Server used for executing external code. For more information about SQL Server Language Extensions, refer to this [documentation](https://docs.microsoft.com/en-us/sql/language-extensions/language-extensions-overview?view=sql-server-ver15). -The dotnet-core-CSharp-extension version in this repository is compatible with SQL Server 2019 CU3 onwards. It integrates .NET core in SQL Server and works with .NET 8.0 in **Windows only**. +The dotnet-core-CSharp-extension version in this repository is compatible with SQL Server 2019 CU3 onwards. It integrates .NET Core in SQL Server and works with .NET 8.0 and up on Windows and Linux. Currently, the extension supports the following data types: SQL_C_SLONG, SQL_C_ULONG, SQL_C_SSHORT, SQL_C_USHORT, SQL_C_SBIGINT, SQL_C_UBIGINT, SQL_C_STINYINT, SQL_C_UTINYINT, SQL_C_BIT, SQL_C_FLOAT, SQL_C_DOUBLE, SQL_C_CHAR, and SQL_C_WCHAR. It supports the following SQL data types: int, bigint, smallint, tinyint, real, float, bit, char(n), varchar(n), nchar(n), and nvarchar(n). -To use this dotnet-core-CSharp-lang-extension.zip package, follow [this tutorial](./sample/regex/README.md). For any fixes or enhancements, you are welcome to modify, rebuild and use the binaries using the following instructions. +To use this `dotnet-core-CSharp-lang-extension.zip` (Windows) or `dotnet-core-CSharp-lang-extension.tar.gz` (Linux) package, follow [this tutorial](./sample/regex/README.md). For any fixes or enhancements, you are welcome to modify, rebuild and use the binaries using the following instructions. ## Building @@ -24,15 +24,24 @@ To use this dotnet-core-CSharp-lang-extension.zip package, follow [this tutorial - PATH\TO\ENLISTMENT\build-output\dotnet-core-CSharp-extension\windows\release\nativecsharpextension.dll \ - PATH\TO\ENLISTMENT\build-output\dotnet-core-CSharp-extension\windows\release\hostfxr.dll \ - PATH\TO\ENLISTMENT\build-output\dotnet-core-CSharp-extension\windows\release\Microsoft.SqlServer.CSharpExtension.dll \ - - PATH\TO\ENLISTMENT\build-output\dotnet-core-CSharp-extension\windows\release\Microsoft.SqlServer.CSharpExtension.runtimeconfig.json\ + - PATH\TO\ENLISTMENT\build-output\dotnet-core-CSharp-extension\windows\release\Microsoft.SqlServer.CSharpExtension.runtimeconfig.json \ - PATH\TO\ENLISTMENT\build-output\dotnet-core-CSharp-extension\windows\release\Microsoft.SqlServer.CSharpExtension.deps.json 5. Run [create-dotnet-core-CSharp-extension-zip.cmd](./build/windows/create-dotnet-core-CSharp-extension-zip.cmd) which will generate: \ - - PATH\TO\ENLISTMENT\build-output\dotnet-core-CSharp-extension\target\debug\dotnet-core-CSharp-lang-extension.zip + - PATH\TO\ENLISTMENT\build-output\dotnet-core-CSharp-extension\windows\release\packages\dotnet-core-CSharp-lang-extension.zip This zip can be used in CREATE EXTERNAL LANGUAGE, as detailed in the tutorial in the Usage section below. ### Linux -Not Supported. + +1. Run [build-dotnet-core-CSharp-extension.ps1](./build/windows/build-dotnet-core-CSharp-extension.cmd) which will generate: \ + - PATH/TO/ENLISTMENT/build-output/dotnet-core-CSharp-extension/linux/release/libnativecsharpextension.so \ + - PATH/TO/ENLISTMENT/build-output/dotnet-core-CSharp-extension/linux/release/Microsoft.SqlServer.CSharpExtension.dll \ + - PATH/TO/ENLISTMENT/build-output/dotnet-core-CSharp-extension/linux/release/Microsoft.SqlServer.CSharpExtension.runtimeconfig.json \ + - PATH/TO/ENLISTMENT/build-output/dotnet-core-CSharp-extension/linux/release/Microsoft.SqlServer.CSharpExtension.deps.json + +2. Run [create-dotnet-core-CSharp-extension-zip.ps1](./build/windows/create-dotnet-core-CSharp-extension-zip.cmd) which will generate: \ + - PATH/TO/ENLISTMENT/build-output/dotnet-core-CSharp-extension/linux/release/packages/dotnet-core-CSharp-lang-extension.tar.gz + This tarball can be used in CREATE EXTERNAL LANGUAGE, as detailed in the tutorial in the Usage section below. ## Testing (Optional) From 7a99b8a0fc1fdbfbb0d8e1083eef8ca946fa6934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Lindstr=C3=B6m?= Date: Fri, 10 Oct 2025 10:31:58 +0200 Subject: [PATCH 10/15] Fix _WIN32 preprocessor symbol typo. --- .../dotnet-core-CSharp/include/DotnetEnvironment.h | 4 ++-- .../dotnet-core-CSharp/include/nativecsharpextension.h | 2 +- .../dotnet-core-CSharp/src/native/DotnetEnvironment.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h b/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h index f58b30e1..e12856cc 100644 --- a/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h +++ b/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h @@ -10,7 +10,7 @@ //********************************************************************* #pragma once -#if defined(__WIN32) || defined(WINDOWS) +#if defined(_WIN32) || defined(WINDOWS) #include "Windows.h" #else #define E_FAIL -1 @@ -21,7 +21,7 @@ #include #include -#if defined(__WIN32) || defined(WINDOWS) +#if defined(_WIN32) || defined(WINDOWS) #define STR(s) L ## s #define CH(c) L ## c #define PATH_SEPARATOR CH('\\') diff --git a/language-extensions/dotnet-core-CSharp/include/nativecsharpextension.h b/language-extensions/dotnet-core-CSharp/include/nativecsharpextension.h index ad2b0c7e..3a8fad58 100644 --- a/language-extensions/dotnet-core-CSharp/include/nativecsharpextension.h +++ b/language-extensions/dotnet-core-CSharp/include/nativecsharpextension.h @@ -20,7 +20,7 @@ #include #include -#if defined(__WIN32) || defined(WINDOWS) +#if defined(_WIN32) || defined(WINDOWS) #include #endif diff --git a/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp b/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp index 031a9725..31b44640 100644 --- a/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp +++ b/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp @@ -27,7 +27,7 @@ #include #include -#if defined(__WIN32) || defined(WINDOWS) +#if defined(_WIN32) || defined(WINDOWS) #define STR(s) L ## s #define CH(c) L ## c #else From 86d15eaf19b8456e2562572b2d762125918af270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Lindstr=C3=B6m?= Date: Fri, 10 Oct 2025 10:48:33 +0200 Subject: [PATCH 11/15] Cleanup argument loop in Linux scripts. --- .../build-dotnet-core-CSharp-extension.ps1 | 19 ++++++++---------- ...reate-dotnet-core-CSharp-extension-zip.ps1 | 20 ++++++++----------- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 b/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 index 228df9b5..7ec3394b 100755 --- a/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 +++ b/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 @@ -11,14 +11,16 @@ if (Test-Path $DOTNET_EXTENSION_WORKING_DIR) { } New-Item -ItemType Directory -Path $DOTNET_EXTENSION_WORKING_DIR | Out-Null -$i = 0 +if ($args.Count -eq 0) { + # Default to release + $actualArgs = @("release") +} +else { + $actualArgs = $args +} # Process each build configuration -while ($true) { - $BUILD_CONFIGURATION = "" - if ($i -lt $args.Count) { - $BUILD_CONFIGURATION = $args[$i] - } +foreach ($BUILD_CONFIGURATION in $actualArgs) { $BUILD_CONFIGURATION = $BUILD_CONFIGURATION.ToLower() # Default to release if not debug if ($BUILD_CONFIGURATION -ne "debug") { @@ -96,11 +98,6 @@ while ($true) { } Write-Host "Success: Built dotnet-core-CSharp-extension for $BUILD_CONFIGURATION configuration." - - $i++ - if ($i -ge $args.Count) { - break - } } exit 0 diff --git a/language-extensions/dotnet-core-CSharp/build/linux/create-dotnet-core-CSharp-extension-zip.ps1 b/language-extensions/dotnet-core-CSharp/build/linux/create-dotnet-core-CSharp-extension-zip.ps1 index 47c33386..06b20dd3 100755 --- a/language-extensions/dotnet-core-CSharp/build/linux/create-dotnet-core-CSharp-extension-zip.ps1 +++ b/language-extensions/dotnet-core-CSharp/build/linux/create-dotnet-core-CSharp-extension-zip.ps1 @@ -14,15 +14,16 @@ function CheckError { } } - -$i = 0 +if ($args.Count -eq 0) { + # Default to release + $actualArgs = @("release") +} +else { + $actualArgs = $args +} # Process each build configuration -while ($true) { - $BUILD_CONFIGURATION = "" - if ($i -lt $args.Count) { - $BUILD_CONFIGURATION = $args[$i] - } +foreach ($BUILD_CONFIGURATION in $actualArgs) { $BUILD_CONFIGURATION = $BUILD_CONFIGURATION.ToLower() # Default to release if not debug if ($BUILD_CONFIGURATION -ne "debug") { @@ -57,9 +58,4 @@ while ($true) { catch { CheckError 1 "Error: Failed to create zip for dotnet-core-CSharp-extension for configuration=$BUILD_CONFIGURATION" } - - $i++ - if ($i -ge $args.Count) { - break - } } From dd2f532e8767d0777f853956468c7e75a28eea5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Lindstr=C3=B6m?= Date: Fri, 27 Mar 2026 08:44:07 +0100 Subject: [PATCH 12/15] Fix doc links. --- language-extensions/dotnet-core-CSharp/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/language-extensions/dotnet-core-CSharp/README.md b/language-extensions/dotnet-core-CSharp/README.md index 8fca9b04..3e7b2ac9 100644 --- a/language-extensions/dotnet-core-CSharp/README.md +++ b/language-extensions/dotnet-core-CSharp/README.md @@ -33,13 +33,13 @@ To use this `dotnet-core-CSharp-lang-extension.zip` (Windows) or `dotnet-core-CS ### Linux -1. Run [build-dotnet-core-CSharp-extension.ps1](./build/windows/build-dotnet-core-CSharp-extension.cmd) which will generate: \ +1. Run [build-dotnet-core-CSharp-extension.ps1](./build/linux/build-dotnet-core-CSharp-extension.ps1) which will generate: \ - PATH/TO/ENLISTMENT/build-output/dotnet-core-CSharp-extension/linux/release/libnativecsharpextension.so \ - PATH/TO/ENLISTMENT/build-output/dotnet-core-CSharp-extension/linux/release/Microsoft.SqlServer.CSharpExtension.dll \ - PATH/TO/ENLISTMENT/build-output/dotnet-core-CSharp-extension/linux/release/Microsoft.SqlServer.CSharpExtension.runtimeconfig.json \ - PATH/TO/ENLISTMENT/build-output/dotnet-core-CSharp-extension/linux/release/Microsoft.SqlServer.CSharpExtension.deps.json -2. Run [create-dotnet-core-CSharp-extension-zip.ps1](./build/windows/create-dotnet-core-CSharp-extension-zip.cmd) which will generate: \ +2. Run [create-dotnet-core-CSharp-extension-zip.ps1](./build/linux/create-dotnet-core-CSharp-extension-zip.ps1) which will generate: \ - PATH/TO/ENLISTMENT/build-output/dotnet-core-CSharp-extension/linux/release/packages/dotnet-core-CSharp-lang-extension.tar.gz This tarball can be used in CREATE EXTERNAL LANGUAGE, as detailed in the tutorial in the Usage section below. From ca1d83a847e471bb384c650f04c84395c84f780b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Lindstr=C3=B6m?= Date: Fri, 27 Mar 2026 08:47:09 +0100 Subject: [PATCH 13/15] Rationalize defines. --- .../include/DotnetEnvironment.h | 16 ++++++---------- .../src/native/DotnetEnvironment.cpp | 8 -------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h b/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h index e12856cc..bc343740 100644 --- a/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h +++ b/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h @@ -12,25 +12,21 @@ #if defined(_WIN32) || defined(WINDOWS) #include "Windows.h" -#else -#define E_FAIL -1 -#define S_OK 0 -#endif - -#include -#include -#include - -#if defined(_WIN32) || defined(WINDOWS) #define STR(s) L ## s #define CH(c) L ## c #define PATH_SEPARATOR CH('\\') #else +#define E_FAIL -1 +#define S_OK 0 #define STR(s) s #define CH(c) c #define PATH_SEPARATOR CH('/') #endif +#include +#include +#include + using namespace std; using string_t = std::basic_string; diff --git a/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp b/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp index 31b44640..7ab6e6fe 100644 --- a/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp +++ b/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp @@ -27,14 +27,6 @@ #include #include -#if defined(_WIN32) || defined(WINDOWS) -#define STR(s) L ## s -#define CH(c) L ## c -#else -#define STR(s) s -#define CH(c) c -#endif - using namespace std; using string_t = std::basic_string; From c47d3f36b076bc8f10c160259efe503f957c6b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Lindstr=C3=B6m?= Date: Fri, 27 Mar 2026 09:00:05 +0100 Subject: [PATCH 14/15] Handle DOTNET_ROOT DotnetEnvironment on Linux. --- .../dotnet-core-CSharp/src/native/DotnetEnvironment.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp b/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp index 7ab6e6fe..20a06571 100644 --- a/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp +++ b/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp @@ -220,6 +220,7 @@ hostfxr_handle DotnetEnvironment::get_dotnet(const char_t *config_path){ params.dotnet_root = nullptr; // Get the required size for the environment variable +#if defined(_WIN32) || defined(WINDOWS) DWORD requiredSize = GetEnvironmentVariableW(L"DOTNET_ROOT", nullptr, 0); std::vector dotnet_root_buffer; if (requiredSize > 0) @@ -230,6 +231,9 @@ hostfxr_handle DotnetEnvironment::get_dotnet(const char_t *config_path){ params.dotnet_root = dotnet_root_buffer.data(); } } +#else + params.dotnet_root = getenv("DOTNET_ROOT"); +#endif int rc = m_init_fptr(config_path, ¶ms, &cxt); if (rc != 0 || cxt == nullptr) From ce9329263e9cfda4320f869f927aebeb5e32a585 Mon Sep 17 00:00:00 2001 From: Sicong Liu Date: Tue, 31 Mar 2026 16:39:17 -0700 Subject: [PATCH 15/15] Linux support improvements on top of PR #64 - Replace PowerShell build scripts with bash (build, archive, restore) - Remove checked-in libnethost.a; fetch at build time via restore-packages.sh - Add libnethost.a existence guard in build script - Add convert_string() abstraction in DotnetEnvironment - Conditional nethost.h include (Linux only), HOSTFXR_PATH_BUFFER_SIZE constant - Add LOG_ERROR on hostfxr/dlopen/dlsym failures (release-safe, not just assert) - Guard S_OK/E_FAIL macros (#ifndef) on non-Windows - Add Linux test build/run scripts and CMake platform support - Add -fshort-wchar and replace std::wstring with vector/raw wchar_t* in test code to avoid ABI mismatch under -fshort-wchar on Linux - Add platform-conditional HintPath in test csproj - Add Linux dlopen/dlsym equivalents in test API code; throw on readlink failure (cherry picked from commit 2497aa275ee6861eabdf8635eb94d3e6313b27b1) --- .../build-dotnet-core-CSharp-extension.ps1 | 103 ------------------ .../build-dotnet-core-CSharp-extension.sh | 94 ++++++++++++++++ ...create-dotnet-core-CSharp-extension-tar.sh | 61 +++++++++++ ...reate-dotnet-core-CSharp-extension-zip.ps1 | 61 ----------- .../build/linux/restore-packages.sh | 84 ++++++++++++++ .../include/DotnetEnvironment.h | 40 ++++--- .../dotnet-core-CSharp/lib/libnethost.a | Bin 178378 -> 0 bytes .../src/native/DotnetEnvironment.cpp | 82 +++++++++++--- ...build-dotnet-core-CSharp-extension-test.sh | 62 +++++++++++ .../run-dotnet-core-CSharp-extension-test.sh | 31 ++++++ .../dotnet-core-CSharp/test/include/Common.h | 12 +- ...osoft.SqlServer.CSharpExtensionTest.csproj | 7 +- .../test/src/native/CMakeLists.txt | 60 ++++++---- .../src/native/CSharpExtensionApiTests.cpp | 64 +++++++++++ .../src/native/CSharpGetOutputParamTests.cpp | 103 +++++++++--------- 15 files changed, 595 insertions(+), 269 deletions(-) delete mode 100755 language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 create mode 100644 language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.sh create mode 100644 language-extensions/dotnet-core-CSharp/build/linux/create-dotnet-core-CSharp-extension-tar.sh delete mode 100755 language-extensions/dotnet-core-CSharp/build/linux/create-dotnet-core-CSharp-extension-zip.ps1 create mode 100644 language-extensions/dotnet-core-CSharp/build/linux/restore-packages.sh delete mode 100644 language-extensions/dotnet-core-CSharp/lib/libnethost.a create mode 100644 language-extensions/dotnet-core-CSharp/test/build/linux/build-dotnet-core-CSharp-extension-test.sh create mode 100644 language-extensions/dotnet-core-CSharp/test/build/linux/run-dotnet-core-CSharp-extension-test.sh diff --git a/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 b/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 deleted file mode 100755 index 7ec3394b..00000000 --- a/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.ps1 +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env pwsh - -# Set root and working directories -$ENL_ROOT = Join-Path $PSScriptRoot "../../../.." -$DOTNET_EXTENSION_HOME = Join-Path $ENL_ROOT "language-extensions/dotnet-core-CSharp" -$DOTNET_EXTENSION_WORKING_DIR = Join-Path $ENL_ROOT "build-output/dotnet-core-CSharp-extension/linux" - -# Clean and create working directory -if (Test-Path $DOTNET_EXTENSION_WORKING_DIR) { - Remove-Item $DOTNET_EXTENSION_WORKING_DIR -Recurse -Force -} -New-Item -ItemType Directory -Path $DOTNET_EXTENSION_WORKING_DIR | Out-Null - -if ($args.Count -eq 0) { - # Default to release - $actualArgs = @("release") -} -else { - $actualArgs = $args -} - -# Process each build configuration -foreach ($BUILD_CONFIGURATION in $actualArgs) { - $BUILD_CONFIGURATION = $BUILD_CONFIGURATION.ToLower() - # Default to release if not debug - if ($BUILD_CONFIGURATION -ne "debug") { - $BUILD_CONFIGURATION = "release" - } - - # Set target directory - $TARGET = Join-Path $ENL_ROOT "build-output/dotnet-core-CSharp-extension/target/$BUILD_CONFIGURATION" - - # Clean and create target directory - if (Test-Path $TARGET) { - Remove-Item $TARGET -Recurse -Force - } - New-Item -ItemType Directory -Path $TARGET | Out-Null - - Write-Host "[Info] Building dotnet-core-CSharp-extension nativecsharpextension dll..." - - # Set build output directory - $BUILD_OUTPUT = Join-Path $DOTNET_EXTENSION_WORKING_DIR $BUILD_CONFIGURATION - if (Test-Path $BUILD_OUTPUT) { - Remove-Item $BUILD_OUTPUT -Recurse -Force - } - New-Item -ItemType Directory -Path $BUILD_OUTPUT | Out-Null - Push-Location $BUILD_OUTPUT - - # Set source and include paths - $DOTNET_NATIVE_SRC = Join-Path $DOTNET_EXTENSION_HOME "src/native" - $DOTNET_NATIVE_INCLUDE = Join-Path $DOTNET_EXTENSION_HOME "include" - $EXTENSION_HOST_INCLUDE = Join-Path $ENL_ROOT "extension-host/include" - $DOTNET_NATIVE_LIB = Join-Path $DOTNET_EXTENSION_HOME "lib" - - # Build native code - $ccArgs = @( - "-shared", - "-fPIC", - "-o", "libnativecsharpextension.so", - "-I$DOTNET_NATIVE_INCLUDE", - "-I$EXTENSION_HOST_INCLUDE", - "-DLINUX" - ) - $ccArgs += Get-ChildItem -Path $DOTNET_NATIVE_SRC -Filter "*.cpp" | ForEach-Object { $_.FullName } - $ccArgs += Join-Path -Path $DOTNET_NATIVE_LIB -ChildPath "libnethost.a" - if ($BUILD_CONFIGURATION -eq "debug") { - $ccArgs += "-D", "DEBUG", "-g" - } - - if ($null -ne $env:CXX) { - $cxx = $env:CXX - } - else { - $cxx = "c++" - } - - $proc = Start-Process -FilePath $cxx -ArgumentList $ccArgs -NoNewWindow -Wait -PassThru - if ($proc.ExitCode -ne 0) { - Write-Host "Error: Failed to build nativecsharpextension for configuration=$BUILD_CONFIGURATION" - exit $proc.ExitCode - } - - # Build managed code - Write-Host "[Info] Building Microsoft.SqlServer.CSharpExtension dll..." - $DOTNET_MANAGED_SRC = Join-Path $DOTNET_EXTENSION_HOME "src/managed" - $dotnetProc = Start-Process -FilePath "dotnet" -ArgumentList @( - "build", - (Join-Path $DOTNET_MANAGED_SRC "Microsoft.SqlServer.CSharpExtension.csproj"), - "-m", - "-c", $BUILD_CONFIGURATION, - "-o", $BUILD_OUTPUT, - "--no-dependencies" - ) -NoNewWindow -Wait -PassThru - - if ($dotnetProc.ExitCode -ne 0) { - Write-Host "Error: Failed to build for Microsoft.SqlServer.CSharpExtension.dll for configuration=$BUILD_CONFIGURATION" - exit $dotnetProc.ExitCode - } - - Write-Host "Success: Built dotnet-core-CSharp-extension for $BUILD_CONFIGURATION configuration." -} - -exit 0 diff --git a/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.sh b/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.sh new file mode 100644 index 00000000..f328c724 --- /dev/null +++ b/language-extensions/dotnet-core-CSharp/build/linux/build-dotnet-core-CSharp-extension.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Set root and working directories +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ENL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)" +DOTNET_EXTENSION_HOME="$ENL_ROOT/language-extensions/dotnet-core-CSharp" +DOTNET_EXTENSION_WORKING_DIR="$ENL_ROOT/build-output/dotnet-core-CSharp-extension/linux" + +# Clean and create working directory +rm -rf "$DOTNET_EXTENSION_WORKING_DIR" +mkdir -p "$DOTNET_EXTENSION_WORKING_DIR" + +# Default to release if no arguments provided +if [ $# -eq 0 ]; then + set -- "release" +fi + +# Process each build configuration +for BUILD_CONFIGURATION in "$@"; do + BUILD_CONFIGURATION="$(echo "$BUILD_CONFIGURATION" | tr '[:upper:]' '[:lower:]')" + + # Default to release if not debug + if [ "$BUILD_CONFIGURATION" != "debug" ]; then + BUILD_CONFIGURATION="release" + fi + + echo "[Info] Building dotnet-core-CSharp-extension libnativecsharpextension.so..." + + # Set build output directory + BUILD_OUTPUT="$DOTNET_EXTENSION_WORKING_DIR/$BUILD_CONFIGURATION" + rm -rf "$BUILD_OUTPUT" + mkdir -p "$BUILD_OUTPUT" + pushd "$BUILD_OUTPUT" > /dev/null + + # Set source and include paths + DOTNET_NATIVE_SRC="$DOTNET_EXTENSION_HOME/src/native" + DOTNET_NATIVE_INCLUDE="$DOTNET_EXTENSION_HOME/include" + EXTENSION_HOST_INCLUDE="$ENL_ROOT/extension-host/include" + DOTNET_NATIVE_LIB="$DOTNET_EXTENSION_HOME/lib" + + # Determine C++ compiler + CXX="${CXX:-c++}" + + # Build compiler arguments + CC_ARGS=( + -shared + -fPIC + -fshort-wchar + -std=c++17 + -o libnativecsharpextension.so + "-I$DOTNET_NATIVE_INCLUDE" + "-I$EXTENSION_HOST_INCLUDE" + ) + + if [ "$BUILD_CONFIGURATION" = "debug" ]; then + CC_ARGS+=(-DDEBUG -g) + fi + + # Add all .cpp source files + for src in "$DOTNET_NATIVE_SRC"/*.cpp; do + CC_ARGS+=("$src") + done + + # Link with static nethost library (must run restore-packages.sh first) + if [ ! -f "$DOTNET_NATIVE_LIB/libnethost.a" ]; then + echo "Error: libnethost.a not found at $DOTNET_NATIVE_LIB/libnethost.a" >&2 + echo " Run restore-packages.sh first to install the .NET SDK and copy libnethost.a." >&2 + exit 1 + fi + CC_ARGS+=("$DOTNET_NATIVE_LIB/libnethost.a") + # Link with libdl for dlopen/dlsym + CC_ARGS+=(-ldl) + + echo "[Info] Compiling with: $CXX ${CC_ARGS[*]}" + "$CXX" "${CC_ARGS[@]}" + + popd > /dev/null + + # Build managed code + echo "[Info] Building Microsoft.SqlServer.CSharpExtension dll..." + DOTNET_MANAGED_SRC="$DOTNET_EXTENSION_HOME/src/managed" + dotnet build \ + "$DOTNET_MANAGED_SRC/Microsoft.SqlServer.CSharpExtension.csproj" \ + -m \ + -c "$BUILD_CONFIGURATION" \ + -o "$BUILD_OUTPUT" \ + --no-restore \ + --no-dependencies + + echo "Success: Built dotnet-core-CSharp-extension for $BUILD_CONFIGURATION configuration." +done + +exit 0 diff --git a/language-extensions/dotnet-core-CSharp/build/linux/create-dotnet-core-CSharp-extension-tar.sh b/language-extensions/dotnet-core-CSharp/build/linux/create-dotnet-core-CSharp-extension-tar.sh new file mode 100644 index 00000000..4c021ab3 --- /dev/null +++ b/language-extensions/dotnet-core-CSharp/build/linux/create-dotnet-core-CSharp-extension-tar.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ENL_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)" +DOTNET_EXTENSION_WORKING_DIR="$ENL_ROOT/build-output/dotnet-core-CSharp-extension/linux" + +check_error() { + local error_level=$1 + local error_message=$2 + if [ "$error_level" -ne 0 ]; then + echo "Error: $error_message" >&2 + exit "$error_level" + fi +} + +# Default to release if no arguments provided +if [ $# -eq 0 ]; then + set -- "release" +fi + +# Process each build configuration +for BUILD_CONFIGURATION in "$@"; do + BUILD_CONFIGURATION="$(echo "$BUILD_CONFIGURATION" | tr '[:upper:]' '[:lower:]')" + + # Default to release if not debug + if [ "$BUILD_CONFIGURATION" != "debug" ]; then + BUILD_CONFIGURATION="release" + fi + + BUILD_OUTPUT="$DOTNET_EXTENSION_WORKING_DIR/$BUILD_CONFIGURATION" + mkdir -p "$BUILD_OUTPUT/packages" + + # Delete the ref folder so that the tarball can be loaded by the SPEES + rm -rf "$BUILD_OUTPUT/ref" + + # Collect files to compress + FILES_TO_COMPRESS=() + FILES_TO_COMPRESS+=("Microsoft.SqlServer.CSharpExtension.runtimeconfig.json") + FILES_TO_COMPRESS+=("Microsoft.SqlServer.CSharpExtension.deps.json") + + # Add all .dll and .so files + for f in "$BUILD_OUTPUT"/*.dll "$BUILD_OUTPUT"/*.so; do + [ -e "$f" ] && FILES_TO_COMPRESS+=("$(basename "$f")") + done + + # Include .pdb files for debug builds + if [ "$BUILD_CONFIGURATION" = "debug" ]; then + for f in "$BUILD_OUTPUT"/*.pdb; do + [ -e "$f" ] && FILES_TO_COMPRESS+=("$(basename "$f")") + done + fi + + # Package the binaries + pushd "$BUILD_OUTPUT" > /dev/null + tar -czvf "$BUILD_OUTPUT/packages/dotnet-core-CSharp-lang-extension.tar.gz" "${FILES_TO_COMPRESS[@]}" + check_error $? "Failed to create tarball for dotnet-core-CSharp-extension for configuration=$BUILD_CONFIGURATION" + popd > /dev/null + + echo "Success: Compressed dotnet-core-CSharp-extension for $BUILD_CONFIGURATION configuration." +done diff --git a/language-extensions/dotnet-core-CSharp/build/linux/create-dotnet-core-CSharp-extension-zip.ps1 b/language-extensions/dotnet-core-CSharp/build/linux/create-dotnet-core-CSharp-extension-zip.ps1 deleted file mode 100755 index 06b20dd3..00000000 --- a/language-extensions/dotnet-core-CSharp/build/linux/create-dotnet-core-CSharp-extension-zip.ps1 +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env pwsh - -$ENL_ROOT = Join-Path $PSScriptRoot "../../../.." -$DOTNET_EXTENSION_WORKING_DIR = Join-Path -Path $ENL_ROOT -ChildPath "build-output/dotnet-core-CSharp-extension/linux" - -function CheckError { - param( - [int]$errorLevel, - [string]$errorMessage - ) - if ($errorLevel -ne 0) { - Write-Error $errorMessage - exit $errorLevel - } -} - -if ($args.Count -eq 0) { - # Default to release - $actualArgs = @("release") -} -else { - $actualArgs = $args -} - -# Process each build configuration -foreach ($BUILD_CONFIGURATION in $actualArgs) { - $BUILD_CONFIGURATION = $BUILD_CONFIGURATION.ToLower() - # Default to release if not debug - if ($BUILD_CONFIGURATION -ne "debug") { - $BUILD_CONFIGURATION = "release" - } - - $BUILD_OUTPUT = Join-Path -Path $DOTNET_EXTENSION_WORKING_DIR -ChildPath $BUILD_CONFIGURATION - New-Item -ItemType Directory -Force -Path "$BUILD_OUTPUT/packages" | Out-Null - - # Delete the ref folder so that the zip can be loaded by the SPEES - Remove-Item -Recurse -Force -ErrorAction SilentlyContinue "$BUILD_OUTPUT/ref" - - # Define files to compress, conditionally including .pdb files if BUILD_CONFIGURATION is "debug" - $FILES_TO_COMPRESS = @( - "Microsoft.SqlServer.CSharpExtension.runtimeconfig.json", - "Microsoft.SqlServer.CSharpExtension.deps.json" - ) - $FILES_TO_COMPRESS += Get-ChildItem -Path $BUILD_OUTPUT -Filter "*.dll" | ForEach-Object { $_.Name } - $FILES_TO_COMPRESS += Get-ChildItem -Path $BUILD_OUTPUT -Filter "*.so" | ForEach-Object { $_.Name } - if ($BUILD_CONFIGURATION -ieq "debug") { - $FILES_TO_COMPRESS += Get-ChildItem -Path $BUILD_OUTPUT -Filter "*.pdb" | ForEach-Object { $_.Name } - } - - # Package the signed binaries. - try { - Push-Location $BUILD_OUTPUT - & tar -czvf "$BUILD_OUTPUT/packages/dotnet-core-CSharp-lang-extension.tar.gz" $FILES_TO_COMPRESS - CheckError $LASTEXITCODE "Error: Failed to create zip for dotnet-core-CSharp-extension for configuration=$BUILD_CONFIGURATION" - Pop-Location - Write-Host "Success: Compressed dotnet-core-CSharp-extension for $BUILD_CONFIGURATION configuration." - } - catch { - CheckError 1 "Error: Failed to create zip for dotnet-core-CSharp-extension for configuration=$BUILD_CONFIGURATION" - } -} diff --git a/language-extensions/dotnet-core-CSharp/build/linux/restore-packages.sh b/language-extensions/dotnet-core-CSharp/build/linux/restore-packages.sh new file mode 100644 index 00000000..4d104afc --- /dev/null +++ b/language-extensions/dotnet-core-CSharp/build/linux/restore-packages.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +# Note: errexit (-e) is intentionally NOT set. Each significant command is +# followed by check_exit_code, which prints a descriptive message and exits +# with the failing command's status. Enabling -e would abort before those +# messages could be emitted, making failures harder to diagnose. +set -uo pipefail + +check_exit_code() { + local exit_code=$? + if [ ${exit_code} -eq 0 ]; then + echo "$1" + else + echo "$2" + exit ${exit_code} + fi +} + +# Install .NET SDK 8.0 for building the C# extension managed code +# and for obtaining libnethost.a (used by the native host to discover hostfxr). +# + +# Check if dotnet is already installed and is version 8.x +if command -v dotnet &>/dev/null && dotnet --version 2>/dev/null | grep -q "^8\."; then + echo "Info: .NET SDK 8.x already installed ($(dotnet --version))" +else + echo "Info: Installing .NET SDK 8.0..." + apt-get update + apt-get install -y --no-install-recommends wget apt-transport-https + wget https://dot.net/v1/dotnet-install.sh -O /tmp/dotnet-install.sh + chmod +x /tmp/dotnet-install.sh + /tmp/dotnet-install.sh --channel 8.0 --install-dir /usr/share/dotnet + ln -sf /usr/share/dotnet/dotnet /usr/bin/dotnet + check_exit_code "Success: Installed .NET SDK 8.0" "Error: Failed to install .NET SDK 8.0" +fi + +# Copy libnethost.a from the .NET SDK runtime packs into the extension's lib directory. +# The SDK ships this as part of the AppHost pack. +# +SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" +ENL_ROOT=${SCRIPTDIR}/../../../.. +DOTNET_EXTENSION_HOME=${ENL_ROOT}/language-extensions/dotnet-core-CSharp + +NETHOST_DIR=$(find /usr/share/dotnet -path "*/runtimes/linux-x64/native/libnethost.a" -print -quit 2>/dev/null) +if [ -z "$NETHOST_DIR" ]; then + echo "Error: libnethost.a not found in .NET SDK. Ensure .NET SDK 8.0 is installed." + exit 1 +fi + +echo "Info: Found libnethost.a at: $NETHOST_DIR" +mkdir -p "${DOTNET_EXTENSION_HOME}/lib" +cp "$NETHOST_DIR" "${DOTNET_EXTENSION_HOME}/lib/libnethost.a" +check_exit_code "Success: Copied libnethost.a to extension lib directory" "Error: Failed to copy libnethost.a" + +# Pre-restore NuGet packages while network is still available. +# OneBranch enables network isolation after package-restore phases, +# so dotnet restore must happen here rather than during the build step. +# +# Use the parent repo's NuGet.Config which points to the ADO artifact feed +# (with nuget.org as upstream) rather than the submodule's NuGet.Config +# which points directly to nuget.org (unreachable from OneBranch containers). +# +MANAGED_PROJ="${DOTNET_EXTENSION_HOME}/src/managed/Microsoft.SqlServer.CSharpExtension.csproj" +MANAGED_TEST_PROJ="${DOTNET_EXTENSION_HOME}/test/src/managed/Microsoft.SqlServer.CSharpExtensionTest.csproj" +PARENT_NUGET_CONFIG="${ENL_ROOT}/../NuGet.Config" + +echo "Info: Restoring NuGet packages for Microsoft.SqlServer.CSharpExtension..." +if [ -f "$PARENT_NUGET_CONFIG" ]; then + echo "Info: Using NuGet.Config from parent repo: $PARENT_NUGET_CONFIG" + dotnet restore "$MANAGED_PROJ" --configfile "$PARENT_NUGET_CONFIG" + check_exit_code "Success: NuGet packages restored (extension)" "Error: Failed to restore NuGet packages (extension)" + + echo "Info: Restoring NuGet packages for Microsoft.SqlServer.CSharpExtensionTest..." + dotnet restore "$MANAGED_TEST_PROJ" --configfile "$PARENT_NUGET_CONFIG" + check_exit_code "Success: NuGet packages restored (test)" "Error: Failed to restore NuGet packages (test)" +else + echo "Info: Parent NuGet.Config not found, using default" + dotnet restore "$MANAGED_PROJ" + check_exit_code "Success: NuGet packages restored (extension)" "Error: Failed to restore NuGet packages (extension)" + + dotnet restore "$MANAGED_TEST_PROJ" + check_exit_code "Success: NuGet packages restored (test)" "Error: Failed to restore NuGet packages (test)" +fi + +exit 0 diff --git a/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h b/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h index bc343740..a32c41e8 100644 --- a/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h +++ b/language-extensions/dotnet-core-CSharp/include/DotnetEnvironment.h @@ -10,23 +10,34 @@ //********************************************************************* #pragma once -#if defined(_WIN32) || defined(WINDOWS) +#ifdef _WIN32 #include "Windows.h" +#else +// Provide the minimal HRESULT-style codes used by this extension on non-Windows +// platforms. Guard each definition so we never clash with a value supplied by +// another header, and parenthesize the values for safe use in expressions. +#ifndef S_OK +#define S_OK (0) +#endif +#ifndef E_FAIL +#define E_FAIL (-1) +#endif +#endif + +#include +#include +#include + +#ifdef _WIN32 #define STR(s) L ## s #define CH(c) L ## c #define PATH_SEPARATOR CH('\\') #else -#define E_FAIL -1 -#define S_OK 0 #define STR(s) s #define CH(c) c #define PATH_SEPARATOR CH('/') #endif -#include -#include -#include - using namespace std; using string_t = std::basic_string; @@ -57,11 +68,7 @@ class DotnetEnvironment const string_t ManagedExtensionName = STR("Microsoft.SqlServer.CSharpExtension"); const string_t ManagedExtensionPath = m_root_path + PATH_SEPARATOR + ManagedExtensionName + STR(".dll"); const string_t ManagedExtensionType = ManagedExtensionName + STR(".CSharpExtension, ") + ManagedExtensionName; -#if defined(_WIN32) || defined(WINDOWS) - const string_t ManagedExtensionMethod = to_utf16_str(method_name); -#else - const string_t ManagedExtensionMethod = method_name; -#endif + const string_t ManagedExtensionMethod = convert_string(method_name); const string_t DelegateTypeName = ManagedExtensionName + STR(".CSharpExtension+") + ManagedExtensionMethod + STR("Delegate, ") + ManagedExtensionName; int rc = m_load_assembly_and_get_function_pointer( ManagedExtensionPath.c_str(), @@ -85,9 +92,16 @@ class DotnetEnvironment load_assembly_and_get_function_pointer_fn m_load_assembly_and_get_function_pointer; string_t m_root_path; - // Convert utf8_str to utf16_str + // Convert a std::string to the platform string_t type. + // On Windows this is UTF-8 -> UTF-16; on Linux it is a no-op. + // + static string_t convert_string(const std::string& str); + +#ifdef _WIN32 + // Convert utf8_str to utf16_str (Windows only) // static string_t to_utf16_str(const std::string& utf8str); +#endif // Convert an int to string in hex. // diff --git a/language-extensions/dotnet-core-CSharp/lib/libnethost.a b/language-extensions/dotnet-core-CSharp/lib/libnethost.a deleted file mode 100644 index 9b333a48619ed1002570e148f9a916d5ab2c9dd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178378 zcmeFa3w)eqnK%4QrcFXAolum6oTg9?6`N+#v`siRv;#AAQex8ra&VX?lQuTV#5uG9 ztrVIlV~EyW*{uhBtKuuT2fHd-RInUui@H*ASL@=d=%UgMDjpV4(R}~^eR$@YXXYu8 zmi>0W@4bJSJooc|uKT*L`?}BPITxm~-Rm!&u^?P-n#8NN%m3kU>!KyCn&ieAj^n(? zan3lt^thDkH)7gQ(!Bjr&EWf%jo9=B)Cf5xQCx_D6TxKxU-F4Mm;osBf-vo|Hlnk=KMR;=vGF9GA>;YcLXyf&5V>rUqK*}lPb@$RmC zB+|V;l}+ZeslI$J-W}~q;a@b`*|jLy)tV&t*52VH*e3JKo1+^EbmbRsOn2uq+4xjA z!vpY+MlH8;;hWo}@X6k6W+2&<$qynl*-R#X7@?_DRCAN{D?Qwo%R?ba=rP*axv_I9 z0eiVvS_B<;rx)eY`NEJ^q(&B}2UBbN(-Law8|=+QJ6CkyOsS2-bT*rTbY)nDOp8MF4;Gj?CHx+Ej4#7PIh*+Ff~Ua zsiC3tV2{chQ;KGU41Ag+-I;8E-i&Hbj>>ENnr@ISz=2TeoQ)5ZNS~R=fzQbhH+`>FXHiM~wv`R3U0y5d*$kr@b zPCbYV$p)EKRT4K%l~47m5LvG+n;yt)OshJppQSduO6Jl-scZ_B(v&haBc?f<9_mL4 zon(bLB`)1886ry87AecqxjbkRVe z9|c@`W4b@tpUG?}AVaBYuZMmvRFd{)5tdDv?1p3t1$3@w11P&w`M%6xZfY7_+gWO= zp!jFYqXjJwczA@`Fp;)oM>0L2lg-*xHzY3%$FEqmLbY(B8w#vfA5#^gpcR(ms9l@&~?^-2D%`Ao8a%06qzOvSBIzT~Rfm|#66-65i>KFu^e zl+%%cHb!zSn(o;t7Jg{2kgLk&HWzkh27CL~sTiM{QZ>`A{7}DtyWc-`u1>CQO0JF| z`yh3`8AtY&o?`s|0?EWr1k|b}dm-x2YPc)UVLdX1ifi(=W84 z+qpfNO@sZJR8O*y?JuRht|sMkD}834kA12f%C=-rI@gT`2Fp273^VoiXEu3KZ)9;k z4X2sGe$~Tcs-0@4f0;g|+v{7_9PR7to0>|rwo5(8!{G(>dKq~kRwXM}xRR&e&a_Ic zmmDtX&J3WTkXB#NATDqM>{KxRdbjVw%}^i1}ntDDTH)l0=_i*1*UC}E&abSD?F3ln(0CYE%kal(@*S<_&vK0Nx zB1bSW9|o_oG^vI1LMkz{3#S`IY8x0x4WaFt z>X{;=PFDW9K5kXKx-&yJp}5i`1BcC&jp6Q1J<%yu)g{ST1harn_aHZ@K?c)3p7PL- z33T&9499TPE!kV>M-lz@0*2{&`_fZYrz>(3Mb6zcur|{l#qg9Gf-uboz@E-D#e3aGP5ZDb$_La!EB{ zQR<6NC3!&Tg?a#Qm_wLss3b9roJLAlz0$+xu2S;C^5`!N_UHkUG9TepbgF$&<%{k+ zt?SRMP4(M>WiZWQ!27UAwHKSdB}>v4*?GyO`+E^5>rnHivnX^-GuifTZHd0JD_Plz zU#PNv-yq6}hwgZ`-0t%DEXQ@k% zfpmU-rswdviduIO>-RD-r#2knhFuk=HZnD1PHfrlOISmw!gNY)ZuIL!d5Mt#J3>YN zhhLC#deCoZt-X2C;v{D%OM`1HZx1s-{-GiB=7mOGs^bl{sXDqZJ=KJNNcFuI9v(V) zkv29N3WJ>P<22i*%%;UHx=%`BB8%}kjqzKQksuhB-xju_37|3-NZ8s{^AH$A%nqRC zdo$}g%r|2eacb3+89z`eiS!Nj<@*%ZWH;gi4bB4QwGKUx=q&Iq-w>-es#%-eLr4bzI55R9TlgCpjl7+IqYng&*BD;CC#3)~tyYf7X6=`&I30+E;hQZhiJ1eCUad zO*`vcSYpNK{@CdAp^-n~C0_iOc=3gJ@h{KNZEXA^KD52~NXO{Y`Lp6%FPwYArqkNL zdg~Tviy?UML{LR`E4qX4fCRDe=x*@c8QUA(MGkwSyMa9ro1C1CjTd$*!javf(OS@g zYiQ)XAnF+FpWDze`peMBda@|q)vCyE*byt<4cR%d z@y**}9nS6b2mhLO6{3TW zLK27%Pr+kvbh{RRhm#mjZ%GtCrFiZg9foK6%N9_({>R4G=)koXzaATVfW(QmU*xB2 zsm4eL&TV@Q(*D)0P-1lCm-!`$v4LIj@n|4E7K#_^+P}ITA38>#&z}}+dnkKSH1afj z*_kLlpD6xER^|=(k7ex1mM!$p*E-yY6yY=*TDz-Eu{cM zCz{phjpW$2H#BnhUkrnTABHVf+?y!=DpCBY;bCGrx;-}5v2D+^00LZq5>>J^2~Mlu zBr2tMSIwc(JHhiH|C!_zFYc+3p>X!Zywc~1Qzxz}y@A*_5(}@JSTJ#>;^_LAKUE1n zaeRrz#3?0vmFOCKfXQ6BDvPp0eWLh+g0(7&d&{2ACxYPsdoZdqRDAyQ{4iBLUP4|RT`EY+amCJ|E>T%Yv zq6q8mMwlZCVe~JxlkjzC;>WtvYwiS44Zq(9 z|F#dl0D@KHv)Bi}*#|%1ga6nE57$)pH|2xh>w_Qk!JqTNr$MjP!aL-H-{OOB1AazL zDV?Z12pxaghyH#a{4pQ=CqDQeh$C!XQ;SSmEnLR|pHox%VkLfq4?R0Yk>=#(i7PK{ zPb8Bolaa{6+$Id?WHxaPOSf93=QJi7G2dezjS=VaWy{bX?oJPJwg#(?u+r0V$Q9Nm z<4MsJg%4f%L~AnHmtiZ}DKB@quGC#cSh4)@m8{=eRKE7mvt;uKFQbGOu%=1(Bwy{i zRj$X-orCDA0W}v zPWSo^$=+07zuzKjE|E?p*J7A<5WUuXH*-x_z6~uFy$V|mo^<6~`!F`f;qR6>vKr>@ zs^ZHPp6tnGlIyXWsh?tyJ-V&K$e@Rse0N$*LBuu!%fOe=a-_My;4G?G%UXP?!ATzy z`1J-SJ&HJ&e!am-&r;0t`GCOLueA7HfzKBBvjS(A)zS~5exf0Nw#6)do50z6wD<>o z@b3s5TPR%mX9OO66u8vemj%u~fc5ul0zXaQdj)>F zz#sR)r-O!u@`v%W{B4Vn_#AY9IA|M9K>`<@XG%Md{F*d0`pvI{2w;& z^q*y~*Z;i+GsU=}cATFW|0$a^-uG|eBQb1oEHl=9z6{m0ge;$@V5g3~{$FDXIV&$n zl(Fyj`6SS4;ooJ#Z_^pWPk*dCi~j}~WeIxX@5Yz$A5N5c7XOw19{2mJ_|LfW`hOfe zx4O_A_5bx{g?#CYm-bxk-ZpDoLhF8?i)f4W-@<3YeKq|(W8~jv2&q4|1Fd_jzh<}( zCMGlZv;{xcG?|Pi>qvg|%FlO?>J*Pw<9@eE7;M{dbj#~M>r`*rA%ho9WxIfZ6UOU5 zgIUe}FdM@#OFa%eug(HFN#xEGsp+Arb4HQH&CPoD=*AhZbk1nUVl9i3M_HGm&2xs* zCDRncK)EO~)Nm(~Ycy_izj9owY_eH(KbynUVXLyVX`(SOD?N?|9)k`u+>lC{&0i5^UDB-(y$CQG6t zPv_5!k1Z}A{ZAAzwErXJ;`4!njff3Q)WF|#+1wLk*#TL~CUiJ&kT4TnBfIk_aAqMs zc0xJ(i3>qy=c@|GikHv`YbQ9u{EHg+cBdl>n5WSL`tTNWM*qMWgpRRuI>v6P$5d2d zW}^61BJg5p^nNf%6xEDKqWDaF>tsXr989*5+{~=F^I3WxWNci`o)kMUH8hz&6)pmW zqcDdxbMEc+6SLIZ$=GBn$+vXUR z6Oi&_n0PGZBfKG3*-6P#&Q;Td??bE=4`7y%o`_k($dl!%eB$ps4%aZc%cqMgbX1rN z$f&1q6YQM~^thc1EWGRBo$%GfnI+7Be_~}!h1M!|&%{L<@HoF@;w>7tYPmwOnpjru z{5{r#a_9L|H1+Ez8Wg=QdfqnCJ#mS4KcsbAIECuOv?k|q3k#`DyzJ&n>%;|yw@Z-! zv1MnSQB1HPkXW8MrzXnmEcCN;%GV0^MJp;rycJ^?x|Zd8wwu zaUKhf1^#rD^DonmJ9w1yhiR`LKg#*X=_h{WC}+>~<6f$FelUH}XX~9Ub)iok{?V|a z`Vn8oTIQCEfFd_h{JG|AmI^57S0_)_GiHD+JAB)u&J8qAS zA-|&l<8*%UFE(q3qR%M@(UIrz1vM1Eu#Ab>FR?LI76Dc%sx%Wn_JArJ#z&h-E^!7C z^h{E~LnEidqI@~{di6yWE()N`&?J?i-c?Re-oH}sMxM-vQNSqWk2OUj@L1MY2Y(BF z#&ns8Dp*GjmP=@Q<*`kQv9X2aZWDUdMWT403XrJibI(di+iC|@=Ub7;8y>W`8OaODnX7ll?yU)Vr=X<1C>`4;6vTn*c6fA z=3BRmSdJI+IvbhvQ9>(+J&^f&23&qcd^kCD|YN-!|D{( zu&PJHYL?rs!onNduEM$?He|;-c9k`!Ht$kuGRco#n0I4A33Pg5yl_vVSiowQ=&nTD zQ~9~z#Fu?|-ZODJYT2;@B$N4b;vzH?rp-k&p%#R@LAZnAM9X2PLkKGipv?rC(`g>)djvl=e5SW}ACwmo698wv7;yEBFUp0IJ(o54Pj#&B1fXCHu$ z$EMh|L$EPyZwglpx&GuUn*Pjf3G+)EH{dt-V3z_wD(9@+j2Z1iL+3-d8SUZ-1`P}i zg&Cx<+3l;q#+A|4E)#{!NFW4*;Q?%PI)BFce10f*{(=Rm4XMU~+ycEKY613?&+kta z2D{hK=PYKrXU2@>W}~svvSMo1N1Z?8tlZgfK%ebClDDcUiS%3x!mRywDmL7mT|;*HC^1`vFH^|%--N(&x1Eee@@PbQP4#fqSMM<5S|48(c4x6w(sY*L_=mn897mwYvR0Am zBU5)(nGjJ-P~p_zP1eDD9BQzZ+i$`+B?Bp_H`5QUIcGW6q^Y%NSl^5tmMZ6%$nyHZ z#Qe&VBowR?pCgakI{$pGPC#(-*+O?dJcuoVN`Y$8oG1gNmGh@G-nDFDV4Mr2iSeIZ zOSWw5wD$#TmU2bgT)<2D4Mq(QSsJWaTMvRLR+(Lj=bP|-y`plQ6rYyvvaM=OTFvE# z_zH5>{%$w)5lR0F*TU_B8_Gtr8NKppv{*mITDY2yM(4Ul2me<#IyW~uS2sGH>UE7m zZfSJh(devebh^}EO+tN+BSkbiH#Ry2g~kXr)Zp>vM(3JFXJey-WC#0IipK_oh>Ptm zZFIUDotn$=TcN&z6ux8{9TZ8c8lBZxgWE_koA7&z7MP15xS#C7%YtBinOet3H?L#m z2J`QDmbqE6we}9Z^6%<^5r%ZvoE5$7>@~Fcn_&4YpCY6;0nBpSISI}S({Nm$_?ha> zaazq&j|HCKTxZ@@n*?^IEz<8dxE*Rf%i#ZNa1|EVsT|V(z~J|q=MsaPS642sH6;D@ zrekWyfSHzw+qD9JXB?u*sqp!Reu2TSHF&qd?c8w>@EUg{SjQEee9h3?^lSC7&*1+n zNGd&NtZf|owbEI{ah_1tN}O=DwT@2%XSg0Ta@u(KmBD{v@G#zKUc`^|0VB^+f-p6R zVB+ltpQ~YKroqz&Ki}YdR9jOS=x!U@iRkyDNv2gRCF>wsha zdNkf5rDxDh1tn~`k^gqXUyaM*^Gbu?Y4CX^CkPiuwku6gjdEARZE0Hib`AK~s8uQ& zQ{@?~!^&UIXlR31*96r}rTeNZ?*YN=8kBypr^ubGKCylVVU0zRjPGhEuhxiVoN`qH{v zy$-jr*&++AWs$Wkww5KvV&5Z8*4An*ZPpTra9yNl8L&DwO1C@J(W5J0{W?%}Qmgf( z#ahs6uUtQDLKblY5^+Nku|a8!xS~heDtKVQjasGL7-_Ef<|-`WYAn*?U9-#{f^Fz? z51m?KDHbj(6-lBaykPe=I#txJ1J*~UmM?pK^-;=2%&8n!9t~pKq&~LP95CiRu!`qD zO(o6fv%CHaHu=9+d+n#DRIxJLU8;Pf4un<8JVLwT^)X_0_cn(%+>P5u>|6u{#E+M}S3r+K;c-};22 zBfYEJf0MO)(3M#&9&rzF2R~EC!UGZ)Jl}q&M|YPvgdpq znF9eVxBt3r1m1(ykMw!r(7D=Qug+>X;^$HQSL{@9oPN`;LUY|Y7eBjZdb7aUXS4Vn z24_=_Ka2mh!O4f~wJrWJfuAREJI+9Qj$c{&2}4ib=%TndXYFXb{Qs`%Be?ZW)?`rvQ zk01@{(S>mF2_O0w1l}y@my!Vt<&^sA6u8vS8i6l_8!L|;pCO+Xfxpwx^MS5~OTSg% zY`a@N?-l$P3H*x!XCKDWKP+&LMOpk`1aO#<3?v~H*1-;DgN0AVQ@+`y8(w{DHS)R`` zI3FC9wDe7aUZxW}j!OQrUjC?{m*M)H;7_aN&&8`WOqoh1CUoh{`5a-@6s~_5F$R~!M#TnN$qz~g~aXW77<HxD>=`G-O72a!ATz%_=yH*8FPietsWSz6#{2ll!kl~_*wqM zXc#?bDqHzK;wuFdHg@tqP2kG~K1blTo+bSm0{^I>m;UY*c!!|>p};AVH9s>J^0#S* z&~`&aocY3`igC|ucc?6Ajtvs*YMnhR}gcrrSFFd^btOyq_J`vvlxw2BUNGpGh+G(4uJ{A z|KoQ?qu4>a%GL?8I9h66*mtH0#(60{ zhmHKrk_dL|o@c2^W8K^5_wb!wdB~jSOrdV&wLI**eSQZHtEImtqc~emg~5P!>(1i8 zfP2ak^rW8`U&enpQRb)Ce~zVfUy`J7i*oS@ z?RxXRJ|S zrG3Uh)H*azul(vPOZQ~-m(NR)g^Qc?;pneCFWqC>g%n|_l4B|6XXmT1oQ3zZ73S_E z;~v7&9<*Zg8EnlvfUSA^u(57GN%uq#U}K#cCBs%hy+IAz%a&p1n|>MH{jSB&kP_&8 zY{9!nyp7B=MJ1n+v$m>G2RFa9gd(MfMt?+p zw?0a?YG)>n5a@*a(7bW(eGbjLb4WdI*b0DuroAoGqvPiU0}Jt^Ap_4 zX^!J4MxR2CYrtOF1F>E8;LidQZ~JZHLee@r8hL)=*xqdxYM;b`0QrXY7xt(W_l!<) zKS_+KgoK0r_v1WvyUX@#>;&4QIE?N~QZaNRXFzzx1C-LZ=Pan37}x@!;Kln6@Nk6U zood^Cah$u{p;op1yg1J7=Mb(H8bV&BhPcfe8Vak2I^HBS?^8?F+dpU}s(tmLdFm9G zl4#V(_~?G+7f$LK?|_mz_QmcmDXD{7jCY34jp9g$=nmMS`sfxY3&gu4I5q`mqNs39 z?oM>Sm_OQ{b%T>txOe&(Xem_Cw~fJ5F4j|;fX6H$o30p)#)AebUA|9z+2-w0N+qgrVMyV4=}&#LC27$gwZ)i+i$w z;-JdqW1VlHxcWxTbnL5myynU3<+1ha&fPaWa-{O>wAVDhPWzvgUq$)Hy!!G*V1C}z zO0!o}f+7DrFZ0i#%CGp?#Vo>X>c=r!rIcE&{3^_pC9_G6%uH$@zFU#$+Z_~c{~f9H zSM!>tzncG1=?`ZN*m7>EEiX}6&Oz&GmVNhWFR8j$#h;1hm~BF}Dzx;fjvlCtP{yY| zCqcJfjH}xj-?8F${n)1+KY};X%R<|f*zVB@hTkhc$ACV-EW2`5Ge4H_;;Y}{tWi$G*yNrWN)Lw1J)mQ3 zj8~VHY|7$(sljzx4)1?6oKdk4doBp=7;QU?ZM%>83bhaWI=iKpG|LUs20ks_3Q*vc ze0qq(9EuO=?od=%pIGVNe@g$pVCd9Pj`h#%;U=GdG2aj6Ul=qm8M-{5mJjxN*Ys)G z8Md&gvgUhe?A<6!&G(b!_tho$H=6Hi*h=_e-P4)h-74qe8^c308+@kGVtHJy;^8i< z7d8<)`5k7jm$pOP!_rsUjZHTr$_3Uyp_(q^ztY{m{e_YX_z_;%GN;2ccx8ClN3dd~ zLXSTLoH>?6tzF>@(+Kg9`NB13)>PXE&c3{!@v&5;XSF+B&cx?{6V_paU4;=(u8|?G z(yX%MAIx?55vN=<_BjAM<(#Wfy{3`YjRxl!2@N}EG`HYKdaePYQRxHE+wmj5iXeFtR#|M=Y8;peemxB4=V}E|M}hJ^K&0Qzw*JK2QK&JvYeth8qaF-zs?7LgAYE}2d6*P z_(y#3r9Sv&z~%m7%k!-Yw{NCyM7RnHFHN$jx)AssKK%b$(c4oeS~SVC#vyW7yp=?CxJ)RN+6jPbaV*OK* zrm=YRBSgju=1gQkXpK# z!e$Ea!D?%X{i=4s8W)>zUKU#G+J#HHsJFk6TMr+5m8HbvmE3Zrx8163q$Yh;zuV`v zxbw%ZyJZ&V&#q@}H5s1mu`>l86*${kmVTwcnfooy_8JZOaE`>{y#!(CCyE9aXFHCD z^qd#7^lY!u5I+|`i;ok8@#6ogVTE(*#?t?rz$G8n|1@4cvrxZT{Ee_!K5Ez&@05QI z35>bSJS}e5f%W*{n+#5W&%@91-(qn3`6hwC*Wi{v-O=cFsFsJL$Cl5B)mI%Z8ISiG zoP5|Cwe=x?v>|xfl~*T57%wcP;M#DaYD~?@y*hoWN^!$ZfVXD z^fDgjd+2RT`(l9~1NWAHRPbS5uy~iiCI7$j!R0#XIpAac{j?ALg94Z7^AUkF?yWq_ zVW+Wrz^Jb>pU`jx?u{3l4xjeWTl^0OXS~RCSYza|{_;D`u|m!!yjwX>6FA%A7PtFU zEg!3&iv+z4@7L)G4E^Jh3d_fi9T1oMJP+cXhIk8pmi|u!VZ3~To^V+`*z?k?oED!a zDHooo_^fF(3!{A8J4{qhR z>BQ>CE2qW(yOpa)dBVlE7me+qdc!qWe|GB^8!kJZMt#b7U+SUfcN*Dlycq8`URJ2D zmGRqUaBq1c+mn*dT0t+<+mOL2horaRV7O(ueXO9D<+kmSd&||+4ZRn)>CnsH`b&8v ze>GMP%*)3cUsi8Y&UU!7xRsN7yHwy-PR8#tfwPTALwT0tXZaALVKzHW*~y3qYX%xh9wh85F7+i6&B(!GqLli)Ztd$W!(!u zgdcKzp&A>{ctJIi@}YyO6n30IjUpV9Vxzh7(cWBHZBU^a19yC(+Q&nTF;pk#=!s{z zK@c8tcUN9thRJ(7?}1FTQ0LMV}7=I)IM2;FiXlj zidVGUyBdb#2_xVQZ>MT~!5`9B4$n*8?|kDoIRDL?CMul$bztLFcHqi8#C zV%Svp|0q6G^MAXktfc;quHygG#(!`8zholRrW5PG5zD?hT%A@;{;+B2c=hFdKN^m! z$zLDVUReLBKh}3%{VnwIzsdOja`D&bGK83IEw<_4?280Q^;if2Z;P z<@EmnAOG!?_jAI3wgw|olyztEtO>t&zvA{+C_m5NSN`KEO;Z*B33>hhpz&Y2rG1BOczHfpy<7NR?cP>% z7Ixh?dE~PGTf)2Xy_)`FMt(bf&-8aLe%8I!-{;}J+8(R@rgLWd`_@0N{CxL_s7MbO z_xG53_)S31z|ZSHi&<~lA%iWZQ+R?t8ljdnI<` z247wf_fnw13*tiV1#y2#6o2{*g~BauAXGQX?c@b<>dInshcNjnB3wU-8^A($?p{9f zTz)3J+G)3O=`B3=3fEE)xP@oU^5Sz?Y1=CH1eFuCl&!o~_qAa^P<3XW%?8*H)ZpxZ zhIa(s^fxs)CWs0*rb53ZUxj94qtAy%*o?y6e7FZKX17IP*9LC4(>EN4HXd5Hllha? z<$HCZFCJ}Fi#6-w?!7YA3UxkOuD6b{chz&>RoJT4ZcB+3e-JP3R&j9f-3T7{tXvfz zt8L%%&brA3`IQ~x>jLrds~Ya-cyz37e|}ngY-K~d?OPj9Y-bY(>4e)^B2SnNWN>=v zo@uQMI*PsZ%Dx;OJ^DToD4`nY08w4y?d{~K6(kG*tdjS38 z_(~ba~chr4B=i>oU;Mn-*&5NxiFR{%kF}@l5qBi3;MBIV5sUB$> zI|qsf5`pJJqn}mUD(pfoi*Nml&Q-gS)OVF~mAXX{xoSVR-XP}x5-50MUf%|eOtlNC zXdiOn?($m~bzEY59ayWy?sj|K_-6e{<$~R%Ym!UyUl4t&(5db}gtjYgKYXfCMV-EU zJh@5j-Dz+*8U0s*^G0f%2Lhr2Ont`2FP)5CaCv=hPE21wZSJ8Sc`Om)?UI+YPwpSx zjXi-kJQBeVx-1#xzWWJxv3}Ke+(e3731e;F&rKr(!`@tMJ@Qyb2-*y`#^!y$eeyfT zBkKeCxy%7pdykR-%6jd`<5m55LivFk+v&~Y!W;4{?vIUKRUaF_0$RATJ~ubUo839_ zk1K$u^D(j;ig>81BIv$!7ig2`0($o@(DFMnFUMB5y2RMx1Tv?&9`rYfz#l@RTj*zT z^GPzx$Ro()P40FwnJ5obOL%^l zczU;WG9SP0`dQe^`!*EdKMBl)?Uw;h=cinM&`a6<&XDX~^-gHylh8{X#gyJktIqqx zX200phgp`XVk=&J6v%^(AS{RXpn{ytZ|-RO{l?+g_%iqq>=;|pQM#B_YyL&8`Tefu z_oKip(>wsB`OwJ6C=D*W*SD7HyIBtw>9gh^+qYPi{Bp-f!`Fcfv^CtQ8F>o{k(oYe~v1bY;AuM_7I~2NEClNag?%_Ham`C zM`bDg%cJx>9peo?F^(pHD!i0h)jx*C8aKX7`{qPXjO{lu_7ymY7k_VJEWY>aj42aw zzwpEm_Ci)h+~l74SYj^9vTr2j{Wf&lgNonYj(IP1gf9JaZ0n1YiO}*Ftx|*7`q=hI zX!Va}*=Z*nAv^7JBXyrQQUU1f6k&uGPBj?U(x!)P;mO6%OH;+jj?=RpAWag?9 zeVa%2`f8&f#G$Dy%K02RG7vl)AFL0(aq*Oi3pOs8dx|eS$Yo1X`&4B9U9s`bKnLo~ zc-x-PySXaI1V26&iWeV`2fkl83aK3; zTdJw`CL4*RT#0PKTv>PrA`d=I>TKcX(8Q2n}5R;B*6(WUZ( zjH0IsCz|}?#^i5IOe$I%KlY4*YrOddw?*$pgMVjvb)avHz6~8MsG#h&Xm$p~ZPDxu zY}1{AS9V+UE>RwhkBb>*cz08`3y7J%t0H2PPtX%7zG32Q|$0D*XQ zcE>hd*Wym?@y*-rrv@WWwC`e;Q)-NjcQwR{xOBZKR(vS7^&#B5{%v%)iVI_7I7OkW zVWKwFP#Ze7CN|btk9Yj)V|$m@!@09(X#+ZQ`ap+^bet@%5CfvSNjo-{t5*`J@JS)m zu?*EN;oct_{U>;(bHO!!1y^YF4ibqfLgt43g}3{Ex=Mb+9(~+C&ko@mCT2|>r(SjV zX6Wz*HH1E6Rbg3m>b>q-x5d5c=}dm4Be2Z*5|sa?z%jq54Lnd2`f{!F(cr94)jFRE zu5g_H3?BdKT4!w95cWMxd;RuWXWR6YEvYp>S*dupEfH#SxmL+Pk< z$1yf{DF1jiit!ok?2jB1nD+V^a{`QdV{HEK>MVY@z4!t;qc$|Br zT=R7PS~VPTnJMoRbiEf+zCza-<@!3~I;B#$9L#_>ZVF!>dT{fjEWd)6-QIF81cJg6 zMSVI8gGZhHv@C7r8zuUvXfZEr~`VASD~2*wW|Ut0M|%B%j%_m zoo~f>L2$Dnixmr|z=}is)h_jDQ!j`^`vtSzupe&L`rH`xIud?}lSdA}EN?q(nbv~U3GSsVp za(r(b-FO_Of?nt@GTRf~jjHVy_N+dHSJv+6M<{Bno_NRh;2cPWyw(P2^+M{ne# z?UB&P27E=L+A(ojS@|5>`bao-!CfQ^ja*KeVqseXHQ88q`9LlgaiQ2lW9B(kZgQ~E zzcKT4ey%F}8cf-Dygh@eqQJVntg)ea+5@LTqwJc-#vY&o5@Q*}RHh++O&kXh#fwoi z(@?{XrMI)^iKs%)^CzLvxkgSU&>n01aehL1JMpxQ ze2f%spZJ)E-`y(3lzPIR&!dO>TzH$T8lW5PAv)=gOq@MYr^*F!a+W=KOD52l32)_C z%SYZ4Sm~_BX5*O;)H&H%pRWsiAGF{-K8cFLRvDm{J}XK9A~|yKGF84{5&*kCT4|B zU(P6?zW-EJ-|?~27^kz5y-SUmgXCR$EgxMJ?He_l(xkevW!2(=*xt+P6_2snJ$K1b$ShJZ}Sh*j+HR7|@ieS_nN;XsyyDIuyNIx+yKQ{-4E#-iuB`eY^550OLVRC=c3}nX@&C4#ATreFFZ#DXW8x3&WTSx z+R^s&!he*+<_TtpGXQDRyPjR`)bo2K_DU;ga1}T2+8f+H^q)^-d^hI@`$YBkCd>*hjs==9xI$3S2GkUwYTp& z8Um|itw;nl6yYA)c%ZNd#cHQJ%Ig+K*8h^$7%7!$amI~r!12)5^Y+LD0ahB*q1j2 zAPw>uOS#Rv^bm=dMo`0Hl&Wlsej9B{E#5M*qJqM9Q5kuGIgp^FN$8+D7HC^N&|V#{T>_6o$As z!TAuf+lK_hQfjB`F*^E@KuZ)fZ|A~^7GDDQREgO_CDY&wyjT2|BcskXqIq{Pn zYW53(Ss$u#9u8n=aJ*(FtRJf3x+-hD`u@M^Uw6X(yQKefW5sVG3!q;RFTPYt9(pYh zN`_x2iWoNdql$K9XpyT?jR=ELLo)GwnImo*nWO zqa4CQzSJ#P`{dDzF*B%6 z5%B>N6>j4dN`O1>ap1PW`g+9y-MWUF(6JZ>s4@l^8vQ%u6P<&H)WONRkzxHQM1&Uw zRN$NU8SaHtDXw_dc(0Yx#*Qo4Hzpc1Qy0;FIl(Od`H9)?Seq69e<@C7zT`iwFxGje z^1zUD>i@0AS$)ib8Ygwk7iyfrnKjaBl*aDd0-Nyz?OQH(^6S9% zIn8#lV9U({HgasLL{W|kBz7Jub?^32Zkc+WwMX9s_`cz8gbqJ0{voL*?;eZTp_fI949ruExrv_aZ~^eDlftT*VG7 z;Bw9O39KM!s6!v1mNg!S5YRoCsQ)rusXEYYoop)|j?OlW=CXD3E~n5!x%NhPaG<_- zZ}jeA_F=ZG0D=%9(1UIeK=K%4WIXj!q74Ure}Kv`$$|@+tzuLINeShOp~6t5#te<# zryOz>gFN_MW+;rhZBst^jFJ@!{&n}Y^3%jwam?80@Dv&*-Y|i#EZ>!IGnHy4rW+7* zhQ1SmzgDE`AM$v+^9dx7PXu-b-dpE9RkQkzI%i+7;XiAgZ%?cLMXmGnw4;7j>)bzm z?hCcfCu?Wo>w~p3LHUE)nLvJ4+l8Q&`rqo@*~GZ&+U&i&fmfmQ?!EkV%}wm*F!AoW z{CMbGY2v}9c-Ik%9?Vj4m!s1hhCbF0rMKQRyQ76_UOv-H-%XuKXmW{x0PInm4yef@*9gG4str=B#yv8 z^b4IMIjN91?@Th9sEMFhx?yMhp{J*>9KSFe-?|s|Vs!V)BJ5+e9b+5o@wy9L=E5;3 z!gi@;uWw}miSq1<@l)V&0TZ}-?Q4|@+;|a7efF|8n4^~L%p{Z07oGOo>n1{|PEd4U zkknlPyN_M?D~ikK)W?h8GNVGVu^TWGbU_RyQM_$0CS#E3I>z&J8z}3@4cK0@SJvxL&vx73CG8;R3QW(EI`U1-%;EMS64n4Z+pD( zK&TdKj+#vD=S3wVZnsXh+pK}i7s}m2G6^B?tqzeF@ z_sy_B%CX%FD5o{`M)8p<-w>6$fG%T%^#zNte3GX=JrbDpnQ4LV1pXP7?#HIhg#9zq z!Z@3M+Vsb#IZsTR{<~>`?@yasZ3v&~8D-h4KJ%KfLvuv$g95*vg{|G)t<6j3rx&*L z&Tna1v~YeKE{>WXNiS(zoNnul^fWDT`uo;)XR_(;{%m6|<1AaB?%u!yxBGg-%K$G+ ztO^gM^6SIt;l5lxchPlcu7&rWu?ILohGy)wEwxoXYI)$xvKGQQj*9cvP+ zal-ah(L^$F<&`Vebozb>_vOMkR*=VZyLO=Goq&KGf!~L1ehQbg(1J7p*X{R^U zpG!N-`+CBInS8jnZ?Gp!vBEul2u41Wy-B?~O_An>EsGW}X>GHfuDbHd)k*n+n}ZsA z(nGn%x8*W}I1!*Q$P*a5GlRW->#AZs>5Up+rAfn@meO_iuH!-CpuodfpU)5F&R?(~ zwIS6wkXz7`$q%OU^Qq|hsUh1wb$YX@f%K+KcEd%^*{;Qj=8vNh5*n#Q>*O45^&`|f{7972q?aOs<#CvXG zThp*J+`1U2EoKV+Ix?NLsa#)oGMC5skL%7qKiQE)`0!Tf&W8sJ{ryVe8&mzbGpw8v zVM-77IA`U~4=0mDnOykXP3!x**Q;2&xiBw$aX7!eFE{_9f4WP2ILJrC`68FDz4ZNC#9^v^r$5iCua0vGpVpr) z;|~v42TVG9-r9wdE@$XWxX;D63!x{PyZN;IR=8)N=j|Ej9OqeYXm6FY@{IL!Ew$e3 zx82Zldy>mLzIFQhroBHHTv4K>ypI~%HL$z1mucD>bH+Voc-GL3OP*4O>q>mNhwT)U z`ZSydDDf@n`$F?wr6C)B#v91#{(AGhEM3G(-``oyy}1K`@_y8OPr+XD--PRO@QKc> zZ7|`Zf8R3nYTvPyr&IgK_Hc8@Dz8YN;vb zazo3t=&qbXuQhjDYRx4tAJz4UD z^j|gfZ}8HW;^|@Qf-*g8=rhoVUpRvQRAZf}aEHO0W*&ZJZmxI9RC0``{abaKZ$k&t3c%!~YpC|B_zHDXmdgcNqR05h>$u%kpG?g|33Xv)Tnyn2EqUJ`nh;z&oD_WC|Mb|6lD+2n5{;IPY}N_Ijgm zUhPf@Rd+b51`w3mTJ27VQw{$mCVpNoTvfX(0>i5?NcdGuu*&I5ZJ?w4)|jpC&QgQh z_p>Yko~bfAc%}YTn^Mt~z6Vt)TY9IBTe3-?FzI25`FyLvw^mIj=NYQj zxi-pwzIfBtAK*bfbHNIYDo?Z(^cNai>G4xoNx$4cvrC^qf3bMeR@N>0v`hUi{|9x8 zTtdnoblNMhzW|}XSD7!WT?Bg_3MS$igWD9@VDNu1xEg_j{Un2b&fpwcr8(2!cj{Ml zu7j~6T+6K0eZC3w`t(W{axFnuJ1(Rw!Lm~-^|wnDpUcV#&0A2N98vix=;%^NepzMY z-(|o#;$zTRA>Oq0dgX5=&ac5LC&^_Df2J3jqxD1m4>~Kwo3`F$=()I>#_E~&pnLli z-?U-f%6NR*yvv#+=-z;2sWF$Xddh-=HfuPe@fv+_FUS<3} z+fZC%@OK*A#`{`>zsKNqe5T*vA2GOX7i}~+=QwG&C5UE=!MQ_?#^yV$^ig_XdZe-C zA?-nD10Q9EXCygQaH2t{pN}%bJA?wxJ6x>8*gj|cea!f)*qU!ahpmEg^HoJqiIXbm zmh$Cpg3jB^0lYnK`QVm)@vJy$1#E62KA^dM?LH?b~v^)E=;S68_4&ve?I``6BRYt?EDe-~luk zeei-0{-6&&9T}~fzgPI+>w(X% zIaVtR|2g(Sv&)D6aYb)S1%2NH>CZsMKcnV&T9vW&=J^UQm8KTI8hAB1lRo$c;InJq zVA;DZPHgvpzo)pk8hgdtcNBhF1^%oL|KIuGe*r$bChYQ2W4CyV))PO&#cd1q{R*G! z;v5^I`H{lU(J%O~#%%FC=cwv>c$*LY8Q`;P=DB>-crM=l>_Z=eLaOO`1Mt~3=ec}# z7&$+u@cAyzu_~HB`tZ3B6-PDx?*V>BO{sjFrP(+i13tT^$-L=tEx!Dx!b|-T+d{es z4WVlM-wAwn%_5hNZiiuNP~oM13fL<1l)_tGdYgi4ksxQ+obTc`ea=z1ja7_c8j1^n zSCeN%(c9EfYUlhw;q5Mej=9mi=)>nID5@I&Q-RN}S*{)9KgZ)}-Us{)QD1N@hUU*c zeA1!n{2vE?hG^%fG#lqs2s67TZr;pt54;TluO{a%MW1l#ZU3kqMNBpNYk<$LS?SV$ z*VsPlL;nvx_ipjYe0Gf<`n$WDD(*w_3oL$qeiSYkEW4qUfey5_h zy=61c0XKVm=)bM#-|q6a>FQ)uIo0Hu?}K0DgD>;J-|T~@eDIrm@DC6-dD9I9EFbfs z|2yDwYHoE&QGF=$IUjm^k7G+y#XXEo?zc$Aw}|;>Nh&FtE8SQ}jqat9E$&^9Emdxd ztODUm$ZJ=6uBvRyB~$(VNludY<*|C8kWJ^JRxsCtN@mTj7>h0S5^HI*mWZo`)`|c% zw^TxlT!&Ru;EcQrt_C7YDnC|XTVhrj;)SlJ zB5jq-+@LhOayOUa+!eRkl?V!KOf+JpKx4D(Yjc$#v?10Li*w!GIjmZ+E-Ix%8ctq& zZDesC7jPysgZ(%0YRmNCMqDM)xw0$2B$@0U9*#sJ&2r&Gyt^wOiFB_|WqB)VUp^P_ zj&`N+4`xw%U~RgmBA_iT-it}y6I~&I=VH?42v+B0Q`wu6dihPVZ!n2fE~{2dMWU|7 z$l@ZgTaU z9$W`Kl@xt#bHuDsI}#azAvgEE=v$1_rAuU0W{gDC)hoH=roR08DaI3nwbsV{5)@N| z-2+4Prz?`|vp7A%hg2>tKN?~z%yPdek(F$1SZ8(Q1X~P0N?yBSH4!e8io$?UJ@f|< zmt>h*UPXyS6_u&`usAhS@V+4=l5{r`=~`8o{S+(Ho6M#L z*QJxa19{|`n;99+G5}MLQ+3g{yHs?_e5O!`wxSG9XOVkQbRs}g;1Fpk730ZNVHk`C zaI1K-yC2F*cC#g$PiF`E22no4hvv3ydLXkgtr{m-t-Fqmhh#22l**>CPIgMwwTcN- zlO);lnG%<7br<>+l5~s86e>mIE_$SBBTA2wWooghgwc0@cNenR^dPP@M}L5qjYm{! z;aif~Mpxv`vbVtN?VBT%M7mYfih;^@W&v-qug6^Mt}Ze^LU+tJFUg~UsC1zhCBvI! zZ#FZ4``0(FnnEg5L0UB6F2P2-hfA>Sb?&b0RqnMHm&GNoU8vMjTGyOH#?SR^0K4u~ zz7GqFrxsmnJG=6WH>#E1aWvJ?-p2nZg+^HIhp7acoECu-8hps81FEr}mJlVfyBY}n*zZbB`T9%el=m>EnC76#JUzV4}|$EnuNktM1s>_uY9LyEHcu$5Vw z3-hNYQs>)HDWLg0m~k^3TW)HhziMi`m2KakTF#FGZyj_n$jTP#k&SU#9qX?PRgyPp zot+sLG|7$V1ovjLs^~POoKJDtn$IK)*f5YaWaJ9bh%jnYi7C0e>1Im#XwejVye+v+ zsiEj9-5E4OVXKHPS4WO7Mt_DP~X z$|Hjf7#F&TZKa&+GblmOmCd12P4=X7-RJ|cf;E&IRj+M5GZ(X7e`b>>>nTJ@ccezY z*d`rLCD*3cVZ;ixefN5>@5;Bity9(ygPV|u66Z9l zUw&Cc1-t@ZuJB|}E|Xl3h6Z*zY1U{75BKGrM}dzBe4oHa1^zvOzgytPnE4P6-ScPVnQd^& z`5u9D{T>Z*$>)55-;QsVk6w4D=@~|gx4}+BKJUfP;&$eU_^kpTft`l9)X!ZC)%Y0f zmXFkrl=EwXUi$ll!0!-zP7vW57x+a2mwavz`1=Gs??R;U>X~;e(lA_7&wnrIYw^v7 z>z@Su`vv~6z-4%UAaEHkKN7f%msQ|N@<{zI<)i>{~Ljy06Pu&Y{$>i+Z7qdy8< z@{#c_(Oa0h#!>b3o zev$m89_;!T;xgSfEACpCGF%-7C;cb!v*GO#xD40V1uo^U^^reeaLUisO_qO`z)uyp zELZOqcuvrhm*q1o{FQpxEO4oZ3*eT9;r%#%mJibg4e?v?v-ocb!g%p{g1?L}9z#vz zjW3>kLqk4N&UXuXsUN!zhXuG;-ALP z^8Y?T7~&tt&*FB!o|pgs7V_LG=>LzvrN4CyC=B`BgP-NUTHrEY_Xu3_xkKQR&xZvr z`TW4(eDG)e{gI%T@%4Kj`ak&4hs=5te&)~ef1SY@Udg{j;8M=ZeekymT$XR$0+;Dw zgTU_<^4NNT^2m5U3-2_E9{nWjgt?5B_t3-v+z& zSC;eA-!`B0c8kHi@iHRlWxVk4c^b+u^~tsn4f))HpOvSMAPn*I@Uytp1M&F+m*vvu z1Wp-f=&wu@&aNQ4f0;l2YJZ@k!Y*o)VS@=eC$Wj^?j!RZZuR{nyZm*v3w zeds^vL;o2c`p^2%e@Wo?3V99)T!zb@NkBa?ty=k?_n|+AgfM)_bURz%GTk!o(D1YL z*XCa@9#_ud}`%{ky z{OdmS-!M4kc~sCpE^w)bIR>Y{vc8K5`aQ5)`CA43WdgrK(93a{j|g0*w{QC3KNYx) z$6p9s$}iLNdqHF6e6ygJ<<)Hhr`^)C?x3OG_Tp#p7YM>oKm1wzC4w-dm*t5ppQW6a zDC$c72_Ib6n^K-`Lr+7Nz&R|Wl~!70xQ zfrkS61O1iq*nKC&C7;U$z4Vty=+Tf~riX5UOFdtts5SkkV7K8~BXFiYi}wgzj{DjC zNPp${;T}WJ@Lq|Z?dj z?-%%|1TOWd_w8zbC4ak5lJZFWn}Uzj|Fxi_A^olRS$XJ|hWKsxS^T2}VW@}I_*wiO zf-t22XZ$SAwiXTXt@v4d4?!5>Qcjy6$)7(x7*DUri^ro~1s&CBnN5b{gW3;b-GZmTxj%Y<)rcZwvZC*lE0cwkTBN zk`K#38m5yZe%9X)5`-Zi*?#+yLNzYq{Y-)XZ`duL^8~(4;5P_d(!b3Im-V2eXPTs; zoHAS=R%j*u_dfV3B0bA=K1bk^f7A!J^KH-skh68Jh#Gb^|sF7jF%q#W}5e` z2eGE9UXIgBe{VH>NYAuq`M=MH|0e}~pP;wr3wZTr>qqjJ<1qFNGSbWb z)R`oNAuiL;DuGM+Zxpzsm;Ll7z{`f~Zv?$8H|`d=h2iH~{4D?T3{F0>-f8!tm*W7E{(2w!ln?(AflE2J`{1&_FUx^12zr@+ zn-CrvhIcc5^?ZRjUq4j5{|oF^4{?K&J|pm}4DQwQTLmuj5&LvBUV2+@d-eYzO|Iyr zKJOK{)XzT&TZsnOF@E-`g zPvHM1aJFe_7%#WtXZgta?%Vz!=H3N9s`AVqKgn>3f=t8ck zK)F=wrDF&IQgbtz0lc8mB$nA}TH5N?w%THAcXeBLsa1=u*dPknx|Lpbi`8Gn)~amm zmhDC9dg1^5zLzu4IWtEwg#P#aWOB~=zR&wS&-=XB^PY2_SKvYO@P8Nml`NOjm>~on z__O@aFgWFYTHs{@m-({(=14F3Un=N%?_>Ed7C6TUi+@VsvYxaFT(*}r0%zT`e2&05 z1EX)k&*HMY{-40dBZPUf{Ou2yGJhSaZ(9E`c8-@O=V5R^a1J{;211 z0#7`0UPQhoA!1oFK41q_;0D*@y)tUZRBXC>RjJZbOth+XJ z3B25bIBpd91p?nC@LGZI6ZmX_pJc`_M#lvn6Zj_uzE4k-odSPW;Aw%sE^t{7 zk6}YZpgu3FGyQL}z>UdOM1{Z&ONC!0aNCD7qEX;>EFiu?;I_{p{$+vNxi;~Bfgex6 zgl7ak&Vo4Z6S(b*82vYaM+lfO(I`zjOt2u1Qw4syz!L&LN8n2ZUMBFh0Bwdn&0u1U+nmWLAdeYxqLC4-sdn?9hHwT}bI6 zmLQd%KVyi$)Gk}uflpApHB)`RlK1Vlr}FAgTnc{1?WNQD=DwI7|yzSx{&UNN096vpEq)k2%l4>T`F>w$7gGEE6G+wn&L3_UQugnyg3La? zEgQ10puI)ry0B1R5xO7gV^AN|m&{Hf%MJNg*WN4)>4(!E)`$MhvxnWGeWhH;vWLh~ zRpRap6lEd;PdqJxm9!b(@AN2WrA+1KRHvRo7wby=p9_LTbXQzA~XC7ymRF3nod*(f{=`DH&P70;(Hw5;* zFiUUqW3Nxc33=97z#PXn>5~#NM_~OOg?*ioa zAEbPKC#~}DlV4RpK6UZSABBhRh3vn}$gi>)H%EW|`MqBu`!6)|rTsrrK>i9V-=}}6 zsSrngfPB6mSSbHp1@eDV0r{T}kRLfn`L_kgzpH?JzK`O!|ASV(FQ4T`zI?_`62JZL zxAJ}IHx;md1T=p62Lj}`8~O6x6pHZ6=RQh>%70G*`4bArUxLJi>fa_K-+q5Y*$jU9 z3y{8${L)rU->(m9{FsPyfBqjZ@_p6Md3K~uXxp!0qh#509>ZuSh4kNN(%;8O5&Zfe ziS&i^-(~dQZPK#;I1@j={tW^0H=37V?0eHz1;6}H2FSmkFR>!X@plTZ6`C7=w+6^B z=U|Pn@dNVzc!2y(?K=HFeNg3hHqQO>k3l^tWdB`e;?;)wDT6=%W01a({Wo5xQ%yD% zgZ7__pI?5xk?*fRHAc`vDIDjNB}0FJ{u@ks+FS;|{yz-Rf2VngtK1}}{yg{VfAUCe zKfnHu8$o_~e*Lp1J=E}oT_&M?$A$!cezzliA^Yzypg-pVe*J$FAivz)6j?tmRyn`? z=L6(VPU#e~{mm#KzY+=+s(=0FWk}NutoXeAFAhk*%cS?upZxmINBTncvs*dY{_E4w zpsIlWJpuCf8Tm)Kn(Gt4{96L#k2fz1O8I=h4CE?U{9gj(+pVf!G4%G}m;bE*`Q=9b z29uEa=UC>K{}UtMU;ke@Mkn;k^Vk1(gY-8A_d(^iKS2L}qkp;3pK}4f{#T=)FI4{` zMlj#?mcgH&%aOiN{n=;a%lUsT82RPjX5{y>wg^57qZ``n{=Z4Op?Y=nKJq7f$(0@$<|7M&iiptv zNI(?@CldO&{t zzhmV4)OX&Qq|<+p{ZGO?y^#KqGj#eN>Vx~=Z~t*fUr7J;-8zZvf0qOD>)#U~e~*#h zP31)J%fBT+{xv;1k(@uR0OXhdTO;3ZzvYv4`iVj|PnlO4K8AUEA^UAG>Fs`-HjQ8Z z1k#gdjU_P0yL$CWzqx;A|Ivh>U;pcke82v&b9DNcKDht=`acw;ze)dyn^K?n^?xux z|I0I)exFHC{hRUg>;H0){!?{AzdXPGr{O-Kko~VQ=|5=vI|=Cv*+2FVTK@g|pw>Uy z3g|y8Kz@6fPWVCdrwaq*?=$kth5YLZ$p4C!@2dbCjC^$Op5V9VomRdteI%=sN%@_C z{N?v}fc%X{zTL;zD&d#^{Q&t3jr@hC{j>h?mdG!^0s|yTihcT@RG}qS>4W>T+e*K?DdSbq1GUxL~u*tsRqVwur;N-_f ze}DRoCOvH?gJ1t$CVizO!ufFqxXWi(tB*at5+Hxmbj>j4vs* z68i1;CepKx`}4ow$hY)X4SQ{m{}Uj8pOHV_BxL#Bf}c%i@sY>rG70(YO>Dztc9h=^+qg#3_ z-(FW+{ViaQ5u<<1G%V`hgP%=r^^YNaC~<&YYZrcQ*nfdO9ql6e#IHZE{koH~#C$YK z{tWB!>%z}3AIlecbqA2Ma1sB*x1Ibr71w_GM+5WMJ*HcA2md?H$Ble_m7jjaTJADZ z*3jNw)^=_r5-G>e27Rvo&zwG8!Ic$N(=~FbBl{|8Wq7)WbVpJ#J9O~a_2Sb zw8ohYuZ*Ofg5O#A@%(K3IPCMB&3~FX|0s^<f+k0QzIux}EXNn7ZCS zq)rBTQTmA5@S~$At}huF6U*#KWL`~V{!7uvveBKf%8?4_mKhZQ-AzJ{7arlGtp zdv0C!7O3(Es`7fGXn(l(tKbibJLBC4AU}~w_b2)~%e5YFx$;M7*`p`MGf&m^9!Q-E z@}l%8$d5X;>xWGo7?a4nmdN~fBJ;megDPlHP7QJvIzPHAosM-sHTlZgt7@;V{bX(O z>TTZG%VK@1>MBN5$QxZHqhpzE$n54=wkmf1y8hH~mBve763e`#VD-;&4jd>t$=Qnn z*jqH_^C9Q0B0BKt4UNY6=R>~1PL=n^S<>Y>rkUvLiN5r1Xtp!9Bf5(Pv^}~TMy}hb zM8vY|b|$LdDxl2k=?dh${L0!-UcF6e9!%)f8akJpbHtU-jK!3e3${K|!a6G z+d;#fq~8uj6MgG8p{O?kyI*6QfJOVE`9`bx#zgf2qdD@?UpOCo(w{&+V#o(ePv?XQ z(-D&sW9aC$XQ4A7C8&7*KM8Wq08?R2M~V1e~$1`u{Y?+Df$&RQV8SdpM9?QH}o1HtX zdfu>bFQ*W=D5;)Xl1|0CZyxW2d)QO|kYY zRq?|4(Yw2UaiIGrMFVG6ys)$JE$nc}P5B!99*znYiggp-0P+xRYta^uN zDnNDt3HO{~>(kzoa5gYjw+|?#;!~#%%rz~>@JLT&gD}zQDITMw%ko-|Z2VWt57HX5`8V``Tpn@uF+{_k=Ez@=>1liXFpJx29Gkh zkXb({Wn4w#*^iHp_5M6HI-XsPse>NX09IMB+=8-J>`FJpRI92IBW^siU1hZTkLhCv zP8m4CT%&IFvsRB*79AKqfT;%v&&S|;zp;jwbx9)gmZDYv*l$(R`QFOoejak}EIMUZ z$oXMW$z!N>MMS<)dLe4<($w1bJJT4w=f2L0BSZfQI-A|5(vVxszg6X99^ z8|!@`eO@f{e4^-YsdM-Kh?Gnk&pZe6@NJx<+eF!8C&r3?kv<|;^y6^voru8Dxl7HG zPC!qB!PPrE+7-{tFXhzeL^U-U8Gdw>nV7{gzp;H3<|?3@TN2NX@z08Orx&2-D`gqw z&53rWryK|wXd4L-ulL7MAc^gBc6H7XXIVc zidU48%?vOGGlzTV5(j3C>l&4Of4uvh(vH*62)bf@j}WucGSRMBwvR|GQ?~(AXHIzE(G%V} z$DQzEy7^mO3$neMIJzDa)h^oBaHVdFq&D*uYJ692dK2w=CEUZ8?_%9=7k8jdjp(W> z5BJdRf1>Z&B9yN(fIFvF7Pt(tJ9?|Y)Mhwd{bKlbzQY#F^iBqjnpZdFkFmbG4N$!+ zo;@W|G*CC?jk@Z0>cX>MWBnVA=9nmgF}O2;F-9zd3HU)9162&h@K>=9G6l6(rcfgo zOdj2k8^L;B2>0B8+elR2;tq`ZBW877Fd61p0c=O_bd%vkb_?^!?iu#Md|z8%nWhze za@Q7C3@6-sE|Q_9Xk#S$8oSi(0mkb8N@V_~6ZJhpuE1sSc$HE`^a}X;wb})Y1;qQc zSmxJ?17yWATVqq6XI+hj=j=@MnR?cbS)o+Jy|5uffMl`%e)VL9}deI-* zbL|fI^6^n(%0Mi04_gRsKvCTfrqQ9MrcMHFQMv?od11HBy=N^M3v6@DlhRmMv_$o5 zrrww#r00dySg2Xsbrbe{N>!?wmOdLjj_k~Kg?sOT>T{s*txvI^5~Eik6JFhA2R~yo zbh~+G8@LBj29Cu)5YfSu^#0tm(5= z-G8}2w~d^BuMjncDMLL_$~*jRtS2f<10gkobaQZ|2lZwnebGRzC+#%NOZA1Xr6U8U zD_(BRO^+Ek+90{U(v!J#-o0sqGXQH06qWw-Xy@&sw{iYvsPxX!&h5i+3+H5qhkHjm zUm3m%e%wctyfn($JmUDrMmhUNjQ;*8=O0IoduEjL;KL2sgh8ozKqp7}GzE8N6n=3zoLB9<8$>)uhyyB1}Lc;C(CvF>e{ z4e1(y)fIKit>?3g7h*;l%ie-Dj@dQ24ltIz5gfKfFbJ2#s$WWtyz=V3lzAoR!8Ulu z&+a#>a)!amPplj7qJ?{=sJ;&(&_O%h`=_Rk=-z*Jxc3CWndp7BTaZWR2mFT$>z&Ai zd)5QT5(`Gr{_v?a4raVC_RhqV*Tc8!Ohbhgz-1xdL-1{Vm zaNB=>B7?#8z0!^oQD-(`x^jPRWW{W^?tY9-{dK)>rp^IXQF=UDxEfYZUZFx+_|zVi zWwopXd4E!N#q0$Mx!O{oz05Ut(6W(M{n;OM0FO_pIe*-@>BIPX~^!>3Xkv z%?;s4D`N+qtm%0(H5EYZO()?0l^uI)y0-V??A6Yf1}+Pq+C_o5JolC#Z z+Vf_(=W~!$m-&7Cz|*z_>>v-*tC>!3_j~9AVElOIr!aWM5(uw3<;Pkpex=M}Pd zrLy;_wD(7py^m$(Q8v%n`>5}^7CvgAwBiM?d2`Ky%Y5tdZacj3SLbz*g?h*IOxev2 zz(%|Q-*j-D6HJABl9QbSXcq^H$MlSJ`a-|O`5%XmxoxEL?(m0MqjG`mTQ_@++4jCT zcIz9oK|J#(bRLOJZv+EEBGbpo5Y{-(4NvLi#3wvOjo#rYcXI9!o}z}_@RV$B-$P8Ykb|T(Wt5c#Z;iZ(7*Pb%qz&&^(K3vy6ihn zqrYJnYkJp7%N=aO-PDl8SFla3D?j?tWL*-Ut>Y@)kczaXo0}u}nl-W-+iLqMJJZWR z=1l7RXe60zZ|jUqUbCvHVO6B5GcxIh^pwa&k<_ZD&Z!r5Hr>!Td5SNVniL3l6sAJa z4U3$jwWUQTjviUE0ab+Wkl6P~)bqRAF{QT`&pv9zr64eI=5dXFKdM0dRhqT-fotRA zx%4-g_#pmwe?a`>?-M^6aW9&9wa=`Te`)uK&kX3rqm`BD zR$7I|7J*QyrR=}a(5hv6OADDRj4X;Mx?y2LmEK4`+Hg*^Wysb;y&1TNHts}o^6I)r zer80UmKn{-q<)u^4q-iho;34lkKzSZK|VL?AtAzU{5-UAouA@0mRdh&dRS3A&TB#R zw|nVdG4#iy3oFR~XE}Q2_gG`E1MipLFXi&fG%=HA37%VeWGoamdT5v{h-q#zX}*g) zT2C5j*RqhC=97tYF1%>!l_ocZ523s_%Jg%&BIi8!c_W|O*${G{Y+6L`Q=WU`nZtP& za`@ykKZMj*zFwU5SbzQN#d#HSzYzDvg>*~zfLylndcn>`ha4_;=7&&$uj^|R8K1o7 zhmbQi9|RIoU!QuT_=vaIZ557V_n;ZcAUGmXCdcQd+IrcoYOqly!aM=tS2_0TDl?RobI06lYdnw`8eT_u#ctRiWBmm zWdJMb#|EEja5dtY^N@3fIm;b>fzx93RhP!teryP--qBv^F_;B>Eh3F>);JoE4L|+wv$AOP6&h`*470B){6v?vrQf7*H7itc7Jpjdxpr;wUn~4Hmk-A;hW)@TjYjnUVhlPH zM150p2}Iopyb%490r*IK3tEVNZUEi{d~9*9U31K2*b+d06ygizM-On znvdc1UWMn{wQV5}0UujjZpj@r=i=4K!g9w4;Pe$sjUg#iEuf;O2;Nsvn!)bO-+L{(&F=y z{VZh$zfDH^cT8yd>MFF4yixb z!By~?dVLAyF}Sjr-npi}J-TSIZX9m*>K8{B_*(UxO4M8^>C((l79FfF|HVm~5S6g1 zsjV}K4zzK49KH1H3N6Kt&rx`Cd1qU4RekI7W_E%NYwMG!i)+!hzn`NysWaFgYQOL1 z|4?{&45=CUq1BGlY33(ftKePDJ6LaJ_@=-)i?aBi1up3)nRzMsNctv&a|*?u<-_|E z2Ga8$$KvY)=&kPvKfQg0i#YGHET6{&|7ikOi}<)EJ@39Oy}i#O&b0}PUyO4GKmS!4 z&ckmJxRl!?a4GjvflIlc5V*|O$^iU9flIj$30(5&55RvZaH;1N0+)IQ*#T8c1;5?w z+AMLY=hguF=K}EG30&%l+YJ@SzZySVj(^jzDle{KS-eEl6N$^cYB`&?^!ol^*9VEW z3qI!y`W}Hx`+p?>w|m$5?ej(eJ@4BY{B|B|?pcT*jD3RaHdMG>pX6~Ce%7A0-BDfM zMO$1wD8V)LpDl1cQ)M7N2R}=H7eNF+|F3IU^*xgRzZu-m|3?Ck;@tB8nZRW^4j1;9 z`q=)^uaA9J;Lk7LVqx&>lMuMn=L&&KecA*r>192X_>Tp>5gHzpG)DUpbDvC~!HBoNV~hZd^UFd?pI~6oHosedY;#j-V$m%O@`AStl(% zU*Ixd>g7YwvAkrym+k91K`-02 z+*47u>t4Y}w(GA4@cAc!OFrKV;ImEO(hkoHT-xD)z$LxiQ_*j?OGm&c2*hRoxlrJ; zpP8VjHNC92)PaF~ByLxz{c>5y82tEU8dkV$cUuH5>*xOpT-Hzfbq@K<{z1Lt16*NrPy=vc6p}=w&^>Scj< zUnB5i1>Pla`HXU-z>hcQJoKA$%C&8c&>sFnAkO;;8%CRYPW*HW;#e;5i2}b|;FAT8 zxBgThpD6;rPvFvS)>jef7Yq7V1id*`v40S_sd@?@PX-7)n66G;%RSOTITQsB0(690b!{|EsSN=)V%ZR;?j zju!Z7hL(p51U^aNQGuT!@DI0lYi=d%P@g6g+yWxE&UA>+D!f};3<=wUn{N$`JDJ}g#$Fawa}HpJkftZOm*tiv1cM$2an>aoZB7i&IwfLQyS!YekXmVIOq z6mOqi1jXA28A0*(nMP22-s6pe`OfK^PcJimh|e!QN)G*^f1#`$4k-op<8|wqS)2Fd zG}lhlGQB4*F=P*93*~+Yxyx#h%g~`eX7E_!|EK3|LI>|AJB7B_;p6K6%bV$}#{^N1 z<8}zGF(-CTz-!*eaz4pmk3s24%OKP9zRjky$Ds6=tJL1~vp+=owgx67t){a zjWO8zqpx!1XRrUkr1zH}UItWQwmygP-5d0R68q3&K)=&g1?1^F9TCdG%+vQPeNbum5P26VvHXLE9elejeL2}PmX^1Uki|5 zQ$RkS3;E@53y^=!LFWI70Qr{}kk7u}FaLJ|^4kwm{%->0w-=DlbuhpD5ZZa6_S1Ec z^4|-PzrKKc-e>#epB^B8gOP9FgGEvmO6`d`o`UqO(=|SPqmjR#nM0_-?=<}U^5+@( z$c86uFpC#e9yGuIzbQcfT_(N!zJRR!`mYJl|1PueYsZfoq-9(7>%TKVzTFGPzMqSv zD){AZ3y|MFP1CP39Yzfe4!D*q{f{Q5r>AitcQJp!Mp$l#a%V1WD*Q*osHv%$zO|EE^I z6oT`1BVT?WK@z|GXRLf*`YI#eJ}<06Jf{?X`4KGk6)L}pW-)Z4k1xb|LbplpAAc8` z^hbKA^YyB+OkNB4TQ2F4ql@wBC1{}-)%U-@5S}KE->*NfeWp{i{U-h8 zMqn*a-V*rbbC~pHQKcVmI-XN)0)^(w=P*$y{r$`=!fVK%41WFF>zm8ctxapKH&*Ru zRk>X?ep$V~SZAE~;eD}g+LqIxMxE9;*P~@qmqz?n;K%co_^raP$y~SNcpZK=$Zzx4 z1N8Zs+f!k@=w!5Os6OT1<#ncx=`Ya}eUI?N{3n(JQlHarRk4Y_8%qT;eJ@J{0`yUdQFp=jnC`fOifg79!7^BRo-N3*N=o7KrIelU3304 zY?{Jf+QSdaZaBYT<_dgluk!!##c+fY%U=9MKZt(7$D785LS`GiV;o^V!5gz3)Jvt(D}tu6_Se4tZhhk}z_pp@YBRr#Xa4GUCk7QO z-n1g2*ah@vb7u{#`XiQgX1lU=Z+~ibqHhxU*JWoaC|eq>ppz(gUoy!D5^$9 zB)kq8&GyzqYbBq0=7O>p8{cE>0p7=6%5!kJ&qBLnJLZ*;8QjKfM=2htcvH!sw+oo% zoVx7I@C2hBDxhjs&{qQ-1jRG`hD=B8eGn`ZlC6R#uW-*j_8LyO;90nFGq{C&9ssH; z_6|j>s&I4@oI}AYM0how#%zZ7h)u5R2<`I)PGfe`!`%n@j@YD}RKVGUauSn;7YgkN z$rKR0cWj1FDC=FZ_XSiy*nhP4pnQJa?({-fr|40`g4{pE#Zo}@v zfsD5I64ymQQ?I9oxkToXwIu7?LTWgzjAfqG#g(XniOK@xA z)}_4}RA6+D{VJ8>i&78w@MSghURnoK%M5GGKDY=AuZG|1EoAMfG`$Leub^0_kG$wn zY>M(IcAW7jcB1WM_i|xKIlmF^#o%By)4Q1ovwdWxYm<@_e)QCwXX386&kgr%M4`f$ zUOUNm^sdJNS$A;SWl#I=QA%W#4Pu+mZSQDnIJdJAG8DeKv3bS0E%oi40Cs3Dl+9JT z?Y+;DiR*h&TaLbhvO3q*FPX{=Ple&0)N>Q=;Kqbd&P!)VGCd zVpyPxKj5uFBJ;AgF1n_0&(DECf+kXLUc&X7Devz6Md>FJY+7zP{?T8KiOe6L;JR6C z$CK!>Ou4bs>qX_V1qmY5M^ypz@TdGA^(2wNy2>*;m8d(cxt7+x?v<}Kr3^cy_w9mmUe+{M+n+98sNIWlmO`19E zYIM6bH8?1LLE0;-P?-D`FAR_4G*jC#Zm;OqM)tU}0VYOWY>WAG$~>Cn#;ng4MI@a}ax;o(fjsNBG>)Yw=S9^Czd zCz9HHuPi`d9KOScQ$HpWw$p+{V_ zeMo9LBqk~Bqiauk<{XY2+yX#2kPoDG$0{Y9^?%fruN3`PvGb$i%YF5Mdo$3#-=AYU z=9VxH-T`3*opThz1onLJFO8u_W0=KrCgmxJ8{x$7&i=egXX~@;+qyR2Yp$B+8qR9o zw%{P=Z5SJUvmkVBVGbnSPf5Scinp5DrJjw#vHA|%dX&rROv@%zwQvvnL^Y?g)4QiI z7QrXr>+l<^r+2|V0bk+t4&G&bUO#i5c6~kovrEBC-CpLV=dTD)CK+og8g1+u6#Yf|vwr>|Fb9;0AUv94}hPb^RPX~N?&2^4yu3Se` z)dlswKy#ICl|KFaT;8w!&QOm12G@Lb`~6E1USWQ&_%gOp6IA!8;U_5)# z6I^yNSqa~mublAX;QHJnyyszP63bd|+&=q4Kf9^+l-^w*3qSy79H$7Y|GAwcYs(_IC9Xs!v)QNn{(~P2Eh>biedlI7@a1 z2&vf7KE<#fOLld{&zgoZ_fX#}8u%ZjU+$l@ANDu#jc~h_`t(_Rx|+_K`R7Xt_82Ko zmrJNzKT??@OI52A_F6}$XK;iOSqb-`e}+jtcCHQIv3>8q0>_xL_n%b!iHe8kCiXjs z5QFOBy@_3FZ(?%`4QL_a&J+2Peb(;uVm-(mzWdls=>IkqAN`q-vz7O;EKkgP+|@O= ze9c}(t7`N2Vo3C@EKOw2#NAM1s^Q$_ZK+shUg?f`<5I1SDYhuSo4kGAi8vNi~$_9-U?&mS*Hxqz4W&thMw}s1WF|)>FHhnN??Pe}naxP@bw`C*tL)s9_meQAZ z_tw%_^^a3WVFw66SPomdaiAnzT3j1GI*K~85W~XKyJDG=*p8YKuyMB6lmbp~uqvR0 zHsppB-i%O@J~)>bw-ccMmPjU&BQ z)(Ln-wKu#fx8B65Z3D+B){~t63fjVV^xBlGv#(1>oc;;8wkipkg?Aks8 zp&AdG9=+}oke;W2rPz`?ww*3^GkG{_xBPV7`L3F4e{SY@* zX1uYpGR1;hB4{7zwW`zNJyv?#04e|%CJy$FORbw8S#kB!+xoW-J zwCwTTg2iydIVWS*wDTsLFbCEWy*ra_@4W?$8r=i;MtX$jvA(4ntz}liOdE6kyq%Gm z_R+OPrNzck$mYh}Z74I1{-66a6}Zp9mK=OQt|qr@&AqANl)hF8o>RRqT@Q`A+(OFT zUw0`A3_plewanQ*Um#Fr26sxqJ ziNIcF`akeZ?L3~1B)xIqn;)vlV2nMo;$}SB`nMug6)3>XKi0$E%)D*%*-^baeIoX7 zDlzjStp@scqegA@OIVJ}Ta2*$c)x`C^xNDMX#zmoVRM;xaXdR8TNz-4+lZL(om<0? z{tS%mL=306V^?b6bfDi*cdY&Z$|uhB%1LjJ^<9eT+9+%l7srN6vFax=5zHJD%c9@B zv~*wuCT!uOixm3|J?f8$o5h*h?Q=0-Gq-c5(b)FnKG}dU9jyKA^G3c4;CqO+3-!4R zDg&hSmIDyZW|V&dcm0c>iGl=&~;;#lNu3zboT6l<8#@? z)+H}J!G|3O7M6#fK#6Tp`L}lw*qTKR27M0_MiU%3!R%E)Mo6F^SQy(H+ts}OX3y|z zKwlj0{W%8X+BcsnN}s6i1hRE^VajHk1?NCNg6vI2wQp`O3isZR?jA*^?2+wbroF_y zv%xKqZP|?NR*wwdJ{h^f!v8L1ir#)6uMP1*zzhZZ+wItBw zc&=L7!O4|M)b|LZtWtOYr8xB|`b;0$L*6LtBnb6zHagrp24`wVvAvuZ8jXs>x6_d( z=fOyV3AR_GkkM$E*63ZMk$%eiVYLZyU$b|IP{8W zw7itq7kwM;B_im#YHmAvfl*nA_wwRtr*~dkZBy zsY@PXH}95!TF)>gAQa%!uk=|Q6|z3X_kfF1d|;HM&zcg|mBO7?VCYUR{B9t8LKOQkV)8 zKW|G>VAi%2^5^DoAFyI=tH%B0nC&AERX5$%NJQtxtX1@>!Z#T#T*9%e*3ft5v@w6Pj%8V*}4rF&Ng}wEVb(m8iyO*%)=bj zhuR#(&}7W{JsYS>=AJGb-lqh31A`J^4Z?8jmH<o&MC;odKR1&ZXZfsrV7YRZKxvqht3rCy#=r7=*WCDw5wfLe;o-=0UGd)ddw`pO(>Av0Xyi&jg9w(m;M-CKtW zv^UCsHT-uz{ta9;kk(}Vr~+mRgThOX-+L;eb5W=Y;T}5sP=fas>&U&!aNPSRWN$AQ zoa}0oJ?iAXY7yVtU^%P$md2xPRn^kX=pjulOJ9d`)r6~GO0UMbmr1yn+5zSOhi1uT zUQDKe`7AaC5bk-D((0d)mAb=RS%Im~S-?n5{euO(qEnf@-$Yi;e!0GZAl$o!X7G;# z*sV>&5q2DI{~zw%pNd2mi&IavX{=@PH=jLjrpe&oX3V8`PjIkSBD+BbzIT5;m5svyz{dW$Gv*I^XiDJ9cSCf(f=^c zd2S@$6YejG{qcC`w?`b0H18hqy&^F9!>Hq4Jl?r&^a98E+2~_-9`F2d%yG|*b9%yj zFXhS7n_n5{Y(M&fKOFCTb!-^t_Z&MMi5@w2G-!T#?C6KbIe$KO^uLXBzHr>hIDhiE zlX3p1nsw&>xVzHfk)Oa`lu-ix{1 zx9Gpy?flZ46=y81fbr=GJ`{WZCz;XSH+}Cq>`osAZF5h`6vI6E`?Lz?J<&`ZRN(t> z`W=TM7hmvsn*aG)Eb}uK>CX$jP~_?__F9f*ev-NK0#tLn5|wLec%M|aA>N+ykpW*V zw5!v^{!L+6wY`TM&JD!z`Xrx!^_AmlJ#JNcYZOa&UunGPNy1q7?2-s>o_bWVx{p3o zWeWGumu5VBaa~{QG1$mAH>1bh#V;XX?%1F0?A||o%}%p06>AuFD|S3Qu*dQ-FK%P& zM63=_fv6ezxc2mgtXe*K%-M$z4R>~JVV<4TNf$abxR33<4p#%G8Z_LyjF+Xuo&Ey{ z9&k2c(M4^&iyM727gENd9oVB3fF}^nc9ta0?<`HNj<vqxR#&Bnaf%wzx^pC_7;m zRoZa|+6t$(NST}3(wu7EWbIk1)-vu!C&<@U!#zDb%c?z&VKTcB?%o>-b2ECq9rE5U z;M!U#SIT^`d~E(+wn{tM?t;!$eNu0up)LMTj&COVdZI7A8=Fw>#FTgUSzHg=#r{V< zlS}Wk`OB``nR{n8m;C(b>*)&J`aXFzY{V7wKtk`65ViJOGJRB|b1|GbP(R%sR9(tz z5`)!0S2N#Fvq5LU!CGy}^f9ZPS;&J7)YsqM{nxSVWhJrmKVFg^7wi3b>i8HwvdCU+ zt1J6=CfHP@`g!t`9X_uWsi^_VK6ro4zF#&uS4Mo{9FHUuXP?JcDYw2i9AB3dZ)O?C z!?U-~@0(bTH-}?e_nsen3{{w6WFj1WIZ?z7VqeDnSq$5jVmD`QUHMvU>uY+tt(wf) z)5ve2xS~J4_0{2*^qo{bzwey#c=yw2L(%^EnRCDzJ-T@>MZaUQQ#GNJ(EaY@D5M|V zW9!LSwr*!UQ-|*BX+9jBpthJiDu&G@u@P?V`lvGyX6waU%eiHn=uRwV^C!m1_#LRpJ>QLI=I@JV!??4-!`%J2+FzHsWnX;9 zy!|Z2+U+B;bhbSb?^_UIO0bG&o=Y8HmsyQe3--mUpG$8Emlokwf)N9Fllp9qtw^x1 zuKL~dQ3DfJY@iKrkvcY>T?bZAm-0=5|5AlD@H;3-y_rq-r5%b(WX?`xI!oePUm0Fk z^hP4nP?{@6b^uqSrQr$PRkc^^k-0AO-&_lMeBep9!^a~kCB&O>qQcNT3&eR_rDb&B zt5b_6INwDr`EJpe1EHcH745;Dz}JTjzi*iH&%=iA99HxlJax{^R9OEo_xKQdcQ!;O zUE#E0yNSt@I;TXqUrOWhNKMB9QQvWWr1ScgWo^yAggLJ1 zR>jv)OXjl|EQ%&)EsEC8nZ0Of9oqb&Ml2`Pr&dM$F_9}LbzbevY3gWfNVP#^Lt95< zWEpmuS>Di*Zdul;;v;M7JC$~w?TroGlV^DZYOiQZw=RceQVtV|G{|^!^D^u(gIopj ztxtJbYi_e#$&pUz)2L~%*(vu&t(~2i3ejttkdKZvQyrC;#nD9{kI#58arFhBq6qQVHq0B);BaXc6K%` zYi?8(l7(>YnvSMaW0|PL%Nw!xNekAaBFppTHMcd?Q*c=%+Pb=_qph_C+c#lH+m5Ds zay@e*6+P3+0|W3`Zfd@*BOQ(HZ5=7V%dU^0J+-e%H?c+iU(bM0wWBeW?qJ5|FHI!M zoHD#%CJd-IZz^QHTsvWA%h-9?ch$%uIcLF=`Pi9fNp$g&BsTV0QkzI56ANb7F5#1{ zg|$m!P7`($MytaHP`VtFP6t}G=u_%juSc(?h|nErB6eeEPU<|f%vrLk(Z;$gBJ9{A zSCZgrRdZA$)KzC{r1RRQ_I6avgGg)oO!R~7M%nw7d9$Rq;7WWVxhOg>zIaJ=QFM;Z zRidr!T2_A5OLjD@LXB*Ig&U`uR@xAtw3{!#SscmHAE`6!rk%_FEz;4r5^bfi1BIGr zK5aWs>CB1FtzDYPm69r)bJLw2=XPRCuSULo$P!hhiwCpjaje59oc2j2&&w++r%j(R z^Sr9+vI%G6BWkor)qjgI$T!5`qb{X?nKH7V)BU9ycp}({IyZdeQmjC=NPV~@El3@l z!hUvUJ~neEoz*@SzsRJsuAJJjW^HHt)i_m`A1$9+T|adNuQ@^<>2PJd%;DlU7QP)W zabI8N2!4%g8=bPULGP!nH;!@!xu3S&sO_U4ZS264be|?>ziv*>Ji+=U_V2kp8C2G% z@ZXri?)Pl=hA#e{yPdETL3E5X2p{7=ORM&*E>^p2lUFn25R^GYQ6Dvz9XGj#Ev z&0}ug^=mCP`Hto@1cY1oFQ4{PmXYWlC>C?^C zMYM&`*kY@WYNzzQ#Ne@60wlnw&+8wcPsuQ*E?mCUXR?W(N+N5UYm5r42ei4V4=VqL zo=^W=deaWZ16ph1>956&$0^g&4--QL|5;fFxz9Xxoyc?ux1wGvR+fib?x88RZnJ$X zQPd9Z%Y5|G-aibv_NI*WCe4@pGR$7{p{uL}?JH=rp{I{Vk4|Eb_To-gghLr`m^6H= z)sx1`xHcz)^e2IifzLHP^tpW3+Z4>V-REZ<>X?UK=oFe~Q|Nqj85!eIpFC-#t?ta# zP3rZqN%IWOJ!wonim&tL`xQe^KU@XrcYEoNHEo>sDo8K*GvC%<{Hu5;#X~Ri@Yf?; zA?zF08X0`%>`61Pd)Q|~#huMB*gYC}kQ zH?>##cr6aaf=Z3?h9o3#o9ZE)kpC_tc$$fL+~8j`c%{L&8$87K5fCiS^N@prE)YVF z8f85(yKqIhXByPbkGKv-{7Z(v-1i_uqR2Cg>8 z_YD4+s&qO3MkOdn(npOxXB&FHphkSF!PR(R;zD}nWv=u=_Zc>qo+#K&rq0EM99s)= z7|%iudva)5m)+@drgu4^Co7Zpa{9OO@KHJIj495a~1OJrl7$Z z!~YROV@rs8aZxVo5d)vuG4MkY;=k9IZa%IwcmziVQ#{UcgY&sJL#aMEtp=ZNaN`oo zS!-~{GpO-ZpCVtUnX}yCHbs|@lOlv&*b9Y_J0j*{?*t#_>vq$QWXXk;cf}kn&%UnY zp5X<}Rmg2Ex#))s|F5cacE7~Ix}?FQdr@Cyt*pQTXGkm>8xd;xL4Hu%v7 zuQK!;J4hclxNS%OZSdO+Zsjsr$g{hNX0#vl-d#^L&i|o;yTuUaXy*;1AdmWd-N@x@ zPYlNy+>RAjY1lc{;9Jdn?M8!7F*t;%u+`u^gp@Zk0pN6+p})XXOe^<7gV!3|xTaKz zL&~e6m(M)V>yh7!^D3lgXn7GKPw%UvuP}0ZjNH>rs^tdf+>2qXKB)g8wF{8|aN4T$ zneGM6Rmho=WziRl~dX+W!4-Ibn&pQo1!5B<(3^~$TT9hu=e4Mc%eP`*>=^?}4 z?oB3JRLDAd6e%7Dz1}TPfaWyh_C~q5=M4XKM*mt9h3^YhJ^WvTbDqNRJA*5?GPt(= z;U5hCoT0b&VVaQg-p8|CcnkE}t6vVYXCZ!|;tNNj9kRbkADN`odP(~kh==Qt6T^35xFz!( zL+MP*K&P?ArqbfvwvZbE71Dnk`h`OH z{|n&%7vN)yj{s*KY=3nzOjd}`B?0)A0eE`=z6SW%;@o|fmHVv1bG-}KWEhS{KUheg z@d0>!0KOssZwtWLFBg(a-@AqIZvY=#oEwGgJmuX0`dIk4!|1%@LPe8EzXUKwkAEV@Z5c2Vw{_6tp?g0Gh0Q^PZ6N($PF#N}*3V+7Q*kW6|9p@Q? ze+(6`kUlGck1e*VE${_z?6W6;elnPhEzZr)-!S}p6kcntax>1i6+YX=Uo&SHA6;0V zn*;EBfsZYYx_r16%kW$P{ks8pExP7Hc32mHe=Pt%@|ePWt_Z+C6M%m|0RMdezCQrJ z7z1dbd|d{7Y;jB*3;%7;_)Udh;^McOv;Pg?GYkpG7AIW#E^~I7!somAE#~as6u!{K z(aq|BE_iJ5$Ly7UK8UM`!WX$X%&tNg@Iv`oAAo-$0RKt=zA*rQFaUoT_ylLAt0&jt z877S@tY;bUvBLA=7c{w(Re0{cz?RE93eVja+-c}Pg&}He@ipcu*S}>HUZ015OX160 ze5yGMpHNuOV}Xw?UhdLkx}w620Qz|W_+d z27X#`yGv^8=d%IyPFbR?Gu2@ZWfeF{Cg(1lKU=-Jlq|yzsng((zoI<%E@gDlrSli3 z&Pyg6)~>CnsHl`LUBnv}rz$EM@G4Cbuevp*I^zw|#r60XjXKFI5>h)zX3tJGu5D;+PbE`)*=>f6n{H1k-E=c(02-uhMs;4o znKo{kJ#ksNQLZkodM@vZa+|Eel~PfeC#AyWK7B@hq$@E;R^jHOA{Xl_RN-n=;i^=T zpSKD(L8Yr^rK@J8t5crNARJ#jJ&E>)|7dWwCg*1tR`Iur0=Y%~OX^qul1aQbm5e9l z&KyvEaVk-jOg6P~LsMMpo?;r_w@jv*S{m`vT1)$^8LF3vu69nQ_TN>K0cc5hoZ#5?yR$ZIqJB{Q$gKyk6p0}pHqZKbIMi*Y% zaD&yfn(xRZY5YOZJ{oHuhiPVo+3Yn%IeW={GH^>Uw6}qwk z*e-8gxEcox6V=INYE?(u8dz*)Qvg;Adc_y&8+Lg{udb*Fj~Hbx*&gNbxPs@@Bp@ivzv}z<--hQ}d=bwXSH>MZdfm&AbC7 z4b5$MFL4Evsd|HqEpKdYYH>O{8|qtEFvpjEc;nCL;I|1;Xf(L=(^Yb235BLr9K3z- zviiza)MRu-s<11|-NJ5cU71=XN?TXj%4toV=w1-BBHi44ee$|=eY5(KVUW$itv2PX zHu^)2DC!C8muY*ItW2UR4WhzFQT1GY)AjcrIgW1B^tGwBCRPIWeyKLqF{xSAberjp zwl=(2e~5XltYTix%m2yT2OsDb@rA_T`-8?-Hz!s0Md!hBTjuC58DO)D3f+iQp%1G0 zin3&v2Q6vhL9ue*-?oK0twNg8{j4MDeh4tQ{DXaE70R50j_&P~oRQ|(E*eZF1lMV_ z0A{)N`;sIk<&7|H2fp$+RPNx64?Q?As4qTT6M1VJ-o-~o{2IVs(ZS`Z-k>7IQMMxWlfroJ6c-!L+)fN45u`wu_bRbdoX@AqS# zK@Mh~x4N-o83tE9;NlLoxxRH}vVkAo=x?LyQ|^R9TPC2aS#Jl|ld$YrSsT(Fs`0Vd z-0pK)HKT%MCh;Lnk}0D3wQA7nsM>=J=`RQecb8!sh#`ZX9lc_2_?L1{1 z{q0)P&-<{LrXtnR)PlJN2H(y>R6^Bn3}ULLr&?;Jwm4Ne-A;|#(&#bJ<)F?sCReni z(1P8Yl}cqCTdCSGgfuNrVxpZwjcKu6k*a5ySx?hTGWcu=15HbzHJRw_3fz*ZmgiFA zYqDkO6$PoMbFRupMdryk#4iBQ(zaZ+rp2jhEwHH#>pl4kK+zcuaBA5RMNFki3V^@5BG>#)mW zaV%LPlDtpp`AY76DCbn#a;`$@sA++P29ewI8roVosX(Wjo7-2+v=w+6W;G4#Uki8D7RegHx>iXPlTy@SAfQp@l+PobZ zBy%&L(DRm-#?-2|<%iBy+-uBWSLso6@VCI~PDD-SZ1CX#f}9d2PfcM1H=9Biq@9;RKqFbc6 z#=*9qVA~%if$yKaifZ2fq+7Whhi*Q(rftp4>AH_n6>%n_8(Y#C*+{Tf#!ah2*P~h` zNVq_N!2$~gtr!8ZSa1W@h0ytMm2Yr!92m-3X6hGJsjT2A)TCIO)yC!NmUiljM<4ac zWw_$<%Z(TS%2t!vk<+tKl#XVm-hTN;;dZm zjl)2CSsxw|_`Qg;^b?s;1RjpX&*JL)W?YjGZDR2nLr+}t`Lw_v6!iA{8b%){@CO9_ zzY6>Z0+;pw-vuu1_OihLP4M}Xz$Kqh2t)|fe-nN-U&jgjy8=H=;IdtxD{xugt`hh| zg3q-Am;BcVT=M^-z@?q<3&8(kaAq5?&bax~@3ZUteox?G8UTUxvRxl*a2~#apS2I) z2WQ|x>a#%Lvi)WR{!OH@d_F61$%lP71EXa-9j{Q0%XWII!KuG&r{@TI*-lRoxGXP8 zFZoXs{P`BD)xSpYk^LvfZU*Wv+v6C8=IhV4z(D;^!O!xK;GBVcB1=Du z#0caw0Y8h+BZxpgkK~iJ#>&qDV*h^*mnSGQX1yPCkP2yGmyXRPxxId1Mz$Dv-+$Fz&|T+ z*&e?raM^zCo7dzo>rXkZ8HmgJGh3k=m-Xi?fp5jR)$<~OOFkbHxa9K*fy;jKc7e<#R<4{!NxA<48V1tKan^nhPh5_tCmXx59H-)E`JXB5Df@?Nf!{0Wmk2(x zAO3`(|AwIdl)xn)`~3#>l;wN7pqJ%43v>+BzXCt2|KGFUs1$M!rQcv*ZS_AK{l;{` z|F<}2pq*tu6Vz|Kr>M1^XW-oGIb4*BZ12Yj{0TvCzguT{@gCXoIa|<6KKx#vfqKe* zV~)Ye=REu@A34vId}KYmSJ2CPCGmTNTv-plA#hm_S?3w3ryO4*1QCc!JDjReZO;pE zZuOsNaMH{Es!HIpy(o=@Xv={fgc zAifoAH8w9=+=T^^Vfy;8~61e2gIT!=w zO8&feU?9E`Kg<7FoHGy)vcDW(#tXa2@nxF9X`fm6S^gIadTD3be(x3Z-GYxCU)Bpg z-w^a)7P#bN{hKjgvR%vjO4+Wh{i#346sy0q|4#&d6V4gPU-nl$3e`AmYx#Up;Quag zJML3%OyIJ-Wc@!N_{jQpRDc~O1mNWXxU{FV+t&ilPrfT~IZu}D{R+c}c1Q^Nm4g4z1#aihq^}eBU4ou{h1GMTz@^JDQgMo+n_*s3le-2GA`;A)!pL+$p_HUv2$n}J+f}V3N z%l}tGuIy)C6!hN^^sfl~7XsfeaH&r?1R@0HSGFVirC=Z~>pACa3}nYK!s^K}nt}AP z|5>O|jm!S$lLD9ebP4=FaBlhkgTVh&;G9!2P_E?DPY}VMFU}1Z{Q2VCkAe7)@w0Mk z2_i7RAIHz)>;oA{FYEIfg=+k#IJfkl7PzznZOcIZm*HpWWqX(WH<1{Da%Fy>RA@fF zPvFv?^8V{rps{j~DAEb2&*cIiYjDby{kgnf`-Gsk^J>3+sstZtAMOjq;Lq0s0+;pk zUj=?I(pmjq48Z9RiNVjmm>D&AP(8mI@s|JLuIKBOHd;?vUSBmh)n(hU`g~i^%kfu^ z3(`Kn6ZF!auM1q-p;X{fp9u!{>r*M{r9R^Y{sPXe{=C;o{H$C%ZW3QEaA~&}1>TKw2I}(!ewL4&FA@KZz^NMp@t5$k z^j06@dj!5!)Z3Q@{#$|nR^Vf(I0E@d{9=K#53zEsk6O}8{Fj1Wj*rViAVeU&#I65U z;v4X@{C^_o<$U8cfy?@N0vRCq`PlVvs>M2B`F~u{|4!i6?-rw_+y?}`lxzKEF0PJp6cra~<^e_}P3-5cIDKyiDM) z3A{_-(w>hBT-x*U5+p{Tp6oNN+>QYJGXeOw4eqZGj|+O)9-j%I|8)TUe+AIL7eIf; z5zLj&m+Xgc6}T*y&jsKc1Mu$#;Lil$zX`xkIWni`UYuI{R~el43=8~1LH`YbUlu@r zRRI0!0QwsP=)RHA%ljJoYhj?C5??%8!3zH-PHjE8 zT;P)biU52~0KPr|KkBHQT*?1fgP(jUuX1uo}xHw#?y z`APu(XaN3f0B)+4vcH@cm1^^-c~^@lmvIJXIc^pB*#ejM?-vRDYl42Rz`ri=dV#+o z@J@kCxf2Y|dj1DNFZKBsfzLAZ)L-hkRNzw2n*=V)h4q7hd?f!`g=$>#Um|eHU%m$= z`HvCwDV*DOQ6}&|30&su&jP8<_a0>4+_9RYYo;IeAAwW#$s{VABF_ZK9GU@hY5TmK?LHm9{y0F8khBOpTK23{AciF z@aI>qm)?tbtEcrlO?s*SXjTLSKc9%ehYS5>edaSS%U|AyN;}UHd}RGk2waZuW_qaV zGwYJ&FYC!jA=j=yuz#g5c}ssPGlf7gT>G;4RE6sN9w~4GE5Dt$+Y507slzSGZHSl? z@)xk{ah7<<(D2Z1&WVpO=RB}X8Hn39&g*{uLm+P3IIs8c9|G|c)S3Bblpt>FFk|d` z9q|(hm|*wJAbzq1aZCw5)`pC}L*NktCOj-~JBBjKuGf+OM-8ny5J2F;_Bp(^?@tns z5-`Em6XNw2#IarQStjti1b(f+Hw(O3;C5Y|(X9e6HHAmKP2d*@+?FY$?R$)*zfREK zB_TU&i>7YX9a$P1#x^s;5Q3g`>)mZyhY&Wut6hGpDv!5aFxJswIGf+3H&yJ z-z9L_F187Ll&J$uwp-vw2|h0iyj0*Nremd^V+DS+!0jB8(H98(G(jH~_!NOR3;Y~` zrv+Xn@Qng57x)7LuM+q!f!7H9d4bOsxNZE*?_7Z&VLAokae>?QAL5q^{6ay$K;Wwc z{xN}HC-B7rzf0gt1%9uj%XsSg zS_|@^RDg5{+_L3!9SX-t9<_buO0L+U-rqp|uR{m>jKm91=mQzPMb&X(^XvMpc+ zehfOao{<8TuTUR~4zVy<`-j{SP=2{qT2u_ z_JA}r?f(oj=@ywbobxV>?(BU0iu8|+Y`HJImdzDExHRi%T`{cVNd`@g0 zBe}<*^a+#RK5Jt7{U0Lz|JT^rh1O98as1j){Ah`hNC~CLRf7g;HCCz@EEvBqlv<-m zDOB+)NJ5DN zu|;{7BsGi2>SPQ|+U&9lPTOA9nJF$y{4ykEW6U;gE@MO?Un*hg7?Lb|zT`RL&ya6p zHNux67SBJ8x`;3D%NDceRdlJ$KZz9)zlVdT7N_v9Me+R0sEhdX*5J&M3b1*E8U&doS|6GP&<#@CGp9Dx3&BuQx z!yhHze(xvoUytJX|Bx@^ZmFG;pkO|JR_jW9ZiNs{l=3&l4edRbCh-1034N5m+H0zq zXCB0V8K3t4e@edhzfU?F%+5o)q%514X*=2v{ij}XmYVnft&Dz}`dTW+yw~r8K8n9% zuL|qbOZ=-)eEerK{1!eKm5wP(Q=Wf1!ygX#(w9B|YB(R|Pu@Q%Cg<1GJbxMVQT%fO ze>EEN{B;@rDET*}aLjvtM~2_MPlf66yC&d2lHpI0->Cn5l+jO9Z}0!KFueaCX8fvI(~TJ^*rE~*e>e*`E!W+^w|Xw`u280FKu?o%ETu3>q0l{ z#Q$4Ry#E(6{8{qtxl=Y%;(wLlSKd%Xdj4z=_%|Z(D1ZIUs&91ul-~=$@lS(<9m%*yH1KvVWlSTPmcVbrrzG$YZ~8wZ-qYcf8>xV=91%Y1FF3L z?~w2PpJRX>2IkcJ--Mgm$p02TocfK^d;kB<_&<0=g)`Jk|Er;R|9i>z{$C{D^Su8T zGycz0Z|@zZ8>REdFVIK%?|D}h_U=#d|Ng-L?YK@z{=EMS^2HL?DF44xkJIQ9tIEv+ z><^t3pFSVILLd2GKdKUWrdmw=muK>Q{vXGeipc+!e5ly-eEz4XFZoaXV**dixAz6~ zk^g<~tEih=@&7>-@Bc%%&WZn?KS2I()Z4`K<$c-_e}eq9|2`b>k7W4uo0Mqplg{xE zX86M&sG<$$CozftQ4}A)yvIA*f8A?ERjGWhlj76wzw2;=8Rf6KP9;2z6?&h)<)<)8yOsY^HSn{5hka zy+Zv}xX+2=pQk>3rd)jR@n6csUpt{5r~U6KR9QS$-=E>P+@*T^U4t%_`MuCb`R^e= zz5jSR;QvIvzkY1ITZ!hU@%{1pLxw+hQWYZ<%KqPq;^W_l1V{O=lh4Z_TPeqXAM{cD zgCDCT?f=^X{&Dhs{C|^=%i9w3@wehWG4j8%US*BWU(L`*{?F5Y%fIArJBrWW;S9e@ zJ})Ch48H#!$ndW@rIIv%&j$P^+@MSRK7W(sH_G4Z)cfn_H1!s*1>pT3&**2bQ2%2_ zKTkbR!-g*%|KCC%?Y|-BFWrAH1o?Xa9VF^M)q51_^Wx)w#q@3mbW-13c&;;E{}S|z z{yY6K`rk>t^uHHTY=2l>M>G5`@_Ssno_{#Q|C;=oBnKw^UKGz?v5NiAaYiY{8i^ou_jo0)qh9-?f0d0 zv(njsZ`)lkzTf`=e<0xhNxrpb(;E45+eBys5QLjsStlGRxar+SJN6zY2s}Wyn-v9pr?H__U diff --git a/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp b/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp index 20a06571..e7bf1fa7 100644 --- a/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp +++ b/language-extensions/dotnet-core-CSharp/src/native/DotnetEnvironment.cpp @@ -11,25 +11,29 @@ #include "DotnetEnvironment.h" #include "Logger.h" -#if defined(_WIN32) || defined(WINDOWS) +#ifdef _WIN32 #include "Windows.h" #else #include +#include #endif #include -#include #include #include #include #include #include #include -#include using namespace std; using string_t = std::basic_string; +// Named constant for hostfxr path buffer size on Linux +#ifndef _WIN32 +constexpr size_t HOSTFXR_PATH_BUFFER_SIZE = 4096; +#endif + //-------------------------------------------------------------------------------------------------- // Name: DotnetEnvironment::DotnetEnvironment // @@ -40,12 +44,7 @@ DotnetEnvironment::DotnetEnvironment( std::string language_params, std::string language_path, std::string public_library_path, - std::string private_library_path) : -#if defined(_WIN32) || defined(WINDOWS) - m_root_path(to_utf16_str(language_path)) -#else - m_root_path(language_path) -#endif + std::string private_library_path) : m_root_path(convert_string(language_path)) { } @@ -84,7 +83,23 @@ short DotnetEnvironment::Init() return S_OK; } -#if defined(_WIN32) || defined(WINDOWS) +//-------------------------------------------------------------------------------------------------- +// Name: DotnetEnvironment::convert_string +// +// Description: +// Convert a std::string to the platform string_t type. +// On Windows this performs UTF-8 to UTF-16 conversion; on Linux it is a no-op. +// +string_t DotnetEnvironment::convert_string(const std::string& str) +{ +#ifdef _WIN32 + return to_utf16_str(str); +#else + return str; +#endif +} + +#ifdef _WIN32 //-------------------------------------------------------------------------------------------------- // Name: DotnetEnvironment::to_utf16_str // @@ -124,11 +139,20 @@ string DotnetEnvironment::to_hex_string(int value) void* DotnetEnvironment::load_library(const char_t *path) { LOG("DotnetEnvironment::load_library"); -#if defined(_WIN32) || defined(WINDOWS) +#ifdef _WIN32 HMODULE h = ::LoadLibraryW(path); #else void *h = dlopen(path, RTLD_LAZY); #endif + if (h == nullptr) + { +#ifdef _WIN32 + LOG_ERROR("Failed to load library"); +#else + const char *dl_error = dlerror(); + LOG_ERROR(std::string("Failed to load library: ") + (dl_error != nullptr ? dl_error : "unknown error")); +#endif + } assert(h != nullptr); return (void*)h; } @@ -142,11 +166,20 @@ void* DotnetEnvironment::load_library(const char_t *path) void* DotnetEnvironment::get_export(void *h, const char *name) { LOG("DotnetEnvironment::get_export"); -#if defined(_WIN32) || defined(WINDOWS) +#ifdef _WIN32 void *f = ::GetProcAddress((HMODULE)h, name); #else void *f = dlsym(h, name); #endif + if (f == nullptr) + { +#ifdef _WIN32 + LOG_ERROR(std::string("Failed to get export: ") + name); +#else + const char *dl_error = dlerror(); + LOG_ERROR(std::string("Failed to get export '") + name + "': " + (dl_error != nullptr ? dl_error : "unknown error")); +#endif + } assert(f != nullptr); return f; } @@ -160,17 +193,27 @@ void* DotnetEnvironment::get_export(void *h, const char *name) bool DotnetEnvironment::load_hostfxr() { LOG("DotnetEnvironment::load_hostfxr"); -#if defined(_WIN32) || defined(WINDOWS) +#ifdef _WIN32 string_t hostfxr_location = m_root_path + STR("\\hostfxr.dll"); #else - char buffer[4096]; + // On Linux, use nethost to dynamically locate hostfxr. + // This honors DOTNET_ROOT and install_location files, which is more + // portable across distributions (e.g. RHEL vs Debian). + char buffer[HOSTFXR_PATH_BUFFER_SIZE]; size_t buffer_size = sizeof(buffer); - if (get_hostfxr_path(buffer, &buffer_size, nullptr) != 0) { + if (get_hostfxr_path(buffer, &buffer_size, nullptr) != 0) + { + LOG_ERROR("Failed to locate hostfxr via nethost"); return false; } string_t hostfxr_location(buffer); #endif void *lib = load_library(hostfxr_location.c_str()); + if (lib == nullptr) + { + LOG_ERROR("Failed to load hostfxr library"); + return false; + } m_init_fptr = (hostfxr_initialize_for_runtime_config_fn)get_export(lib, "hostfxr_initialize_for_runtime_config"); m_get_delegate_fptr = (hostfxr_get_runtime_delegate_fn)get_export(lib, "hostfxr_get_runtime_delegate"); m_close_fptr = (hostfxr_close_fn)get_export(lib, "hostfxr_close"); @@ -219,8 +262,8 @@ hostfxr_handle DotnetEnvironment::get_dotnet(const char_t *config_path){ params.host_path = nullptr; params.dotnet_root = nullptr; +#ifdef _WIN32 // Get the required size for the environment variable -#if defined(_WIN32) || defined(WINDOWS) DWORD requiredSize = GetEnvironmentVariableW(L"DOTNET_ROOT", nullptr, 0); std::vector dotnet_root_buffer; if (requiredSize > 0) @@ -232,7 +275,12 @@ hostfxr_handle DotnetEnvironment::get_dotnet(const char_t *config_path){ } } #else - params.dotnet_root = getenv("DOTNET_ROOT"); + // On Linux, read DOTNET_ROOT from the environment (char-based) + const char *dotnet_root_env = getenv("DOTNET_ROOT"); + if (dotnet_root_env != nullptr) + { + params.dotnet_root = dotnet_root_env; + } #endif int rc = m_init_fptr(config_path, ¶ms, &cxt); diff --git a/language-extensions/dotnet-core-CSharp/test/build/linux/build-dotnet-core-CSharp-extension-test.sh b/language-extensions/dotnet-core-CSharp/test/build/linux/build-dotnet-core-CSharp-extension-test.sh new file mode 100644 index 00000000..b93e4453 --- /dev/null +++ b/language-extensions/dotnet-core-CSharp/test/build/linux/build-dotnet-core-CSharp-extension-test.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Set environment variables +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ENL_ROOT="$(cd "$SCRIPT_DIR/../../../../.." && pwd)" +DOTNETCORE_CSHARP_EXTENSION_TEST_HOME="$ENL_ROOT/language-extensions/dotnet-core-CSharp/test" +DOTNETCORE_CSHARP_EXTENSION_TEST_WORKING_DIR="$ENL_ROOT/build-output/dotnet-core-CSharp-extension-test/linux" + +# Clean and create build working directory +rm -rf "$DOTNETCORE_CSHARP_EXTENSION_TEST_WORKING_DIR" +mkdir -p "$DOTNETCORE_CSHARP_EXTENSION_TEST_WORKING_DIR" + +# Default to release if no arguments +if [ $# -eq 0 ]; then + set -- "release" +fi + +for CMAKE_CONFIGURATION in "$@"; do + CMAKE_CONFIGURATION="$(echo "$CMAKE_CONFIGURATION" | tr '[:upper:]' '[:lower:]')" + if [ "$CMAKE_CONFIGURATION" != "debug" ]; then + CMAKE_CONFIGURATION="release" + fi + + BUILD_OUTPUT="$DOTNETCORE_CSHARP_EXTENSION_TEST_WORKING_DIR/$CMAKE_CONFIGURATION" + mkdir -p "$BUILD_OUTPUT" + pushd "$BUILD_OUTPUT" > /dev/null + + echo "[INFO] Generating dotnet-core-CSharp-extension test project build files using CMAKE_CONFIGURATION=$CMAKE_CONFIGURATION" + + # Call cmake to generate makefiles + cmake \ + -DCMAKE_INSTALL_PREFIX:PATH="$DOTNETCORE_CSHARP_EXTENSION_TEST_WORKING_DIR/$CMAKE_CONFIGURATION" \ + -DENL_ROOT="$ENL_ROOT" \ + -DCMAKE_CONFIGURATION="$CMAKE_CONFIGURATION" \ + -DPLATFORM=linux \ + "$DOTNETCORE_CSHARP_EXTENSION_TEST_HOME/src/native" + + echo "[INFO] Building dotnet-core-CSharp-extension test project using CMAKE_CONFIGURATION=$CMAKE_CONFIGURATION" + + # Build managed test executor + dotnet build \ + "$DOTNETCORE_CSHARP_EXTENSION_TEST_HOME/src/managed/Microsoft.SqlServer.CSharpExtensionTest.csproj" \ + -m \ + -c "$CMAKE_CONFIGURATION" \ + -o "$BUILD_OUTPUT" \ + --no-restore \ + --no-dependencies + + # Delete Microsoft.SqlServer.CSharpExtension.dll to avoid test executor referencing it + # instead of the extension itself + rm -f "$BUILD_OUTPUT/Microsoft.SqlServer.CSharpExtension.dll" + + # Build native test executable + cmake --build . --config "$CMAKE_CONFIGURATION" --target install + + popd > /dev/null + + echo "Success: Built dotnet-core-CSharp-extension-test for $CMAKE_CONFIGURATION configuration." +done + +exit 0 diff --git a/language-extensions/dotnet-core-CSharp/test/build/linux/run-dotnet-core-CSharp-extension-test.sh b/language-extensions/dotnet-core-CSharp/test/build/linux/run-dotnet-core-CSharp-extension-test.sh new file mode 100644 index 00000000..978763d2 --- /dev/null +++ b/language-extensions/dotnet-core-CSharp/test/build/linux/run-dotnet-core-CSharp-extension-test.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Set environment variables +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ENL_ROOT="$(cd "$SCRIPT_DIR/../../../../.." && pwd)" +DOTNETCORE_CSHARP_EXTENSION_TEST_WORKING_DIR="$ENL_ROOT/build-output/dotnet-core-CSharp-extension-test/linux" + +# Default to release if no arguments +if [ $# -eq 0 ]; then + set -- "release" +fi + +for CMAKE_CONFIGURATION in "$@"; do + CMAKE_CONFIGURATION="$(echo "$CMAKE_CONFIGURATION" | tr '[:upper:]' '[:lower:]')" + if [ "$CMAKE_CONFIGURATION" != "debug" ]; then + CMAKE_CONFIGURATION="release" + fi + + TEST_DIR="$DOTNETCORE_CSHARP_EXTENSION_TEST_WORKING_DIR/$CMAKE_CONFIGURATION" + pushd "$TEST_DIR" > /dev/null + + echo "[INFO] Running dotnet-core-CSharp-extension tests for $CMAKE_CONFIGURATION configuration" + + ./dotnet-core-CSharp-extension-test \ + --gtest_output=xml:"$ENL_ROOT/out/TestReport_dotnet-core-csharp-extension-test.xml" + + popd > /dev/null +done + +exit 0 diff --git a/language-extensions/dotnet-core-CSharp/test/include/Common.h b/language-extensions/dotnet-core-CSharp/test/include/Common.h index 01c09f56..1e6eae60 100644 --- a/language-extensions/dotnet-core-CSharp/test/include/Common.h +++ b/language-extensions/dotnet-core-CSharp/test/include/Common.h @@ -12,11 +12,21 @@ #ifdef _WIN64 #include +#else +#include +#include +#include +typedef void* HINSTANCE; #endif -#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING #include +#include +#ifdef _WIN32 +#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING #include +#else +#include +#endif #include #include #include diff --git a/language-extensions/dotnet-core-CSharp/test/src/managed/Microsoft.SqlServer.CSharpExtensionTest.csproj b/language-extensions/dotnet-core-CSharp/test/src/managed/Microsoft.SqlServer.CSharpExtensionTest.csproj index 39cc5d40..fb06fdaf 100644 --- a/language-extensions/dotnet-core-CSharp/test/src/managed/Microsoft.SqlServer.CSharpExtensionTest.csproj +++ b/language-extensions/dotnet-core-CSharp/test/src/managed/Microsoft.SqlServer.CSharpExtensionTest.csproj @@ -10,9 +10,14 @@ - + ..\..\..\..\..\build-output\dotnet-core-CSharp-extension\windows\release\Microsoft.SqlServer.CSharpExtension.dll + + + ..\..\..\..\..\build-output\dotnet-core-CSharp-extension\linux\release\Microsoft.SqlServer.CSharpExtension.dll + + diff --git a/language-extensions/dotnet-core-CSharp/test/src/native/CMakeLists.txt b/language-extensions/dotnet-core-CSharp/test/src/native/CMakeLists.txt index 4a10e9db..63680887 100644 --- a/language-extensions/dotnet-core-CSharp/test/src/native/CMakeLists.txt +++ b/language-extensions/dotnet-core-CSharp/test/src/native/CMakeLists.txt @@ -4,6 +4,9 @@ cmake_minimum_required (VERSION 3.5) # project(dotnet-core-CSharp-extension-test VERSION 1.0 LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + # All string comparisons are CASE SENSITIVE in CMAKE. Make all strings lower before comparisons! # string(TOLOWER ${PLATFORM} PLATFORM) @@ -23,31 +26,50 @@ add_executable(dotnet-core-CSharp-extension-test ${DOTNETCORE_CSHARP_EXTENSION_TEST_SOURCE_FILES} ) -target_compile_options(dotnet-core-CSharp-extension-test PRIVATE --std=c++17) - -# Set the DLLEXPORT variable to export symbols -# -add_definitions(-DWIN_EXPORT -D_WIN64 -D_WINDOWS) -target_compile_definitions(dotnet-core-CSharp-extension-test PRIVATE WIN_EXPORT) - - file(TO_CMAKE_PATH ${ENL_ROOT}/language-extensions/dotnet-core-CSharp/test/include/ DOTNETCORE_CSHARP_EXTENSION_TEST_INCLUDE_DIR) file(TO_CMAKE_PATH ${ENL_ROOT}/build-output/dotnet-core-CSharp-extension-test/${PLATFORM}/ DOTNETCORE_CSHARP_EXTENSION_TEST_WORKING_DIR) file(TO_CMAKE_PATH ${DOTNETCORE_CSHARP_EXTENSION_TEST_WORKING_DIR}/${CMAKE_CONFIGURATION} DOTNETCORE_CSHARP_EXTENSION_TEST_INSTALL_DIR) -file(TO_CMAKE_PATH ${ENL_ROOT}/packages/Microsoft.googletest.v140.windesktop.msvcstl.dyn.rt-dyn.1.8.1.3 GTEST_HOME) -file(TO_CMAKE_PATH ${GTEST_HOME}/build/native/include GTEST_INCLUDE_DIR) -file(TO_CMAKE_PATH ${GTEST_HOME}/lib/native/v140/windesktop/msvcstl/dyn/rt-dyn/x64/${CMAKE_CONFIGURATION} GTEST_LIB_PATH) +if(WIN32) + # Set the DLLEXPORT variable to export symbols + # + add_definitions(-DWIN_EXPORT -D_WIN64 -D_WINDOWS) + target_compile_definitions(dotnet-core-CSharp-extension-test PRIVATE WIN_EXPORT) -# MDd is for debug DLL and MD is for release DLL -# -if (${CMAKE_CONFIGURATION} STREQUAL debug) - set(COMPILE_OPTIONS ${COMPILE_OPTIONS} /MDd) - find_library(GTEST_LIB gtestd ${GTEST_LIB_PATH}) - set(CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG} /Od) + file(TO_CMAKE_PATH ${ENL_ROOT}/packages/Microsoft.googletest.v140.windesktop.msvcstl.dyn.rt-dyn.1.8.1.3 GTEST_HOME) + file(TO_CMAKE_PATH ${GTEST_HOME}/build/native/include GTEST_INCLUDE_DIR) + file(TO_CMAKE_PATH ${GTEST_HOME}/lib/native/v140/windesktop/msvcstl/dyn/rt-dyn/x64/${CMAKE_CONFIGURATION} GTEST_LIB_PATH) + + # MDd is for debug DLL and MD is for release DLL + # + if (${CMAKE_CONFIGURATION} STREQUAL debug) + set(COMPILE_OPTIONS ${COMPILE_OPTIONS} /MDd) + find_library(GTEST_LIB gtestd ${GTEST_LIB_PATH}) + set(CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG} /Od) + else() + set(COMPILE_OPTIONS ${COMPILE_OPTIONS} /MD /O2) + find_library(GTEST_LIB gtest ${GTEST_LIB_PATH}) + endif() else() - set(COMPILE_OPTIONS ${COMPILE_OPTIONS} /MD /O2) - find_library(GTEST_LIB gtest ${GTEST_LIB_PATH}) + # On Linux, use the googletest built from source (installed into build-output/googletest/linux/) + # + file(TO_CMAKE_PATH ${ENL_ROOT}/build-output/googletest/linux GTEST_BUILD_ROOT) + file(TO_CMAKE_PATH ${GTEST_BUILD_ROOT}/googletest-src/googletest/include GTEST_INCLUDE_DIR) + + # The Linux googletest build produces libgtest.a for all configurations + # (no separate debug/release naming convention). + # + file(TO_CMAKE_PATH ${GTEST_BUILD_ROOT}/lib/libgtest.a GTEST_LIB) + + # Use -fshort-wchar so wchar_t is 2 bytes, matching SQL Server's UTF-16 + # and the managed C# extension (consistent with R/Python/Java extensions). + # + target_compile_options(dotnet-core-CSharp-extension-test PRIVATE -fshort-wchar) + + # Link with dl for dlopen/dlsym and pthread for gtest + # + find_package(Threads REQUIRED) + target_link_libraries(dotnet-core-CSharp-extension-test Threads::Threads ${CMAKE_DL_LIBS}) endif() # This is not a standard include path so test projects need diff --git a/language-extensions/dotnet-core-CSharp/test/src/native/CSharpExtensionApiTests.cpp b/language-extensions/dotnet-core-CSharp/test/src/native/CSharpExtensionApiTests.cpp index fed15afb..a2df2cf8 100644 --- a/language-extensions/dotnet-core-CSharp/test/src/native/CSharpExtensionApiTests.cpp +++ b/language-extensions/dotnet-core-CSharp/test/src/native/CSharpExtensionApiTests.cpp @@ -11,8 +11,18 @@ //********************************************************************* #include "CSharpExtensionApiTests.h" +#ifndef _WIN32 +#include +#include +#include +#endif + using namespace std; +#ifdef _WIN32 namespace fs = experimental::filesystem; +#else +namespace fs = filesystem; +#endif namespace ExtensionApiTest { @@ -90,6 +100,7 @@ namespace ExtensionApiTest // void CSharpExtensionApiTests::SetUpPath() { +#ifdef _WIN32 char path[MAX_PATH+1] = {0}; GetModuleFileName(NULL, path, MAX_PATH); fs::path exePath = path; @@ -100,6 +111,24 @@ namespace ExtensionApiTest sm_extensionPath = (buildOutputPath / "dotnet-core-CSharp-extension/windows/release").string(); #endif sm_libPath = exePath.parent_path().string(); +#else + char path[PATH_MAX] = {0}; + ssize_t len = readlink("/proc/self/exe", path, sizeof(path) - 1); + if (len == -1) + { + throw std::runtime_error( + std::string("readlink(\"/proc/self/exe\") failed: ") + std::strerror(errno)); + } + path[len] = '\0'; + fs::path exePath = path; + fs::path buildOutputPath = exePath.parent_path().parent_path().parent_path().parent_path(); + #if defined(_DEBUG) + sm_extensionPath = (buildOutputPath / "dotnet-core-CSharp-extension/linux/debug").string(); + #else + sm_extensionPath = (buildOutputPath / "dotnet-core-CSharp-extension/linux/release").string(); + #endif + sm_libPath = exePath.parent_path().string(); +#endif } // Name: CSharpExtensionApiTest::SetupVariables @@ -218,6 +247,7 @@ namespace ExtensionApiTest // void CSharpExtensionApiTests::GetHandles() { +#ifdef _WIN32 sm_libHandle = LoadLibrary((sm_extensionPath+"\\nativecsharpextension.dll").c_str()); EXPECT_TRUE(sm_libHandle != nullptr); @@ -250,6 +280,40 @@ namespace ExtensionApiTest sm_cleanupFuncPtr = reinterpret_cast(GetProcAddress(sm_libHandle, "Cleanup")); EXPECT_TRUE(sm_cleanupFuncPtr != nullptr); +#else + sm_libHandle = dlopen((sm_extensionPath+"/libnativecsharpextension.so").c_str(), RTLD_LAZY); + EXPECT_TRUE(sm_libHandle != nullptr) << "dlopen failed: " << dlerror(); + + sm_initFuncPtr = reinterpret_cast(dlsym(sm_libHandle, "Init")); + EXPECT_TRUE(sm_initFuncPtr != nullptr); + + sm_initSessionFuncPtr = reinterpret_cast(dlsym(sm_libHandle, "InitSession")); + EXPECT_TRUE(sm_initSessionFuncPtr != nullptr); + + sm_initColumnFuncPtr = reinterpret_cast(dlsym(sm_libHandle, "InitColumn")); + EXPECT_TRUE(sm_initColumnFuncPtr != nullptr); + + sm_initParamFuncPtr = reinterpret_cast(dlsym(sm_libHandle, "InitParam")); + EXPECT_TRUE(sm_initParamFuncPtr != nullptr); + + sm_executeFuncPtr = reinterpret_cast(dlsym(sm_libHandle, "Execute")); + EXPECT_TRUE(sm_executeFuncPtr != nullptr); + + sm_getResultColumnFuncPtr = reinterpret_cast(dlsym(sm_libHandle, "GetResultColumn")); + EXPECT_TRUE(sm_getResultColumnFuncPtr != nullptr); + + sm_getResultsFuncPtr = reinterpret_cast(dlsym(sm_libHandle, "GetResults")); + EXPECT_TRUE(sm_getResultsFuncPtr != nullptr); + + sm_getOutputParamFuncPtr = reinterpret_cast(dlsym(sm_libHandle, "GetOutputParam")); + EXPECT_TRUE(sm_getOutputParamFuncPtr != nullptr); + + sm_cleanupSessionFuncPtr = reinterpret_cast(dlsym(sm_libHandle, "CleanupSession")); + EXPECT_TRUE(sm_cleanupSessionFuncPtr != nullptr); + + sm_cleanupFuncPtr = reinterpret_cast(dlsym(sm_libHandle, "Cleanup")); + EXPECT_TRUE(sm_cleanupFuncPtr != nullptr); +#endif } //---------------------------------------------------------------------------------------------- diff --git a/language-extensions/dotnet-core-CSharp/test/src/native/CSharpGetOutputParamTests.cpp b/language-extensions/dotnet-core-CSharp/test/src/native/CSharpGetOutputParamTests.cpp index f59015fa..842c3071 100644 --- a/language-extensions/dotnet-core-CSharp/test/src/native/CSharpGetOutputParamTests.cpp +++ b/language-extensions/dotnet-core-CSharp/test/src/native/CSharpGetOutputParamTests.cpp @@ -560,25 +560,19 @@ namespace ExtensionApiTest EXPECT_EQ(outputSchemaColumnsNumber, 0); - const vector ExpectedParamValueStrings = { - // Test simple NCHAR(5) value with exact string length as the type allows i.e. here 5. - // - L"HELLO", - // Test NVARCHAR(6) value with string length more than the type allows - expected truncation. - // - L"C#Exte", - // Test a 0 length string - // - L"" , - // Test NCHAR(10) value with string length less than the type allows. - // - L"WORLD"}; + // Use raw wchar_t* pointers instead of std::wstring to avoid ABI + // issues with -fshort-wchar on Linux. + // + const wchar_t* expectedStr0 = L"HELLO"; + const wchar_t* expectedStr1 = L"C#Exte"; + const wchar_t* expectedStr2 = L""; + const wchar_t* expectedStr3 = L"WORLD"; vector expectedParamValues = { - ExpectedParamValueStrings[0].c_str(), - ExpectedParamValueStrings[1].c_str(), - ExpectedParamValueStrings[2].c_str(), - ExpectedParamValueStrings[3].c_str(), + expectedStr0, + expectedStr1, + expectedStr2, + expectedStr3, // Test None returned in a NVARCHAR(5) parameter. // @@ -591,10 +585,10 @@ namespace ExtensionApiTest // strLenOrInd is in bytes for NCHAR/NVARCHAR, so multiply by sizeof(wchar_t) // vector expectedStrLenOrInd = { - static_cast(ExpectedParamValueStrings[0].length() * sizeof(wchar_t)), - static_cast(ExpectedParamValueStrings[1].length() * sizeof(wchar_t)), - static_cast(ExpectedParamValueStrings[2].length() * sizeof(wchar_t)), - static_cast(ExpectedParamValueStrings[3].length() * sizeof(wchar_t)), + static_cast(GetWStringLength(expectedStr0) * sizeof(wchar_t)), + static_cast(GetWStringLength(expectedStr1) * sizeof(wchar_t)), + static_cast(GetWStringLength(expectedStr2) * sizeof(wchar_t)), + static_cast(GetWStringLength(expectedStr3) * sizeof(wchar_t)), SQL_NULL_DATA, SQL_NULL_DATA }; @@ -651,30 +645,34 @@ namespace ExtensionApiTest EXPECT_EQ(outputSchemaColumnsNumber, 0); - // Build expected values to match what the C# executor returns + // Build expected values to match what the C# executor returns. + // Use vector instead of wstring to avoid ABI issues with -fshort-wchar on Linux. // - wstring largeAsciiString(10000, L'A'); + vector largeAsciiData(10000, L'A'); + largeAsciiData.push_back(L'\0'); // Build Unicode pattern string: "你好世界€" repeated 1000 times = 5000 characters // - wstring unicodePattern = L"你好世界€"; - wstring largeUnicodeString; - largeUnicodeString.reserve(5000); + const wchar_t unicodePattern[] = L"你好世界€"; + SQLINTEGER patternLen = GetWStringLength(unicodePattern); + vector largeUnicodeData; + largeUnicodeData.reserve(5001); for (int i = 0; i < 1000; i++) { - largeUnicodeString += unicodePattern; + largeUnicodeData.insert(largeUnicodeData.end(), unicodePattern, unicodePattern + patternLen); } + largeUnicodeData.push_back(L'\0'); vector expectedParamValues = { - largeAsciiString.c_str(), - largeUnicodeString.c_str(), + largeAsciiData.data(), + largeUnicodeData.data(), nullptr }; // strLenOrInd is in bytes for NCHAR/NVARCHAR, so multiply by sizeof(wchar_t) // vector expectedStrLenOrInd = { - static_cast(largeAsciiString.length() * sizeof(wchar_t)), - static_cast(largeUnicodeString.length() * sizeof(wchar_t)), + static_cast((largeAsciiData.size() - 1) * sizeof(wchar_t)), + static_cast((largeUnicodeData.size() - 1) * sizeof(wchar_t)), SQL_NULL_DATA }; GetWStringOutputParam( @@ -728,29 +726,29 @@ namespace ExtensionApiTest EXPECT_EQ(outputSchemaColumnsNumber, 0); - // Build expected values to match what the C# executor returns + // Build expected values to match what the C# executor returns. + // Use raw wchar_t* pointers instead of std::wstring to avoid ABI + // issues with -fshort-wchar on Linux. // Note: Emoji are surrogate pairs in UTF-16, so 😀 = 2 wchar_t, 👍 = 2 wchar_t // - const vector ExpectedParamValueStrings = { - L"Hi\U0001F600\U0001F44D", // "Hi" + grinning face + thumbs up (6 UTF-16 code units) - L"Café résumé naïve", // Accented characters (17 chars) - L"Hello世界こんにちは", // Mixed scripts (12 chars) - L"€100 £50 ¥1000 ©®™" // Currency and special symbols (18 chars) - }; + const wchar_t* expectedStr0 = L"Hi\U0001F600\U0001F44D"; // "Hi" + grinning face + thumbs up (6 UTF-16 code units) + const wchar_t* expectedStr1 = L"Café résumé naïve"; // Accented characters (17 chars) + const wchar_t* expectedStr2 = L"Hello世界こんにちは"; // Mixed scripts (12 chars) + const wchar_t* expectedStr3 = L"€100 £50 ¥1000 ©®™"; // Currency and special symbols (18 chars) vector expectedParamValues = { - ExpectedParamValueStrings[0].c_str(), - ExpectedParamValueStrings[1].c_str(), - ExpectedParamValueStrings[2].c_str(), - ExpectedParamValueStrings[3].c_str() }; + expectedStr0, + expectedStr1, + expectedStr2, + expectedStr3 }; // strLenOrInd is in bytes for NCHAR/NVARCHAR, so multiply by sizeof(wchar_t) // vector expectedStrLenOrInd = { - static_cast(ExpectedParamValueStrings[0].length() * sizeof(wchar_t)), - static_cast(ExpectedParamValueStrings[1].length() * sizeof(wchar_t)), - static_cast(ExpectedParamValueStrings[2].length() * sizeof(wchar_t)), - static_cast(ExpectedParamValueStrings[3].length() * sizeof(wchar_t)) }; + static_cast(GetWStringLength(expectedStr0) * sizeof(wchar_t)), + static_cast(GetWStringLength(expectedStr1) * sizeof(wchar_t)), + static_cast(GetWStringLength(expectedStr2) * sizeof(wchar_t)), + static_cast(GetWStringLength(expectedStr3) * sizeof(wchar_t)) }; GetWStringOutputParam( expectedParamValues, @@ -880,15 +878,12 @@ namespace ExtensionApiTest { EXPECT_NE(paramValue, nullptr); - // strLen_or_Ind is in bytes, divide by sizeof(wchar_t) to get character count + // Compare raw UTF-16 bytes directly (avoids std::wstring which is + // ABI-incompatible with -fshort-wchar on Linux). // - SQLINTEGER charCount = strLen_or_Ind / sizeof(wchar_t); - wstring paramValueString(static_cast(paramValue), charCount); - - SQLINTEGER expectedCharCount = expectedStrLenOrInd[paramNumber] / sizeof(wchar_t); - wstring expectedParamValueString(expectedParamValues[paramNumber], expectedCharCount); - - EXPECT_EQ(paramValueString, expectedParamValueString); + EXPECT_EQ(memcmp(paramValue, + expectedParamValues[paramNumber], + expectedStrLenOrInd[paramNumber]), 0); } else {