Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

embed dll loader #120

Merged
merged 3 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# CHANGELOG

## Unreleased

#### Fixes

* Embedding dll loader as another NIF to avoid failures when running in release mode (#120)

## v0.7.3

#### Fixes
Expand Down
12 changes: 12 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,18 @@ set_target_properties(adbc_nif PROPERTIES
BUILD_WITH_INSTALL_RPATH TRUE
)

if(WIN32)
file(GLOB adbc_dll_loader_sources CONFIGURE_DEPENDS "${C_SRC}/dll_loader/*.cpp")
add_library(adbc_dll_loader SHARED ${adbc_dll_loader_sources})
set_property(TARGET adbc_dll_loader PROPERTY CXX_STANDARD 17)
target_include_directories(adbc_dll_loader PUBLIC ${ERTS_INCLUDE_DIR})
install(
TARGETS adbc_dll_loader
DESTINATION "${PRIV_DIR}"
)
SET_TARGET_PROPERTIES(adbc_dll_loader PROPERTIES PREFIX "")
endif()

if(UNIX AND NOT APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-unused-but-set-variable -Wno-reorder")
set_target_properties(adbc_nif PROPERTIES INSTALL_RPATH "\$ORIGIN/lib")
Expand Down
107 changes: 107 additions & 0 deletions c_src/dll_loader/adbc_dll_loader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#include <stdlib.h>
#include <cstring>
#include <string>
#include <sstream>
#include <vector>
#include <erl_nif.h>
#include <windows.h>
#include <libloaderapi.h>
#include <winbase.h>
#include <wchar.h>

#define NIF(NAME) ERL_NIF_TERM NAME(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])

// Helper for returning `{:error, msg}` from NIF.
ERL_NIF_TERM error(ErlNifEnv *env, const char *msg)
{
ERL_NIF_TERM atom = enif_make_atom(env, "error");
ERL_NIF_TERM msg_term = enif_make_string(env, msg, ERL_NIF_LATIN1);
return enif_make_tuple2(env, atom, msg_term);
}

// Helper for returning `{:ok, term}` from NIF.
ERL_NIF_TERM ok(ErlNifEnv *env)
{
return enif_make_atom(env, "ok");
}

NIF(add_dll_directory) {
static bool path_updated = false;
if (path_updated) return ok(env);

wchar_t dll_path_c[65536];
char err_msg[128] = { '\0' };
HMODULE hm = NULL;
if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCWSTR)&add_dll_directory, &hm) == 0) {
int ret = GetLastError();
snprintf(err_msg, sizeof(err_msg) - 1, "GetModuleHandle failed, error = %d\r\n", ret);
return error(env, err_msg);
}

if (GetModuleFileNameW(hm, (LPWSTR)dll_path_c, sizeof(dll_path_c)) == 0) {
int ret = GetLastError();
snprintf(err_msg, sizeof(err_msg) - 1, "GetModuleFileName failed, error = %d\r\n", ret);
return error(env, err_msg);
}

std::wstring dll_path = dll_path_c;
auto pos = dll_path.find_last_of(L'\\');
auto priv_dir = dll_path.substr(0, pos);

std::wstringstream torch_dir_ss;
torch_dir_ss << priv_dir << L"\\bin";
std::wstring torch_dir = torch_dir_ss.str();
PCWSTR directory_pcwstr = torch_dir.c_str();

WCHAR path_buffer[65536];
DWORD path_len = GetEnvironmentVariableW(L"PATH", path_buffer, 65536);
WCHAR new_path[65536];
new_path[0] = L'\0';
wcscpy_s(new_path, _countof(new_path), (const wchar_t*)path_buffer);
wcscat_s(new_path, _countof(new_path), (const wchar_t*)L";");
wcscat_s(new_path, _countof(new_path), (const wchar_t*)directory_pcwstr);
SetEnvironmentVariableW(L"PATH", new_path);

SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_USER_DIRS);
DLL_DIRECTORY_COOKIE ret = AddDllDirectory(directory_pcwstr);
if (ret == 0) {
DWORD last_error = GetLastError();
LPTSTR error_text = nullptr;
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, HRESULT_FROM_WIN32(last_error), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&error_text, 0, NULL);
if (error_text != nullptr) {
ERL_NIF_TERM ret_term = error(env, error_text);
LocalFree(error_text);
return ret_term;
} else {
ERL_NIF_TERM ret_term = error(env, "error happened when adding adbc driver runtime path, but cannot get formatted error message");
return ret_term;
}
}
path_updated = true;
return ok(env);
}

