Skip to content

Commit d773471

Browse files
committed
Solve delay load import issues on windows.
1 parent 7f1f800 commit d773471

File tree

6 files changed

+387
-114
lines changed

6 files changed

+387
-114
lines changed

source/dynlink/include/dynlink/dynlink_type.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,20 @@ typedef void (*dynlink_symbol_addr)(void); /**< Function pointer referring to
7272
\
7373
} while (0)
7474

75+
#define dynlink_symbol_uncast_type(fn, type, result) \
76+
do \
77+
{ \
78+
union \
79+
{ \
80+
type ptr; \
81+
dynlink_symbol_addr fn; \
82+
} cast; \
83+
\
84+
cast.fn = (fn); \
85+
(result) = (type)cast.ptr; \
86+
\
87+
} while (0)
88+
7589
#ifdef __cplusplus
7690
}
7791
#endif

source/loaders/py_loader/CMakeLists.txt

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,46 +9,73 @@ endif()
99

1010
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
1111
set(Python3_FIND_ABI "ON" "ANY" "ANY")
12-
find_package(Python3 COMPONENTS Development)
12+
find_package(Python3 COMPONENTS Interpreter Development)
1313

1414
# Fallback to release if not found
1515
if(NOT Python3_Development_FOUND)
1616
set(Python3_FIND_ABI)
17-
find_package(Python3 COMPONENTS Development REQUIRED)
17+
find_package(Python3 COMPONENTS Interpreter Development REQUIRED)
1818
endif()
1919
else()
20-
find_package(Python3 COMPONENTS Development REQUIRED)
20+
find_package(Python3 COMPONENTS Interpreter Development REQUIRED)
21+
endif()
22+
23+
# Select the proper library
24+
if(NOT Python3_LIBRARY AND Python3_LIBRARIES)
25+
# Go through the list and handle keyword-separated structure
26+
set(index 0)
27+
list(LENGTH Python3_LIBRARIES lib_len)
28+
while(index LESS lib_len)
29+
list(GET Python3_LIBRARIES ${index} item)
30+
31+
# Check if it's a keyword (debug/optimized)
32+
if(item STREQUAL "debug" OR item STREQUAL "optimized" OR item STREQUAL "general")
33+
set(keyword ${item})
34+
math(EXPR next "${index} + 1")
35+
list(GET Python3_LIBRARIES ${next} lib_path)
36+
37+
# Match the right keyword
38+
if((CMAKE_BUILD_TYPE STREQUAL "Debug" AND keyword STREQUAL "debug") OR
39+
(NOT CMAKE_BUILD_TYPE STREQUAL "Debug" AND keyword STREQUAL "optimized") OR
40+
(keyword STREQUAL "general")) # general applies to all configs
41+
set(Python3_LIBRARY ${lib_path})
42+
endif()
43+
44+
math(EXPR index "${index} + 2") # Skip keyword and path
45+
else()
46+
# Plain list without keywords (single-config or fallback)
47+
set(Python3_LIBRARY ${item})
48+
math(EXPR index "${index} + 1")
49+
endif()
50+
endwhile()
2151
endif()
2252

2353
# Copy Python DLL into project output directory
2454
# TODO: https://cmake.org/cmake/help/latest/command/file.html#get-runtime-dependencies
2555
# TODO: https://gist.github.com/micahsnyder/5d98ac8548b429309ec5a35bca9366da
2656
include(Portability)
2757

28-
if(PROJECT_OS_FAMILY STREQUAL win32 AND Python3_LIBRARIES AND Python3_ROOT_DIR AND NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
29-
foreach(library ${Python3_LIBRARIES})
30-
if(${library} MATCHES "[^_d][.]lib$")
31-
# Get the library path with dll suffix
32-
string(REGEX REPLACE "[.]lib$" ".dll" LIB_PATH ${library})
33-
# Get the library name
34-
get_filename_component(LIB_NAME "${LIB_PATH}" NAME)
35-
# Find the library in the Python3 root path
36-
find_file(Python3_LIBRARY_NAME_PATH ${LIB_NAME}
37-
PATHS ${Python3_ROOT_DIR}
38-
NO_DEFAULT_PATH
39-
)
40-
if(Python3_LIBRARY_NAME_PATH)
41-
break()
42-
endif()
43-
endif()
44-
endforeach()
58+
if(PROJECT_OS_FAMILY STREQUAL win32 AND Python3_LIBRARY)
59+
# Get the library path with dll suffix
60+
string(REGEX REPLACE "[.]lib$" ".dll" LIB_PATH "${Python3_LIBRARY}")
61+
# Get the library name
62+
get_filename_component(LIB_NAME "${LIB_PATH}" NAME)
63+
# Get the python root folder
64+
if(NOT Python3_ROOT_DIR)
65+
get_filename_component(Python3_ROOT_DIR "${Python3_EXECUTABLE}" DIRECTORY)
66+
endif()
67+
# Find the library in the Python3 root path
68+
find_file(Python3_LIBRARY_NAME_PATH "${LIB_NAME}"
69+
PATHS "${Python3_ROOT_DIR}"
70+
NO_DEFAULT_PATH
71+
)
72+
# Copy the DLL to the output directory
73+
execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory "${PROJECT_OUTPUT_DIR}")
74+
file(COPY "${Python3_LIBRARY_NAME_PATH}" DESTINATION "${PROJECT_OUTPUT_DIR}")
4575
endif()
4676

47-
if(Python3_LIBRARY_NAME_PATH)
48-
execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_OUTPUT_DIR})
49-
file(COPY "${Python3_LIBRARY_NAME_PATH}" DESTINATION ${PROJECT_OUTPUT_DIR})
50-
else()
51-
set(Python3_LIBRARY_NAME_PATH "${Python3_LIBRARIES}")
77+
if(NOT Python3_LIBRARY_NAME_PATH)
78+
set(Python3_LIBRARY_NAME_PATH "${Python3_LIBRARY}")
5279
endif()
5380

5481
#
@@ -91,6 +118,7 @@ set(headers
91118
${include_path}/py_loader_port.h
92119
${include_path}/py_loader_threading.h
93120
${include_path}/py_loader_dict.h
121+
${include_path}/py_loader_symbol_fallback.h
94122
)
95123

96124
set(sources
@@ -174,7 +202,7 @@ target_link_libraries(${target}
174202
${META_PROJECT_NAME}::metacall # MetaCall library
175203

176204
# Delay load for MSVC
177-
$<$<CXX_COMPILER_ID:MSVC>:${Python3_LIBRARIES}> # Python library
205+
$<$<CXX_COMPILER_ID:MSVC>:${Python3_LIBRARY}> # Python library
178206
$<$<CXX_COMPILER_ID:MSVC>:delayimp>
179207

180208
PUBLIC
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Loader Library by Parra Studios
3+
* A plugin for loading python code at run-time into a process.
4+
*
5+
* Copyright (C) 2016 - 2025 Vicente Eduardo Ferrer Garcia <[email protected]>
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*
19+
*/
20+
21+
#ifndef PY_LOADER_SYMBOL_FALLBACK_H
22+
#define PY_LOADER_SYMBOL_FALLBACK_H 1
23+
24+
#include <py_loader/py_loader_api.h>
25+
26+
#include <dynlink/dynlink.h>
27+
28+
#include <Python.h>
29+
30+
#ifdef __cplusplus
31+
extern "C" {
32+
#endif
33+
34+
PY_LOADER_NO_EXPORT int py_loader_symbol_fallback_initialize(dynlink py_library);
35+
36+
#if defined(_WIN32) && defined(_MSC_VER)
37+
#undef PyBool_Check
38+
PY_LOADER_NO_EXPORT int PyBool_Check(const PyObject *ob);
39+
#undef PyFloat_Check
40+
PY_LOADER_NO_EXPORT int PyFloat_Check(const PyObject *ob);
41+
#undef PyCapsule_CheckExact
42+
PY_LOADER_NO_EXPORT int PyCapsule_CheckExact(const PyObject *ob);
43+
#undef PyFunction_Check
44+
PY_LOADER_NO_EXPORT int PyFunction_Check(const PyObject *ob);
45+
#undef PyCFunction_Check
46+
PY_LOADER_NO_EXPORT int PyCFunction_Check(const PyObject *ob);
47+
#undef PyModule_Check
48+
PY_LOADER_NO_EXPORT int PyModule_Check(const PyObject *ob);
49+
#endif
50+
51+
PY_LOADER_NO_EXPORT PyObject *Py_NonePtr(void);
52+
PY_LOADER_NO_EXPORT PyObject *Py_ReturnNone(void);
53+
PY_LOADER_NO_EXPORT PyObject *Py_ReturnFalse(void);
54+
PY_LOADER_NO_EXPORT PyObject *Py_ReturnTrue(void);
55+
56+
#ifdef __cplusplus
57+
}
58+
#endif
59+
60+
#endif /* PY_LOADER_SYMBOL_FALLBACK_H */

