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"
27 # include "lib/sysdep/os/osx/osx_bundle.h"
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
);
39 static CStr g_Libdir
= "";
42 // note: on Linux, lib is prepended to the SO file name
44 static CStr g_DllPrefix
= "lib";
46 static CStr g_DllPrefix
= "";
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.
55 static CStr suffixes
[] = { "_dbg", "" }; // (order matters)
57 static CStr suffixes
[] = { "", "_dbg" };
60 // NB: our Windows dlopen() function changes the extension to .dll
61 static CStr extensions
[] = {
64 ".dylib" // supported by OS X dlopen
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
)
76 if (!g_Libdir
.empty())
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
+ "/";
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/";
102 n
+= g_DllPrefix
+ name
+ suffix
+ extension
;
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
);
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()
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.
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()
180 void DllLoader::LoadSymbolInternal(const char* name
, void** fptr
) const
184 debug_warn(L
"Loading symbol from invalid DLL");
186 throw PSERROR_DllLoader_DllNotLoaded();
189 *fptr
= dlsym(m_Handle
, name
);
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
);
201 case CLogger::Warning
:
202 LOGWARNING("DllLoader: %s", errors
);
205 LOGERROR("DllLoader: %s", errors
);
210 void DllLoader::OverrideLibdir(const char* libdir
)