Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
9 changes: 5 additions & 4 deletions analytics/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,10 @@ set(android_SRCS
set(ios_SRCS
src/analytics_ios.mm)

# Source files used by the stub implementation.
set(stub_SRCS
src/analytics_stub.cc)
# Source files used by the desktop / stub implementation.
set(desktop_SRCS
src/analytics_desktop.cc
src/analytics_desktop_dynamic.c)

if(ANDROID)
set(analytics_platform_SRCS
Expand All @@ -86,7 +87,7 @@ elseif(IOS)
"${ios_SRCS}")
else()
set(analytics_platform_SRCS
"${stub_SRCS}")
"${desktop_SRCS}")
endif()

add_library(firebase_analytics STATIC
Expand Down
76 changes: 52 additions & 24 deletions analytics/generate_windows_stubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@
"""Generate stubs and function pointers for Windows SDK"""

import argparse
import hashlib
import os
import re
import sys

HEADER_GUARD_PREFIX = "FIREBASE_ANALYTICS_SRC_WINDOWS_"
INCLUDE_PATH = "src/windows/"
INCLUDE_PATH = "src/"
INCLUDE_PREFIX = "analytics/" + INCLUDE_PATH
COPYRIGHT_NOTICE = """// Copyright 2025 Google LLC
//
Expand All @@ -39,17 +40,29 @@

"""

def generate_function_pointers(header_file_path, output_h_path, output_c_path):

def hash_file(filename):
sha256_hash = hashlib.sha256()
with open(filename, "rb") as file:
while chunk := file.read(4096):
sha256_hash.update(chunk)
return sha256_hash.digest()

def generate_function_pointers(dll_file_path, header_file_path, output_h_path, output_c_path):
"""
Parses a C header file to generate a self-contained header with typedefs,
extern function pointer declarations, and a source file with stub functions,
initialized pointers, and a dynamic loading function for Windows.

Args:
header_file_path (str): The path to the DLL file.
header_file_path (str): The path to the input C header file.
output_h_path (str): The path for the generated C header output file.
output_c_path (str): The path for the generated C source output file.
"""
print(f"Reading DLL file: {dll_file_path}")
dll_hash = hash_file(dll_file_path)

print(f"Reading header file: {header_file_path}")
try:
with open(header_file_path, 'r', encoding='utf-8') as f:
Expand Down Expand Up @@ -83,7 +96,7 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
return_type = match.group(1).strip()
function_name = match.group(2).strip()
params_str = match.group(3).strip()

cleaned_params_for_decl = re.sub(r'\s+', ' ', params_str) if params_str else ""
stub_name = f"Stub_{function_name}"

Expand All @@ -94,7 +107,7 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
return_statement = f' return ({return_type})(&g_stub_memory);'
else: # bool, int64_t, etc.
return_statement = " return 1;"

stub_function = (
f"// Stub for {function_name}\n"
f"static {return_type} {stub_name}({params_str}) {{\n"
Expand All @@ -111,7 +124,7 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):

pointer_init = f"{return_type} (*ptr_{function_name})({cleaned_params_for_decl}) = &{stub_name};"
pointer_initializations.append(pointer_init)

function_details_for_loader.append((function_name, return_type, cleaned_params_for_decl))

print(f"Found {len(pointer_initializations)} functions. Generating output files...")
Expand All @@ -124,12 +137,12 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
f.write(f"#ifndef {header_guard}\n")
f.write(f"#define {header_guard}\n\n")
f.write("#include <stdbool.h> // needed for bool type in pure C\n\n")

f.write("// --- Copied from original header ---\n")
f.write("\n".join(includes) + "\n\n")
f.write("".join(typedefs))
f.write("// --- End of copied section ---\n\n")

f.write("#ifdef __cplusplus\n")
f.write('extern "C" {\n')
f.write("#endif\n\n")
Expand All @@ -139,15 +152,20 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
f.write("\n\n")
f.write("\n".join(macro_definitions))
f.write("\n// clang-format on\n")
f.write("\n\n// --- Dynamic Loader Declaration for Windows ---\n")
f.write("\n// --- Dynamic Loader Declaration for Windows ---\n")
f.write("#if defined(_WIN32)\n")
f.write('#include <windows.h> // For HMODULE\n')
f.write('// Load Google Analytics functions from the given DLL handle into function pointers.\n')
f.write(f'// Returns the number of functions successfully loaded (out of {len(function_details_for_loader)}).\n')
f.write("int FirebaseAnalytics_LoadAnalyticsFunctions(HMODULE dll_handle);\n\n")
f.write('#include <windows.h>\n')
f.write(f'\n// Google Analytics Windows DLL SHA256 hash, to be verified on load.')
f.write(f'\nextern const unsigned char FirebaseAnalytics_WindowsDllHash[{len(dll_hash)}];\n');

f.write(f'\n// Number of Google Analytics functions expected to be loaded from the DLL.')
f.write(f'\n#define FIREBASE_ANALYTICS_DYNAMIC_FUNCTION_COUNT {len(function_details_for_loader)}\n\n')
f.write('// Load Analytics functions from the given DLL handle into function pointers.\n')
f.write(f'// Returns the number of functions successfully loaded (out of\nFIREBASE_ANALYTICS_DYNAMIC_FUNCTION_COUNT).\n')
f.write("int FirebaseAnalytics_LoadDynamicFunctions(HMODULE dll_handle);\n\n")
f.write('// Reset all function pointers back to stubs.\n')
f.write("void FirebaseAnalytics_UnloadAnalyticsFunctions(void);\n\n")
f.write("#endif // defined(_WIN32)\n")
f.write("void FirebaseAnalytics_UnloadDynamicFunctions(void);\n\n")
f.write("#endif // defined(_WIN32)\n")
f.write("\n#ifdef __cplusplus\n")
f.write("}\n")
f.write("#endif\n\n")
Expand All @@ -159,18 +177,22 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
with open(output_c_path, 'w', encoding='utf-8') as f:
f.write(f"{COPYRIGHT_NOTICE}")
f.write(f"// Generated from {os.path.basename(header_file_path)} by {os.path.basename(sys.argv[0])}\n\n")
f.write(f'#include "{INCLUDE_PREFIX}{os.path.basename(output_h_path)}"\n')
f.write(f'#include "{INCLUDE_PREFIX}{os.path.basename(output_h_path)}"\n\n')
f.write('#include <stddef.h>\n\n')
f.write("// clang-format off\n\n")
f.write("static void* g_stub_memory = NULL;\n\n")
f.write("// clang-format off\n\n")
f.write('// Google Analytics Windows DLL SHA256 hash, to be verified on load.\n')
f.write('const unsigned char FirebaseAnalytics_WindowsDllHash[] = {\n ')
f.write(', '.join(["0x%02x" % s for s in dll_hash]))
f.write('\n};\n\n')
f.write("// --- Stub Function Definitions ---\n")
f.write("\n\n".join(stub_functions))
f.write("\n\n\n// --- Function Pointer Initializations ---\n")
f.write("\n".join(pointer_initializations))
f.write("\n\n// --- Dynamic Loader Function for Windows ---\n")
loader_lines = [
'#if defined(_WIN32)',
'int FirebaseAnalytics_LoadAnalyticsFunctions(HMODULE dll_handle) {',
'int FirebaseAnalytics_LoadDynamicFunctions(HMODULE dll_handle) {',
' int count = 0;\n',
' if (!dll_handle) {',
' return count;',
Expand All @@ -188,7 +210,7 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
loader_lines.extend(proc_check)
loader_lines.append('\n return count;')
loader_lines.append('}\n')
loader_lines.append('void FirebaseAnalytics_UnloadAnalyticsFunctions(void) {')
loader_lines.append('void FirebaseAnalytics_UnloadDynamicFunctions(void) {')
for name, ret_type, params in function_details_for_loader:
loader_lines.append(f' ptr_{name} = &Stub_{name};');
loader_lines.append('}\n')
Expand All @@ -203,6 +225,12 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
parser = argparse.ArgumentParser(
description="Generate C stubs and function pointers from a header file."
)
parser.add_argument(
"--windows_dll",
default = os.path.join(os.path.dirname(sys.argv[0]), "windows/analytics_win.dll"),
#required=True,
help="Path to the DLL file to calculate a hash."
)
parser.add_argument(
"--windows_header",
default = os.path.join(os.path.dirname(sys.argv[0]), "windows/include/public/c/analytics.h"),
Expand All @@ -211,21 +239,21 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
)
parser.add_argument(
"--output_header",
default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_dynamic.h"),
default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_desktop_dynamic.h"),
#required=True,
help="Path for the generated output header file."
)
parser.add_argument(
"--output_source",
default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_dynamic.c"),
default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_desktop_dynamic.c"),
#required=True,
help="Path for the generated output source file."
)

args = parser.parse_args()

generate_function_pointers(
args.windows_header,
args.output_header,
args.windows_dll,
args.windows_header,
args.output_header,
args.output_source
)
12 changes: 12 additions & 0 deletions analytics/integration_test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,18 @@ else()
)
elseif(MSVC)
set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32)
set(ANALYTICS_WINDOWS_DLL "${FIREBASE_CPP_SDK_DIR}/analytics/windows/analytics_win.dll")

# For Windows, check if the Analytics DLL exists, and copy it in if so.
if (EXISTS "${ANALYTICS_WINDOWS_DLL}")
add_custom_command(
TARGET ${integration_test_target_name} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${ANALYTICS_WINDOWS_DLL}"
$<TARGET_FILE_DIR:${integration_test_target_name}>)
else()
message(WARNING "Couldn't find ${ANALYTICS_WINDOWS_DLL}. Analytics will run in stub mode.")
endif()
else()
set(ADDITIONAL_LIBS pthread)
endif()
Expand Down
Loading
Loading