Skip to content
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
31 changes: 23 additions & 8 deletions src/compiler/crystal/compiler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -366,14 +366,29 @@ module Crystal
@link_flags.try { |flags| link_args << flags }

{% if flag?(:msvc) %}
if program.has_flag?("preview_dll") && !program.has_flag?("no_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"

Loader.search_dlls(Process.parse_arguments_windows(link_args.join(' '))).each do |dll|
link_args << "/DELAYLOAD:#{dll}"
unless @cross_compile
extra_suffix = program.has_flag?("preview_dll") ? "-dynamic" : "-static"
search_result = Loader.search_libraries(Process.parse_arguments_windows(link_args.join(' ').gsub('\n', ' ')), extra_suffix: extra_suffix)
if not_found = search_result.not_found?
error "Cannot locate the .lib files for the following libraries: #{not_found.join(", ")}"
end

link_args = search_result.remaining_args.concat(search_result.library_paths).map { |arg| Process.quote_windows(arg) }

if !program.has_flag?("no_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 %}
Expand Down
57 changes: 43 additions & 14 deletions src/compiler/crystal/loader/msvc.cr
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,53 @@ class Crystal::Loader
end
end

# Returns the list of DLLs imported from the libraries specified in the given
# linker arguments. Used by the compiler for delay-loaded DLL support.
def self.search_dlls(args : Array(String), *, search_paths : Array(String) = default_search_paths) : Set(String)
search_paths, libnames = parse_args(args, search_paths)
dlls = Set(String).new
struct SearchLibResult
getter library_paths = [] of String
getter remaining_args = [] of String
getter(not_found) { [] of String }

def not_found?
@not_found
end
end

# Extracts the command-line arguments from *args* that add libraries and
# expands them to their absolute paths. Returns a `SearchLibResult` with those
# expanded paths, plus unused arguments and libraries that were not found.
def self.search_libraries(args : Array(String), *, search_paths : Array(String) = default_search_paths, extra_suffix : String? = nil) : SearchLibResult
result = SearchLibResult.new
search_paths, libnames = parse_args(args, search_paths, remaining: result.remaining_args)

libnames.each do |libname|
search_paths.each do |directory|
library_path = File.join(directory, library_filename(libname))
next unless File.file?(library_path)
if found_path = search_library(libname, search_paths, extra_suffix)
result.library_paths << found_path
else
result.not_found << libname
end
end

result
end

Crystal::System::LibraryArchive.imported_dlls(library_path).each do |dll|
dlls << dll unless dll.compare("kernel32.dll", case_insensitive: true).zero?
private def self.search_library(libname, search_paths, extra_suffix)
if ::Path::SEPARATORS.any? { |separator| libname.includes?(separator) }
libname = File.expand_path(libname)
library_path = library_filename(libname)
return library_path if File.file?(library_path)
else
search_paths.each do |directory|
if extra_suffix
library_path = File.join(directory, library_filename(libname + extra_suffix))
return library_path if File.file?(library_path)
end
break

library_path = File.join(directory, library_filename(libname))
return library_path if File.file?(library_path)
end
end

dlls
end

private def self.parse_args(args, search_paths)
def self.parse_args(args, search_paths, *, remaining = nil)
libnames = [] of String

# NOTE: `/LIBPATH`s are prepended before the default paths:
Expand All @@ -68,10 +93,14 @@ class Crystal::Loader
extra_search_paths << lib_path
elsif !arg.starts_with?('/') && (name = arg.rchop?(".lib"))
libnames << name
elsif remaining
remaining << arg
end
end

search_paths = extra_search_paths + search_paths
search_paths.uniq! &.downcase
libnames.uniq! &.downcase
{search_paths, libnames}
end

Expand Down
9 changes: 5 additions & 4 deletions src/crystal/system/win32/delay_load.cr
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,12 @@ fun __delayLoadHelper2(pidd : LibC::ImgDelayDescr*, ppfnIATEntry : LibC::FARPROC

pitd = idd.pINT + iINT

dli.dlp.fImportByName = pitd.value.u1.ordinal & LibC::IMAGE_ORDINAL_FLAG == 0
import_by_name = (pitd.value.u1.ordinal & LibC::IMAGE_ORDINAL_FLAG) == 0
dli.dlp.fImportByName = import_by_name ? 1 : 0

if dli.dlp.fImportByName
import_by_name = p_from_rva(LibC::RVA.new!(pitd.value.u1.addressOfData))
dli.dlp.union.szProcName = import_by_name + offsetof(LibC::IMAGE_IMPORT_BY_NAME, @name)
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
Expand Down
7 changes: 5 additions & 2 deletions src/crystal/system/win32/library_archive.cr
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,11 @@ module Crystal::System::LibraryArchive
sig2 = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian)
return unless sig2 == 0xFFFF

# version(2) + machine(2) + time(4) + size(4) + ordinal/hint(2) + flags(2)
io.skip(16)
version = io.read_bytes(UInt16, IO::ByteFormat::LittleEndian)
return unless version == 0 # 1 and 2 are used by object files (ANON_OBJECT_HEADER)

# machine(2) + time(4) + size(4) + ordinal/hint(2) + flags(2)
io.skip(14)

# TODO: is there a way to do this without constructing a temporary string,
# but with the optimizations present in `IO#gets`?
Expand Down