source/loaders/py_loader/source/py_loader_impl.c

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <py_loader/py_loader_dict.h>
2222
#include <py_loader/py_loader_impl.h>
2323
#include <py_loader/py_loader_port.h>
24+
#include <py_loader/py_loader_symbol_fallback.h>
2425
#include <py_loader/py_loader_threading.h>
2526

2627
#include <loader/loader.h>
@@ -280,7 +281,7 @@ PyObject *py_loader_impl_capsule_new_null(void)
280281
/* We want to create a new capsule with contents set to NULL, but PyCapsule
281282
* does not allow that, instead we are going to identify our NULL capsule with
282283
* this configuration (setting the capsule to Py_None) */
283-
return PyCapsule_New(Py_None, py_loader_capsule_null_id, NULL);
284+
return PyCapsule_New((void *)Py_NonePtr(), py_loader_capsule_null_id, NULL);
284285
}
285286

286287
void py_loader_impl_value_invoke_state_finalize(value v, void *data)
@@ -918,7 +919,7 @@ type_id py_loader_impl_capi_to_value_type(loader_impl impl, PyObject *obj)
918919
{
919920
return TYPE_FUNCTION;
920921
}
921-
else if (obj == Py_None)
922+
else if (obj == Py_NonePtr())
922923
{
923924
return TYPE_NULL;
924925
}
@@ -1146,7 +1147,7 @@ value py_loader_impl_capi_to_value(loader_impl impl, PyObject *obj, type_id id)
11461147
const char *name = PyCapsule_GetName(obj);
11471148
void *ptr = PyCapsule_GetPointer(obj, name);
11481149

