diff --git a/UPGRADING.md b/UPGRADING.md index ec30083c7e18..e2a3c1cebd11 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -12,6 +12,13 @@ We're only listing the most relevant changes here that could have a relevant imp The [changelog](./CHANGELOG.md) contains more information about all changes in a specific release. +## Crystal 1.13 + +* `CRYSTAL_LIBRARY_RPATH` and the `preview_win32_delay_load` feature flag have + been removed. Individual DLLs can be explicitly delay-loaded with the MSVC + toolchain by using `/DELAYLOAD` as a linker flag. Similarly RPATH can be added + with GCC or Clang toolchains by adding `-Wl,-rpath`. + ## Crystal 1.9 * The implementation of the comparison operator `#<=>` between `Big*` (`BigDecimal`, diff --git a/spec/std/spec_helper.cr b/spec/std/spec_helper.cr index 69c80968b117..8bb0a9e1a2f2 100644 --- a/spec/std/spec_helper.cr +++ b/spec/std/spec_helper.cr @@ -86,10 +86,9 @@ def compile_file(source_file, *, bin_name = "executable_file", flags = %w(), fil args = ["build"] + flags + ["-o", executable_file, source_file] output = IO::Memory.new status = Process.run(compiler, args, env: { - "CRYSTAL_PATH" => Crystal::PATH, - "CRYSTAL_LIBRARY_PATH" => Crystal::LIBRARY_PATH, - "CRYSTAL_LIBRARY_RPATH" => Crystal::LIBRARY_RPATH, - "CRYSTAL_CACHE_DIR" => Crystal::CACHE_DIR, + "CRYSTAL_PATH" => Crystal::PATH, + "CRYSTAL_LIBRARY_PATH" => Crystal::LIBRARY_PATH, + "CRYSTAL_CACHE_DIR" => Crystal::CACHE_DIR, }, output: output, error: output) unless status.success? diff --git a/src/compiler/crystal/codegen/link.cr b/src/compiler/crystal/codegen/link.cr index 245d3de24fcf..3601aa0fd870 100644 --- a/src/compiler/crystal/codegen/link.cr +++ b/src/compiler/crystal/codegen/link.cr @@ -114,59 +114,6 @@ module Crystal default_paths.join(Process::PATH_DELIMITER) end - def self.default_rpath : String - # do not call `CrystalPath.expand_paths`, as `$ORIGIN` inside this env - # variable is always expanded at run time - ENV.fetch("CRYSTAL_LIBRARY_RPATH", "") - end - - # Adds the compiler itself's RPATH to the environment for the duration of - # the block. `$ORIGIN` in the compiler's RPATH is expanded immediately, but - # `$ORIGIN`s in the existing environment variable are not expanded. For - # example, on Linux: - # - # - CRYSTAL_LIBRARY_RPATH of the compiler: `$ORIGIN/so` - # - Current $CRYSTAL_LIBRARY_RPATH: `/home/foo:$ORIGIN/mylibs` - # - Compiler's full path: `/opt/crystal` - # - Generated executable's Crystal::LIBRARY_RPATH: `/home/foo:$ORIGIN/mylibs:/opt/so` - # - # On Windows we additionally append the compiler's parent directory to the - # list, as if by appending `$ORIGIN` to the compiler's RPATH. This directory - # is effectively the first search entry on any Windows executable. Example: - # - # - CRYSTAL_LIBRARY_RPATH of the compiler: `$ORIGIN\dll` - # - Current %CRYSTAL_LIBRARY_RPATH%: `C:\bar;$ORIGIN\mylibs` - # - Compiler's full path: `C:\foo\crystal.exe` - # - Generated executable's Crystal::LIBRARY_RPATH: `C:\bar;$ORIGIN\mylibs;C:\foo\dll;C:\foo` - # - # Combining RPATHs multiple times has no effect; the `CRYSTAL_LIBRARY_RPATH` - # environment variable at compiler startup is used, not really the "current" - # one. This can happen when running a program that also uses macro `run`s. - def self.add_compiler_rpath(&) - executable_path = Process.executable_path - compiler_origin = File.dirname(executable_path) if executable_path - - current_rpaths = ORIGINAL_CRYSTAL_LIBRARY_RPATH.try &.split(Process::PATH_DELIMITER, remove_empty: true) - compiler_rpaths = Crystal::LIBRARY_RPATH.split(Process::PATH_DELIMITER, remove_empty: true) - CrystalPath.expand_paths(compiler_rpaths, compiler_origin) - - rpaths = compiler_rpaths - rpaths.concat(current_rpaths) if current_rpaths - {% if flag?(:win32) %} - rpaths << compiler_origin if compiler_origin - {% end %} - - old_env = ENV["CRYSTAL_LIBRARY_RPATH"]? - ENV["CRYSTAL_LIBRARY_RPATH"] = rpaths.join(Process::PATH_DELIMITER) - begin - yield - ensure - ENV["CRYSTAL_LIBRARY_RPATH"] = old_env - end - end - - private ORIGINAL_CRYSTAL_LIBRARY_RPATH = ENV["CRYSTAL_LIBRARY_RPATH"]? - class_getter paths : Array(String) do default_paths end diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index 7644f412bb8e..f8ece87e3d4b 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -349,7 +349,6 @@ class Crystal::Command hierarchy_exp : String?, cursor_location : String?, output_format : String, - combine_rpath : Bool, includes : Array(String), excludes : Array(String), verbose : Bool, @@ -357,7 +356,7 @@ class Crystal::Command tallies : Bool do def compile(output_filename = self.output_filename) compiler.emit_base_filename = emit_base_filename || output_filename.rchop(File.extname(output_filename)) - compiler.compile sources, output_filename, combine_rpath: combine_rpath + compiler.compile sources, output_filename end def top_level_semantic @@ -632,10 +631,9 @@ class Crystal::Command emit_base_filename = ::Path[sources.first.filename].stem end - combine_rpath = run && !compiler.no_codegen? @config = CompilerConfig.new compiler, sources, output_filename, emit_base_filename, arguments, specified_output, hierarchy_exp, cursor_location, output_format.not_nil!, - combine_rpath, includes, excludes, verbose, check, tallies + includes, excludes, verbose, check, tallies end private def gather_sources(filenames) diff --git a/src/compiler/crystal/command/env.cr b/src/compiler/crystal/command/env.cr index cf450ec55f3d..c4605860bf36 100644 --- a/src/compiler/crystal/command/env.cr +++ b/src/compiler/crystal/command/env.cr @@ -18,12 +18,11 @@ class Crystal::Command end vars = { - "CRYSTAL_CACHE_DIR" => CacheDir.instance.dir, - "CRYSTAL_PATH" => CrystalPath.default_path, - "CRYSTAL_VERSION" => Config.version || "", - "CRYSTAL_LIBRARY_PATH" => CrystalLibraryPath.default_path, - "CRYSTAL_LIBRARY_RPATH" => CrystalLibraryPath.default_rpath, - "CRYSTAL_OPTS" => ENV.fetch("CRYSTAL_OPTS", ""), + "CRYSTAL_CACHE_DIR" => CacheDir.instance.dir, + "CRYSTAL_PATH" => CrystalPath.default_path, + "CRYSTAL_VERSION" => Config.version || "", + "CRYSTAL_LIBRARY_PATH" => CrystalLibraryPath.default_path, + "CRYSTAL_OPTS" => ENV.fetch("CRYSTAL_OPTS", ""), } if var_names.empty? diff --git a/src/compiler/crystal/command/eval.cr b/src/compiler/crystal/command/eval.cr index 5db37cb0cd27..507c4cbe9750 100644 --- a/src/compiler/crystal/command/eval.cr +++ b/src/compiler/crystal/command/eval.cr @@ -26,7 +26,7 @@ class Crystal::Command output_filename = Crystal.temp_executable "eval" - compiler.compile sources, output_filename, combine_rpath: true + compiler.compile sources, output_filename execute output_filename, program_args, compiler end end diff --git a/src/compiler/crystal/command/spec.cr b/src/compiler/crystal/command/spec.cr index ce34b2ac4155..fd285b653432 100644 --- a/src/compiler/crystal/command/spec.cr +++ b/src/compiler/crystal/command/spec.cr @@ -96,7 +96,7 @@ class Crystal::Command output_filename = Crystal.temp_executable "spec" ENV["CRYSTAL_SPEC_COMPILER_BIN"] ||= Process.executable_path - compiler.compile sources, output_filename, combine_rpath: true + compiler.compile sources, output_filename report_warnings execute output_filename, options, compiler, error_on_exit: warnings_fail_on_exit? end diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 9cff591f8400..12c19ef8c12a 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -198,21 +198,12 @@ module Crystal # Compiles the given *source*, with *output_filename* as the name # of the generated executable. # - # If *combine_rpath* is true, add the compiler itself's RPATH to the - # generated executable via `CrystalLibraryPath.add_compiler_rpath`. This is - # used by the `run` / `eval` / `spec` commands as well as the macro `run` - # (via `Crystal::Program#macro_compile`), and never during cross-compiling. - # # Raises `Crystal::CodeError` if there's an error in the # source code. # # Raises `InvalidByteSequenceError` if the source code is not # valid UTF-8. - def compile(source : Source | Array(Source), output_filename : String, *, combine_rpath : Bool = false) : Result - if combine_rpath - return CrystalLibraryPath.add_compiler_rpath { compile(source, output_filename, combine_rpath: false) } - end - + def compile(source : Source | Array(Source), output_filename : String) : Result source = [source] unless source.is_a?(Array) program = new_program(source) node = parse program, source @@ -443,22 +434,6 @@ module Crystal end link_args = search_result.remaining_args.concat(search_result.library_paths).map { |arg| Process.quote_windows(arg) } - - if program.has_flag?("preview_win32_delay_load") - # "LINK : warning LNK4199: /DELAYLOAD:foo.dll ignored; no imports found from foo.dll" - # it is harmless to skip this error because not all import libraries are always used, much - # less the individual DLLs they refer to - link_args << "/IGNORE:4199" - - dlls = Set(String).new - search_result.library_paths.each do |library_path| - Crystal::System::LibraryArchive.imported_dlls(library_path).each do |dll| - dlls << dll.downcase - end - end - dlls.delete "kernel32.dll" - dlls.each { |dll| link_args << "/DELAYLOAD:#{dll}" } - end end {% end %} diff --git a/src/compiler/crystal/loader/msvc.cr b/src/compiler/crystal/loader/msvc.cr index 60aca1aae333..05bf988c9218 100644 --- a/src/compiler/crystal/loader/msvc.cr +++ b/src/compiler/crystal/loader/msvc.cr @@ -172,7 +172,7 @@ class Crystal::Loader end private def open_library(path : String) - # TODO: respect Crystal::LIBRARY_RPATH (#13490), or `@[Link(dll:)]`'s search order + # TODO: respect `@[Link(dll:)]`'s search order LibC.LoadLibraryExW(System.to_wstr(path), nil, 0) end diff --git a/src/compiler/crystal/macros/macros.cr b/src/compiler/crystal/macros/macros.cr index 42ebb6004bb3..a5d5714f115b 100644 --- a/src/compiler/crystal/macros/macros.cr +++ b/src/compiler/crystal/macros/macros.cr @@ -93,7 +93,7 @@ class Crystal::Program return CompiledMacroRun.new(executable_path, elapsed_time, true) end - result = host_compiler.compile Compiler::Source.new(filename, source), executable_path, combine_rpath: true + result = host_compiler.compile Compiler::Source.new(filename, source), executable_path # Write the new files from which 'filename' depends into the cache dir # (here we store how to obtain these files, because a require might use diff --git a/src/compiler/crystal/program.cr b/src/compiler/crystal/program.cr index f5abde7e7bfa..b1cc99f0dfc6 100644 --- a/src/compiler/crystal/program.cr +++ b/src/compiler/crystal/program.cr @@ -311,11 +311,6 @@ module Crystal The value is defined by the environment variables `CRYSTAL_LIBRARY_PATH`. MD - define_crystal_string_constant "LIBRARY_RPATH", Crystal::CrystalLibraryPath.default_rpath, <<-MD - Colon-separated paths where the loader searches for dynamic libraries at runtime. - - The value is defined by the environment variables `CRYSTAL_LIBRARY_RPATH`. - MD define_crystal_string_constant "VERSION", Crystal::Config.version, <<-MD The version of the Crystal compiler. MD diff --git a/src/compiler/crystal/tools/doc/generator.cr b/src/compiler/crystal/tools/doc/generator.cr index d40aa8a1325e..635a6be65731 100644 --- a/src/compiler/crystal/tools/doc/generator.cr +++ b/src/compiler/crystal/tools/doc/generator.cr @@ -227,7 +227,7 @@ class Crystal::Doc::Generator {"BUILD_COMMIT", "BUILD_DATE", "CACHE_DIR", "DEFAULT_PATH", "DESCRIPTION", "PATH", "VERSION", "LLVM_VERSION", - "LIBRARY_PATH", "LIBRARY_RPATH", "HOST_TRIPLE", "TARGET_TRIPLE"}.each do |name| + "LIBRARY_PATH", "HOST_TRIPLE", "TARGET_TRIPLE"}.each do |name| return true if type == crystal_type.types[name]? end diff --git a/src/crystal/main.cr b/src/crystal/main.cr index 480571d06fb4..059a822c5ff4 100644 --- a/src/crystal/main.cr +++ b/src/crystal/main.cr @@ -1,17 +1,5 @@ require "process/executable_path" # Process::PATH_DELIMITER -module Crystal - {% unless Crystal.has_constant?("LIBRARY_RPATH") %} - LIBRARY_RPATH = {{ env("CRYSTAL_LIBRARY_RPATH") || "" }} - {% end %} -end - -{% if flag?(:unix) && !flag?(:darwin) %} - {% unless Crystal::LIBRARY_RPATH.empty? %} - # TODO: is there a better way to quote this? - @[Link(ldflags: {{ "'-Wl,-rpath,#{Crystal::LIBRARY_RPATH.id}'" }})] - {% end %} -{% end %} lib LibCrystalMain @[Raises] fun __crystal_main(argc : Int32, argv : UInt8**) @@ -143,7 +131,6 @@ end {% if flag?(:win32) %} require "./system/win32/wmain" - require "./system/win32/delay_load" {% end %} {% if flag?(:wasi) %} diff --git a/src/crystal/system/win32/delay_load.cr b/src/crystal/system/win32/delay_load.cr deleted file mode 100644 index 49ed467156cb..000000000000 --- a/src/crystal/system/win32/delay_load.cr +++ /dev/null @@ -1,353 +0,0 @@ -require "c/delayimp" -require "c/heapapi" - -lib LibC - $image_base = __ImageBase : IMAGE_DOS_HEADER -end - -private macro p_from_rva(rva) - pointerof(LibC.image_base).as(UInt8*) + {{ rva }} -end - -private macro print_error(format, *args) - {% if args.empty? %} - %str = {{ format }} - LibC.WriteFile(LibC.GetStdHandle(LibC::STD_ERROR_HANDLE), %str, %str.bytesize, out _, nil) - {% else %} - %buf = uninitialized LibC::CHAR[1024] - %args = uninitialized Void*[{{ args.size }}] - {% for arg, i in args %} - %args[{{ i }}] = ({{ arg }}).as(Void*) - {% end %} - %len = LibC.FormatMessageA(LibC::FORMAT_MESSAGE_FROM_STRING | LibC::FORMAT_MESSAGE_ARGUMENT_ARRAY, {{ format }}, 0, 0, %buf, %buf.size, %args) - LibC.WriteFile(LibC.GetStdHandle(LibC::STD_ERROR_HANDLE), %buf, %len, out _, nil) - {% end %} -end - -module Crystal::System::DelayLoad - @[Extern] - record InternalImgDelayDescr, - grAttrs : LibC::DWORD, - szName : LibC::LPSTR, - phmod : LibC::HMODULE*, - pIAT : LibC::IMAGE_THUNK_DATA*, - pINT : LibC::IMAGE_THUNK_DATA*, - pBoundIAT : LibC::IMAGE_THUNK_DATA*, - pUnloadIAT : LibC::IMAGE_THUNK_DATA*, - dwTimeStamp : LibC::DWORD - - @[AlwaysInline] - def self.pinh_from_image_base(hmod : LibC::HMODULE) - (hmod.as(UInt8*) + hmod.as(LibC::IMAGE_DOS_HEADER*).value.e_lfanew).as(LibC::IMAGE_NT_HEADERS*) - end - - @[AlwaysInline] - def self.interlocked_exchange(atomic : LibC::HMODULE*, value : LibC::HMODULE) - Atomic::Ops.atomicrmw(:xchg, atomic, value, :sequentially_consistent, false) - end - - # the functions below work on null-terminated strings; they must not use any C - # runtime features nor the GC! bang methods may allocate memory - - # returns the length in character units of the null-terminated string *str* - private def self.strlen(str : LibC::WCHAR*) : Int32 - len = 0 - while str.value != 0 - len &+= 1 - str += 1 - end - len - end - - # assigns *src* to *dst*, and returns the end of the new string in *dst* - private def self.strcpy(dst : LibC::WCHAR*, src : LibC::WCHAR*) : LibC::WCHAR* - while src.value != 0 - dst.value = src.value - dst += 1 - src += 1 - end - dst - end - - # assigns the concatenation of *args* to the buffer at *buf* with the given - # *size*, possibly reallocating it, and returns the new buffer - private def self.strcat(buf : LibC::WCHAR*, size : Int32, *args : *T) : {LibC::WCHAR*, Int32} forall T - new_size = 1 - {% for i in 0...T.size %} - %len{i} = strlen(args[{{ i }}]) - new_size &+= %len{i} - {% end %} - if new_size > size - size = new_size - buf = LibC.HeapReAlloc(LibC.GetProcessHeap, 0, buf, size &* 2).as(LibC::WCHAR*) - end - - ptr = buf - {% for i in 0...T.size %} - ptr = strcpy(ptr, args[{{ i }}]) - {% end %} - ptr.value = 0 - - {buf, size} - end - - # if *str* starts with *prefix*, returns the substring with *prefix* removed, - # otherwise returns *str* unmodified - private def self.str_lchop(str : LibC::WCHAR*, prefix : LibC::WCHAR*) : LibC::WCHAR* - src = str - - while prefix.value != 0 - return src unless prefix.value == str.value - prefix += 1 - str += 1 - end - - str - end - - # given *str*, a normalized absolute path of *size* UTF-16 code units, returns - # its parent directory by replacing the last directory separator with a null - # character - private def self.dirname(str : LibC::WCHAR*, size : Int32) - ptr = str + size - 1 - - # C:\foo.exe -> C: - # C:\foo\bar.exe -> C:\foo - # C:\foo\bar\baz.exe -> C:\foo\bar - while ptr != str - if ptr.value === '\\' - ptr.value = 0 - return {str, (ptr - str).to_i32!} - end - ptr -= 1 - end - - {str, size} - end - - # effective returns `::File.dirname(::Process.executable_path).to_utf16` - private def self.get_origin! : {LibC::WCHAR*, Int32} - buf = LibC.HeapAlloc(LibC.GetProcessHeap, 0, LibC::MAX_PATH &* 2).as(LibC::WCHAR*) - len = LibC.GetModuleFileNameW(nil, buf, LibC::MAX_PATH) - return dirname(buf, len.to_i32!) unless WinError.value.error_insufficient_buffer? - - buf = LibC.HeapReAlloc(LibC.GetProcessHeap, 0, buf, 65534).as(LibC::WCHAR*) - len = LibC.GetModuleFileNameW(nil, buf, 32767) - return dirname(buf, len.to_i32!) unless WinError.value.error_insufficient_buffer? - - print_error("FATAL: Failed to get current executable path\n") - LibC.ExitProcess(1) - end - - # converts *utf8_str* to a UTF-16 string - private def self.to_utf16!(utf8_str : LibC::Char*) : LibC::WCHAR* - utf16_size = LibC.MultiByteToWideChar(LibC::CP_UTF8, 0, utf8_str, -1, nil, 0) - utf16_str = LibC.HeapAlloc(LibC.GetProcessHeap, 0, utf16_size &* 2).as(LibC::WCHAR*) - LibC.MultiByteToWideChar(LibC::CP_UTF8, 0, utf8_str, -1, utf16_str, utf16_size) - utf16_str - end - - # replaces all instances of "$ORIGIN" in *str* with the directory containing - # the running executable - # if "$ORIGIN" is not found, returns *str* unmodified without allocating - # memory - private def self.expand_origin!(str : LibC::WCHAR*) : LibC::WCHAR* - origin_prefix = UInt16.static_array(0x24, 0x4F, 0x52, 0x49, 0x47, 0x49, 0x4E, 0x00) # "$ORIGIN".to_utf16 - ptr = str - origin = Pointer(LibC::WCHAR).null - origin_size = 0 - output_size = 1 - - while ptr.value != 0 - new_ptr = str_lchop(ptr, origin_prefix.to_unsafe) - if new_ptr != ptr - origin, origin_size = get_origin! unless origin - output_size &+= origin_size - ptr = new_ptr - next - end - output_size &+= 1 - ptr += 1 - end - - return str unless origin - output = LibC.HeapAlloc(LibC.GetProcessHeap, 0, output_size &* 2).as(LibC::WCHAR*) - dst = output - ptr = str - - while ptr.value != 0 - new_ptr = str_lchop(ptr, origin_prefix.to_unsafe) - if new_ptr != ptr - dst = strcpy(dst, origin) - ptr = new_ptr - next - end - dst.value = ptr.value - dst += 1 - ptr += 1 - end - dst.value = 0 - - LibC.HeapFree(LibC.GetProcessHeap, 0, origin) - output - end - - # `dll` is an ASCII base name without directory separators, e.g. `WS2_32.dll` - def self.load_library(dll : LibC::Char*) : LibC::HMODULE - utf16_dll = to_utf16!(dll) - - {% begin %} - {% paths = Crystal::LIBRARY_RPATH.gsub(/\$\{ORIGIN\}/, "$ORIGIN").split(::Process::PATH_DELIMITER).reject(&.empty?) %} - {% unless paths.empty? %} - size = 0x40 - buf = LibC.HeapAlloc(LibC.GetProcessHeap, 0, size &* 2).as(LibC::WCHAR*) - - {% for path, i in paths %} - # TODO: can this `to_utf16` be done at compilation time? - root = to_utf16!({{ path.ends_with?("\\") ? path : path + "\\" }}.to_unsafe) - root_expanded = expand_origin!(root) - buf, size = strcat(buf, size, root_expanded, utf16_dll) - handle = LibC.LoadLibraryExW(buf, nil, LibC::LOAD_WITH_ALTERED_SEARCH_PATH) - LibC.HeapFree(LibC.GetProcessHeap, 0, root_expanded) if root_expanded != root - LibC.HeapFree(LibC.GetProcessHeap, 0, root) - - if handle - LibC.HeapFree(LibC.GetProcessHeap, 0, buf) - LibC.HeapFree(LibC.GetProcessHeap, 0, utf16_dll) - return handle - end - {% end %} - - LibC.HeapFree(LibC.GetProcessHeap, 0, buf) - {% end %} - {% end %} - - handle = LibC.LoadLibraryExW(utf16_dll, nil, 0) - LibC.HeapFree(LibC.GetProcessHeap, 0, utf16_dll) - handle - end -end - -# This is a port of the default delay-load helper function in the `DelayHlp.cpp` -# file that comes with Microsoft Visual C++, except that all user-defined hooks -# are omitted. It is called every time the program attempts to load a symbol -# from a DLL. For more details see: -# https://learn.microsoft.com/en-us/cpp/build/reference/understanding-the-helper-function -# -# It is available even when the `preview_dll` flag is absent, so that system -# DLLs such as `advapi32.dll` and shards can be delay-loaded in the usual mixed -# static/dynamic builds by passing the appropriate linker flags explicitly. -# -# The delay load helper cannot call functions from the library being loaded, as -# that leads to an infinite recursion. In particular, if `preview_dll` is in -# effect, `Crystal::System.print_error` will not work, because the C runtime -# library DLLs are also delay-loaded and `LibC.snprintf` is unavailable. If you -# want print debugging inside this function, use the `print_error` macro -# instead. Note that its format string is passed to `LibC.FormatMessageA`, which -# uses different conventions from `LibC.printf`. -# -# `kernel32.dll` is the only DLL guaranteed to be available. It cannot be -# delay-loaded and the Crystal compiler excludes it from the linker arguments. -# -# This function does _not_ work with the empty prelude yet! -fun __delayLoadHelper2(pidd : LibC::ImgDelayDescr*, ppfnIATEntry : LibC::FARPROC*) : LibC::FARPROC - # TODO: support protected delay load? (/GUARD:CF) - # DloadAcquireSectionWriteAccess - - # Set up some data we use for the hook procs but also useful for our own use - idd = Crystal::System::DelayLoad::InternalImgDelayDescr.new( - grAttrs: pidd.value.grAttrs, - szName: p_from_rva(pidd.value.rvaDLLName).as(LibC::LPSTR), - phmod: p_from_rva(pidd.value.rvaHmod).as(LibC::HMODULE*), - pIAT: p_from_rva(pidd.value.rvaIAT).as(LibC::IMAGE_THUNK_DATA*), - pINT: p_from_rva(pidd.value.rvaINT).as(LibC::IMAGE_THUNK_DATA*), - pBoundIAT: p_from_rva(pidd.value.rvaBoundIAT).as(LibC::IMAGE_THUNK_DATA*), - pUnloadIAT: p_from_rva(pidd.value.rvaUnloadIAT).as(LibC::IMAGE_THUNK_DATA*), - dwTimeStamp: pidd.value.dwTimeStamp, - ) - - dli = LibC::DelayLoadInfo.new( - cb: sizeof(LibC::DelayLoadInfo), - pidd: pidd, - ppfn: ppfnIATEntry, - szDll: idd.szName, - dlp: LibC::DelayLoadProc.new, - hmodCur: LibC::HMODULE.null, - pfnCur: LibC::FARPROC.null, - dwLastError: LibC::DWORD.zero, - ) - - if 0 == idd.grAttrs & LibC::DLAttrRva - # DloadReleaseSectionWriteAccess - print_error("FATAL: Delay load descriptor does not support RVAs\n") - LibC.ExitProcess(1) - end - - hmod = idd.phmod.value - - # Calculate the index for the IAT entry in the import address table - # N.B. The INT entries are ordered the same as the IAT entries so - # the calculation can be done on the IAT side. - iIAT = ppfnIATEntry.as(LibC::IMAGE_THUNK_DATA*) - idd.pIAT - iINT = iIAT - - pitd = idd.pINT + iINT - - import_by_name = (pitd.value.u1.ordinal & LibC::IMAGE_ORDINAL_FLAG) == 0 - dli.dlp.fImportByName = import_by_name ? 1 : 0 - - if import_by_name - image_import_by_name = p_from_rva(LibC::RVA.new!(pitd.value.u1.addressOfData)) - dli.dlp.union.szProcName = image_import_by_name + offsetof(LibC::IMAGE_IMPORT_BY_NAME, @name) - else - dli.dlp.union.dwOrdinal = LibC::DWORD.new!(pitd.value.u1.ordinal & 0xFFFF) - end - - # Check to see if we need to try to load the library. - if !hmod - unless hmod = Crystal::System::DelayLoad.load_library(dli.szDll) - # DloadReleaseSectionWriteAccess - print_error("FATAL: Cannot find the DLL named `%1`, exiting\n", dli.szDll) - LibC.ExitProcess(1) - end - - # Store the library handle. If it is already there, we infer - # that another thread got there first, and we need to do a - # FreeLibrary() to reduce the refcount - hmodT = Crystal::System::DelayLoad.interlocked_exchange(idd.phmod, hmod) - LibC.FreeLibrary(hmod) if hmodT == hmod - end - - # Go for the procedure now. - dli.hmodCur = hmod - if pidd.value.rvaBoundIAT != 0 && pidd.value.dwTimeStamp != 0 - # bound imports exist...check the timestamp from the target image - pinh = Crystal::System::DelayLoad.pinh_from_image_base(hmod) - - if pinh.value.signature == LibC::IMAGE_NT_SIGNATURE && - pinh.value.fileHeader.timeDateStamp == idd.dwTimeStamp && - hmod.address == pinh.value.optionalHeader.imageBase - # Everything is good to go, if we have a decent address - # in the bound IAT! - if pfnRet = LibC::FARPROC.new(idd.pBoundIAT[iIAT].u1.function) - ppfnIATEntry.value = pfnRet - # DloadReleaseSectionWriteAccess - return pfnRet - end - end - end - - unless pfnRet = LibC.GetProcAddress(hmod, dli.dlp.union.szProcName) - # DloadReleaseSectionWriteAccess - if import_by_name - print_error("FATAL: Cannot find the symbol named `%1` within `%2`, exiting\n", dli.dlp.union.szProcName, dli.szDll) - else - print_error("FATAL: Cannot find the symbol with the ordinal #%1!u! within `%2`, exiting\n", Pointer(Void).new(dli.dlp.union.dwOrdinal), dli.szDll) - end - LibC.ExitProcess(1) - end - - ppfnIATEntry.value = pfnRet - # DloadReleaseSectionWriteAccess - pfnRet -end diff --git a/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr b/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr index 971e96fa9eb5..f60e80a59328 100644 --- a/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr +++ b/src/lib_c/x86_64-windows-msvc/c/stringapiset.cr @@ -11,7 +11,7 @@ lib LibC lpDefaultChar : CHAR*, lpUsedDefaultChar : BOOL* ) : Int - # this is only for the delay-load helper, all other code should use + # this was for the now removed delay-load helper, all other code should use # `String#to_utf16` instead fun MultiByteToWideChar( codePage : UInt, dwFlags : DWORD, lpMultiByteStr : LPSTR,