Skip to content

Modify Analytics on desktop to use a verified DLL on Windows (although it is currently still a stub). #1731

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 54 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
6d3d353
Here's the updated commit message:
google-labs-jules[bot] Jun 11, 2025
94abdb3
feat: Integrate Firebase logging for Windows Analytics
google-labs-jules[bot] Jun 12, 2025
b829441
refactor: Update event parameter handling for map and vector types
google-labs-jules[bot] Jun 12, 2025
fa9363e
refactor: Align Windows Analytics stubs with SDK patterns
google-labs-jules[bot] Jun 12, 2025
9b377ac
Here's the plan:
google-labs-jules[bot] Jun 12, 2025
e29cce5
refactor: Address final review comments for Windows Analytics
google-labs-jules[bot] Jun 12, 2025
a75e482
chore: Relocate analytics_desktop.cc to analytics/src/
google-labs-jules[bot] Jun 12, 2025
5c40559
Merge branch 'main' into feat/test_analytics_changes
jonsimantov Jun 12, 2025
e66c912
Clean up generated code.
jonsimantov Jun 12, 2025
3916cb5
Update desktop code to fix issues.
jonsimantov Jun 12, 2025
4fcf299
Copy Analytics DLL from SDK directory.
jonsimantov Jun 12, 2025
4fe2647
Add wide string version of SetAnalyticsLibraryPath and export
jonsimantov Jun 12, 2025
3354a59
Update log messages.
jonsimantov Jun 12, 2025
c57844c
Format code.
jonsimantov Jun 12, 2025
4620f05
Update paths and remove old stub.
jonsimantov Jun 12, 2025
3bfefbd
Update some nomenclature.
jonsimantov Jun 13, 2025
c67ac66
Add a SHA256 hash to verify on load.
jonsimantov Jun 13, 2025
d3271d4
Fix build error.
jonsimantov Jun 13, 2025
c274e7c
Add hash constant.
jonsimantov Jun 13, 2025
96abef7
Update layout.
jonsimantov Jun 13, 2025
df2278d
refactor: Rename library_path to library_filename for clarity
google-labs-jules[bot] Jun 13, 2025
2a90735
Tweak errors and format code.
jonsimantov Jun 13, 2025
5fa50c1
Revert to stubs on load fail.
jonsimantov Jun 13, 2025
0026cde
Remove big comment.
jonsimantov Jun 13, 2025
08ad239
Don't include analytics_windows.h except on Windows.
jonsimantov Jun 13, 2025
3ad6ec6
Add copyright notice.
jonsimantov Jun 13, 2025
a2f6681
Fixed log include.
jonsimantov Jun 13, 2025
619224f
Fix comment.
jonsimantov Jun 13, 2025
4a6b397
refactor: Extract GetExecutablePath helper in analytics_windows.cc
google-labs-jules[bot] Jun 13, 2025
903d0ef
refactor: Update log.h include path and extract GetExecutablePath helper
google-labs-jules[bot] Jun 13, 2025
24398f9
Merge remote-tracking branch 'origin/rename-libraryPath-param' into a…
jonsimantov Jun 13, 2025
9e09b5e
Updated formatting and errors.
jonsimantov Jun 13, 2025
0fa5cfe
Factor out log tag.
jonsimantov Jun 13, 2025
5d814c8
Format code.
jonsimantov Jun 13, 2025
f56b1bd
Fix syntax error.
jonsimantov Jun 13, 2025
8de56bb
Refactor GetExecutablePath with two-tier buffer strategy
google-labs-jules[bot] Jun 13, 2025
6780953
Clean up logic for getting executable path.
jonsimantov Jun 13, 2025
6208c96
Format code.
jonsimantov Jun 13, 2025
d70c338
Fix nesting.
jonsimantov Jun 13, 2025
7caa354
Fix nesting again.
jonsimantov Jun 13, 2025
6255b43
Use LoadLibraryW because LoadLibraryExW is failing.
jonsimantov Jun 13, 2025
b58c2ec
Fix build error.
jonsimantov Jun 13, 2025
828189e
Add a warning.
jonsimantov Jun 13, 2025
a10b866
Add an info message.
jonsimantov Jun 13, 2025
f0894e9
Fix error.
jonsimantov Jun 13, 2025
0ba79ee
Change message to debug.
jonsimantov Jun 13, 2025
aa1a9c1
Move functions.
jonsimantov Jun 13, 2025
7bfd4e3
Delete DLL file with stubs.
jonsimantov Jun 13, 2025
5ec8e5d
Silence an unneeded error.
jonsimantov Jun 13, 2025
57f2062
Revert "Delete DLL file with stubs."
jonsimantov Jun 13, 2025
016f569
Change file format and move the hash out of non-Windows platforms.
jonsimantov Jun 13, 2025
d4a1ab7
Fix: Use sizeof for Analytics DLL hash array
google-labs-jules[bot] Jun 14, 2025
c6cf8f6
Refactor: Adjust log level for hash mismatch in analytics_windows
google-labs-jules[bot] Jun 14, 2025
56de502
Format code.
jonsimantov Jun 14, 2025
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
14 changes: 10 additions & 4 deletions analytics/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,15 @@ 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(WIN32)
# Add Windows-specific sources for desktop builds.
list(APPEND desktop_SRCS src/analytics_windows.cc)
endif()

if(ANDROID)
set(analytics_platform_SRCS
Expand All @@ -86,7 +92,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\n// FIREBASE_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