1149-
if (ptr == Py_None && name == py_loader_capsule_null_id)
1150+
if (ptr == Py_NonePtr() && name == py_loader_capsule_null_id)
11501151
{
11511152
v = value_create_ptr(NULL);
11521153
}
@@ -1293,7 +1294,7 @@ value py_loader_impl_capi_to_value(loader_impl impl, PyObject *obj, type_id id)
12931294
{
12941295
PyObject *tb = PyException_GetTraceback(obj);
12951296

1296-
v = py_loader_impl_error_value_from_exception(loader_impl_get(impl), (PyObject *)Py_TYPE(obj), obj, tb ? tb : Py_None);
1297+
v = py_loader_impl_error_value_from_exception(loader_impl_get(impl), (PyObject *)Py_TYPE(obj), obj, tb ? tb : Py_NonePtr());
12971298

12981299
Py_XDECREF(tb);
12991300
}
@@ -2670,6 +2671,12 @@ loader_impl_data py_loader_impl_initialize(loader_impl impl, configuration confi
26702671
#endif
26712672
}
26722673

2674+
/* Initialize symbol fallback */
2675+
if (py_loader_symbol_fallback_initialize(loader_impl_dependency(impl, "python")) != 0)
2676+
{
2677+
goto error_init_py;
2678+
}
2679+
26732680
/* Initialize threading */
26742681
py_loader_thread_initialize(host);
26752682

0 commit comments

Comments
 (0)