Civ backgrounds for minimap
[0ad.git] / source / ps / DllLoader.cpp
blobc0261573770eb058da35b54cef5efd06cdc00450
1 /* Copyright (C) 2021 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
18 #include "precompiled.h"
20 #include "DllLoader.h"
22 #include "lib/timer.h"
23 #include "lib/posix/posix_dlfcn.h"
24 #include "ps/CStr.h"
26 #if OS_MACOSX
27 # include "lib/sysdep/os/osx/osx_bundle.h"
28 #endif
30 static void* const HANDLE_UNAVAILABLE = (void*)-1;
32 // directory to search for libraries (optionally set by --libdir at build-time,
33 // optionally overridden by -libdir at run-time in the test executable);
34 // if we don't have an explicit libdir then the linker will look in DT_RUNPATH
35 // (which we set to $ORIGIN) to find it in the executable's directory
36 #ifdef INSTALLED_LIBDIR
37 static CStr g_Libdir = STRINGIZE(INSTALLED_LIBDIR);
38 #else
39 static CStr g_Libdir = "";
40 #endif
42 // note: on Linux, lib is prepended to the SO file name
43 #if OS_UNIX
44 static CStr g_DllPrefix = "lib";
45 #else
46 static CStr g_DllPrefix = "";
47 #endif
49 // we usually want to use the same debug/release build type as the
50 // main executable (the library should also be efficient in release builds and
51 // allow easy symbol access in debug builds). however, that version of the
52 // library might be missing, so we check for both.
53 // this works because the interface is binary-compatible.
54 #ifndef NDEBUG
55 static CStr suffixes[] = { "_dbg", "" }; // (order matters)
56 #else
57 static CStr suffixes[] = { "", "_dbg" };
58 #endif
60 // NB: our Windows dlopen() function changes the extension to .dll
61 static CStr extensions[] = {
62 ".so",
63 #if OS_MACOSX
64 ".dylib" // supported by OS X dlopen
65 #endif
68 // (This class is currently only used by 'Collada' and 'AtlasUI' which follow
69 // the naming/location convention above - it'll need to be changed if we want
70 // to support other DLLs.)
72 static CStr GenerateFilename(const CStr& name, const CStr& suffix, const CStr& extension)
74 CStr n;
76 if (!g_Libdir.empty())
77 n = g_Libdir + "/";
79 #if OS_MACOSX
80 if (osx_IsAppBundleValid())
82 // We are in a bundle, in which case the lib directory is ../Frameworks
83 // relative to the binary, so we use a helper function to get the system path
84 // (alternately we could use @executable_path as below, but this seems better)
85 CStr frameworksPath = osx_GetBundleFrameworksPath();
86 if (!frameworksPath.empty())
87 n = frameworksPath + "/";
89 else
91 // On OS X, dlopen will search the current working directory for the library to
92 // to load if given only a filename. But the CWD is not guaranteed to be
93 // binaries/system (where our dylibs are placed) and it's not when e.g. the user
94 // launches the game from Finder.
95 // To work around this, we use the @executable_path variable, which should always
96 // resolve to binaries/system, if we're not a bundle.
97 // (see Apple's dyld(1) and dlopen(3) man pages for more info)
98 n = "@executable_path/";
100 #endif
102 n += g_DllPrefix + name + suffix + extension;
103 return n;
108 // @param name base name of the library (excluding prefix/suffix/extension)
109 // @param errors receives descriptions of any and all errors encountered
110 // @return valid handle or 0
111 static void* LoadAnyVariant(const CStr& name, std::stringstream& errors)
113 for (size_t idxSuffix = 0; idxSuffix < ARRAY_SIZE(suffixes); idxSuffix++)
115 for (size_t idxExtension = 0; idxExtension < ARRAY_SIZE(extensions); idxExtension++)
117 CStr filename = GenerateFilename(name, suffixes[idxSuffix], extensions[idxExtension]);
119 // we don't really care when relocations take place, but one of
120 // {RTLD_NOW, RTLD_LAZY} must be specified. go with the former because
121 // it is safer and matches the Windows load behavior.
122 const int flags = RTLD_LOCAL|RTLD_NOW;
123 void* handle = dlopen(filename.c_str(), flags);
124 if (handle)
125 return handle;
126 else
127 errors << "dlopen(" << filename << ") failed: " << dlerror() << "; ";
131 return 0; // none worked
135 DllLoader::DllLoader(const char* name, CLogger::ELogMethod loadErrorLogMethod)
136 : m_Name(name), m_Handle(0), m_LoadErrorLogMethod(loadErrorLogMethod)
140 DllLoader::~DllLoader()
142 if (IsLoaded())
143 dlclose(m_Handle);
146 bool DllLoader::IsLoaded() const
148 return (m_Handle != 0 && m_Handle != HANDLE_UNAVAILABLE);
151 bool DllLoader::LoadDLL()
153 // first time: try to open the shared object
154 // postcondition: m_Handle valid or == HANDLE_UNAVAILABLE.
155 if (m_Handle == 0)
157 TIMER(L"LoadDLL");
159 std::stringstream errors;
160 m_Handle = LoadAnyVariant(m_Name, errors);
161 if (!m_Handle) // (only report errors if nothing worked)
163 LogLoadError(errors.str().c_str());
164 m_Handle = HANDLE_UNAVAILABLE;
168 return (m_Handle != HANDLE_UNAVAILABLE);
171 void DllLoader::Unload()
173 if (!IsLoaded())
174 return;
176 dlclose(m_Handle);
177 m_Handle = 0;
180 void DllLoader::LoadSymbolInternal(const char* name, void** fptr) const
182 if (!IsLoaded())
184 debug_warn(L"Loading symbol from invalid DLL");
185 *fptr = NULL;
186 throw PSERROR_DllLoader_DllNotLoaded();
189 *fptr = dlsym(m_Handle, name);
190 if (*fptr == NULL)
191 throw PSERROR_DllLoader_SymbolNotFound();
194 void DllLoader::LogLoadError(const char* errors)
196 switch (m_LoadErrorLogMethod)
198 case CLogger::Normal:
199 LOGMESSAGE("DllLoader: %s", errors);
200 break;
201 case CLogger::Warning:
202 LOGWARNING("DllLoader: %s", errors);
203 break;
204 case CLogger::Error:
205 LOGERROR("DllLoader: %s", errors);
206 break;
210 void DllLoader::OverrideLibdir(const char* libdir)
212 g_Libdir = libdir;