int upgrade(ErlNifEnv *env, void **priv_data, void **old_priv_data, ERL_NIF_TERM load_info) {
// Silence "unused var" warnings.
(void)(env);
(void)(priv_data);
(void)(old_priv_data);
(void)(load_info);

return 0;
}

int load(ErlNifEnv *,void **,ERL_NIF_TERM) {
return 0;
}

#define F(NAME, ARITY) \
{ \
#NAME, ARITY, NAME, 0 \
}

static ErlNifFunc nif_functions[] = {
F(add_dll_directory, 0)
};

ERL_NIF_INIT(Elixir.Adbc.Nif.DLLLoader, nif_functions, load, NULL, upgrade, NULL);
25 changes: 25 additions & 0 deletions lib/adbc/dll_loader.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule Adbc.Nif.DLLLoader do
@moduledoc false
@on_load :__on_load__
def __on_load__ do
case :os.type() do
{:win32, _} ->
path = :filename.join(:code.priv_dir(:adbc), ~c"adbc_dll_loader")
:erlang.load_nif(path, 0)
add_dll_directory()

_ ->
:ok
end
end

def add_dll_directory do
case :os.type() do
{:win32, _} ->
:erlang.nif_error(:not_loaded)

_ ->
:ok
end
end
end
4 changes: 3 additions & 1 deletion lib/adbc_nif.ex
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
defmodule Adbc.Nif do
@moduledoc false

alias Adbc.Nif.DLLLoader

@on_load :load_nif
def load_nif do
nif_file = ~c"#{:code.priv_dir(:adbc)}/adbc_nif"

:ok =
case :os.type() do
{:win32, _} ->
:dll_loader_helper_beam.add_dll_directory("#{:code.priv_dir(:adbc)}/bin")
DLLLoader.add_dll_directory()

_ ->
:ok
Expand Down
1 change: 0 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ defmodule Adbc.MixProject do
{:elixir_make, "~> 0.8", runtime: false},

# runtime
{:dll_loader_helper_beam, "~> 1.0"},
{:castore, "~> 1.0", optional: true},
{:decimal, "~> 2.1"},

Expand Down
1 change: 0 additions & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
"castore": {:hex, :castore, "1.0.6", "ffc42f110ebfdafab0ea159cd43d31365fa0af0ce4a02ecebf1707ae619ee727", [:mix], [], "hexpm", "374c6e7ca752296be3d6780a6d5b922854ffcc74123da90f2f328996b962d33a"},
"cc_precompiler": {:hex, :cc_precompiler, "0.1.10", "47c9c08d8869cf09b41da36538f62bc1abd3e19e41701c2cea2675b53c704258", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f6e046254e53cd6b41c6bacd70ae728011aa82b2742a80d6e2214855c6e06b22"},
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
"dll_loader_helper_beam": {:hex, :dll_loader_helper_beam, "1.2.2", "b86f97ec8fc64770c87468e41969eb309d87b29dd5a439b667e5954f85f8f65a", [:rebar3], [], "hexpm", "0e6119edde0ef5e42b4fe22d7dc71b7462e08573cee977c01a26ec5d9cd94a9a"},
"earmark_parser": {:hex, :earmark_parser, "1.4.42", "f23d856f41919f17cd06a493923a722d87a2d684f143a1e663c04a2b93100682", [:mix], [], "hexpm", "6915b6ca369b5f7346636a2f41c6a6d78b5af419d61a611079189233358b8b8b"},
"elixir_make": {:hex, :elixir_make, "0.8.3", "d38d7ee1578d722d89b4d452a3e36bcfdc644c618f0d063b874661876e708683", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "5c99a18571a756d4af7a4d89ca75c28ac899e6103af6f223982f09ce44942cc9"},
"ex_doc": {:hex, :ex_doc, "0.36.1", "4197d034f93e0b89ec79fac56e226107824adcce8d2dd0a26f5ed3a95efc36b1", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d7d26a7cf965dacadcd48f9fa7b5953d7d0cfa3b44fa7a65514427da44eafd89"},
Expand Down