Skip to content

Commit e473214

Browse files
authored
Add Analytics Windows C header and script to generate stubs and function pointers. (#1728)
* Add Windows C header and script to generate stubs and function pointers. * Add header and DLL for library. * Update script and generated files. * Tweak script and generated files. * Update function name. * Add copyright notices. * Format code. * Add an Unload method. * Add script name that generated headers. * Update with clang-format off. * Add copyright notices.
1 parent 33b6d6f commit e473214

File tree

8 files changed

+1851
-0
lines changed

8 files changed

+1851
-0
lines changed

analytics/generate_windows_stubs.py

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2025 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
"""Generate stubs and function pointers for Windows SDK"""
17+
18+
import argparse
19+
import os
20+
import re
21+
import sys
22+
23+
HEADER_GUARD_PREFIX = "FIREBASE_ANALYTICS_SRC_WINDOWS_"
24+
INCLUDE_PATH = "src/windows/"
25+
INCLUDE_PREFIX = "analytics/" + INCLUDE_PATH
26+
COPYRIGHT_NOTICE = """// Copyright 2025 Google LLC
27+
//
28+
// Licensed under the Apache License, Version 2.0 (the "License");
29+
// you may not use this file except in compliance with the License.
30+
// You may obtain a copy of the License at
31+
//
32+
// http://www.apache.org/licenses/LICENSE-2.0
33+
//
34+
// Unless required by applicable law or agreed to in writing, software
35+
// distributed under the License is distributed on an "AS IS" BASIS,
36+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
37+
// See the License for the specific language governing permissions and
38+
// limitations under the License.
39+
40+
"""
41+
42+
def generate_function_pointers(header_file_path, output_h_path, output_c_path):
43+
"""
44+
Parses a C header file to generate a self-contained header with typedefs,
45+
extern function pointer declarations, and a source file with stub functions,
46+
initialized pointers, and a dynamic loading function for Windows.
47+
48+
Args:
49+
header_file_path (str): The path to the input C header file.
50+
output_h_path (str): The path for the generated C header output file.
51+
output_c_path (str): The path for the generated C source output file.
52+
"""
53+
print(f"Reading header file: {header_file_path}")
54+
try:
55+
with open(header_file_path, 'r', encoding='utf-8') as f:
56+
header_content = f.read()
57+
except FileNotFoundError:
58+
print(f"Error: Header file not found at '{header_file_path}'")
59+
return
60+
61+
# --- Extract necessary definitions from the original header ---
62+
63+
# Find all standard includes (e.g., <stdint.h>)
64+
includes = re.findall(r"#include\s+<.*?>", header_content)
65+
66+
# Find all typedefs, including their documentation comments
67+
typedefs = re.findall(r"/\*\*(?:[\s\S]*?)\*/\s*typedef[\s\S]*?;\s*", header_content)
68+
69+
# --- Extract function prototypes ---
70+
function_pattern = re.compile(
71+
r"ANALYTICS_API\s+([\w\s\*]+?)\s+(\w+)\s*\((.*?)\);",
72+
re.DOTALL
73+
)
74+
matches = function_pattern.finditer(header_content)
75+
76+
extern_declarations = []
77+
stub_functions = []
78+
pointer_initializations = []
79+
function_details_for_loader = []
80+
81+
for match in matches:
82+
return_type = match.group(1).strip()
83+
function_name = match.group(2).strip()
84+
params_str = match.group(3).strip()
85+
86+
cleaned_params_for_decl = re.sub(r'\s+', ' ', params_str) if params_str else ""
87+
stub_name = f"Stub_{function_name}"
88+
89+
# Generate return statement for the stub
90+
if "void" in return_type:
91+
return_statement = " // No return value."
92+
elif "*" in return_type:
93+
return_statement = " return NULL;"
94+
else: # bool, int64_t, etc.
95+
return_statement = " return 0;"
96+
97+
stub_function = (
98+
f"// Stub for {function_name}\n"
99+
f"static {return_type} {stub_name}({params_str}) {{\n"
100+
f"{return_statement}\n"
101+
f"}}"
102+
)
103+
stub_functions.append(stub_function)
104+
105+
declaration = f"extern {return_type} (*ptr_{function_name})({cleaned_params_for_decl});"
106+
extern_declarations.append(declaration)
107+
108+
pointer_init = f"{return_type} (*ptr_{function_name})({cleaned_params_for_decl}) = &{stub_name};"
109+
pointer_initializations.append(pointer_init)
110+
111+
function_details_for_loader.append((function_name, return_type, cleaned_params_for_decl))
112+
113+
print(f"Found {len(pointer_initializations)} functions. Generating output files...")
114+
115+
# --- Write the self-contained Header File (.h) ---
116+
header_guard = f"{HEADER_GUARD_PREFIX}{os.path.basename(output_h_path).upper().replace('.', '_')}_"
117+
with open(output_h_path, 'w', encoding='utf-8') as f:
118+
f.write(f"{COPYRIGHT_NOTICE}")
119+
f.write(f"// Generated from {os.path.basename(header_file_path)} by {os.path.basename(sys.argv[0])}\n\n")
120+
f.write(f"#ifndef {header_guard}\n")
121+
f.write(f"#define {header_guard}\n\n")
122+
123+
f.write("// --- Copied from original header ---\n")
124+
f.write("\n".join(includes) + "\n\n")
125+
f.write("".join(typedefs))
126+
f.write("// --- End of copied section ---\n\n")
127+
128+
f.write("#ifdef __cplusplus\n")
129+
f.write('extern "C" {\n')
130+
f.write("#endif\n\n")
131+
f.write("// --- Function Pointer Declarations ---\n")
132+
f.write("// clang-format off\n")
133+
f.write("\n".join(extern_declarations))
134+
f.write("\n// clang-format on\n")
135+
f.write("\n\n// --- Dynamic Loader Declaration for Windows ---\n")
136+
f.write("#if defined(_WIN32)\n")
137+
f.write('#include <windows.h> // For HMODULE\n')
138+
f.write('// Load Google Analytics functions from the given DLL handle into function pointers.\n')
139+
f.write(f'// Returns the number of functions successfully loaded (out of {len(function_details_for_loader)}).\n')
140+
f.write("int FirebaseAnalytics_LoadAnalyticsFunctions(HMODULE dll_handle);\n\n")
141+
f.write('// Reset all function pointers back to stubs.\n')
142+
f.write("void FirebaseAnalytics_UnloadAnalyticsFunctions(void);\n\n")
143+
f.write("#endif // defined(_WIN32)\n")
144+
f.write("\n#ifdef __cplusplus\n")
145+
f.write("}\n")
146+
f.write("#endif\n\n")
147+
f.write(f"#endif // {header_guard}\n")
148+
149+
print(f"Successfully generated header file: {output_h_path}")
150+
151+
# --- Write the Source File (.c) ---
152+
with open(output_c_path, 'w', encoding='utf-8') as f:
153+
f.write(f"{COPYRIGHT_NOTICE}")
154+
f.write(f"// Generated from {os.path.basename(header_file_path)} by {os.path.basename(sys.argv[0])}\n\n")
155+
f.write(f'#include "{INCLUDE_PREFIX}{os.path.basename(output_h_path)}"\n')
156+
f.write('#include <stddef.h>\n\n')
157+
f.write("// clang-format off\n\n")
158+
f.write("// --- Stub Function Definitions ---\n")
159+
f.write("\n\n".join(stub_functions))
160+
f.write("\n\n\n// --- Function Pointer Initializations ---\n")
161+
f.write("\n".join(pointer_initializations))
162+
f.write("\n\n// --- Dynamic Loader Function for Windows ---\n")
163+
loader_lines = [
164+
'#if defined(_WIN32)',
165+
'int FirebaseAnalytics_LoadAnalyticsFunctions(HMODULE dll_handle) {',
166+
' int count = 0;\n',
167+
' if (!dll_handle) {',
168+
' return count;',
169+
' }\n'
170+
]
171+
for name, ret_type, params in function_details_for_loader:
172+
pointer_type_cast = f"({ret_type} (*)({params}))"
173+
proc_check = [
174+
f' FARPROC proc_{name} = GetProcAddress(dll_handle, "{name}");',
175+
f' if (proc_{name}) {{',
176+
f' ptr_{name} = {pointer_type_cast}proc_{name};',
177+
f' count++;',
178+
f' }}'
179+
]
180+
loader_lines.extend(proc_check)
181+
loader_lines.append('\n return count;')
182+
loader_lines.append('}\n')
183+
loader_lines.append('void FirebaseAnalytics_UnloadAnalyticsFunctions(void) {')
184+
for name, ret_type, params in function_details_for_loader:
185+
loader_lines.append(f' ptr_{name} = &Stub_{name};');
186+
loader_lines.append('}\n')
187+
loader_lines.append('#endif // defined(_WIN32)\n')
188+
f.write('\n'.join(loader_lines))
189+
f.write("// clang-format on\n")
190+
191+
print(f"Successfully generated C source file: {output_c_path}")
192+
193+
194+
if __name__ == '__main__':
195+
parser = argparse.ArgumentParser(
196+
description="Generate C stubs and function pointers from a header file."
197+
)
198+
parser.add_argument(
199+
"--windows_header",
200+
default = os.path.join(os.path.dirname(sys.argv[0]), "windows/include/public/c/analytics.h"),
201+
#required=True,
202+
help="Path to the input C header file."
203+
)
204+
parser.add_argument(
205+
"--output_header",
206+
default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_dynamic.h"),
207+
#required=True,
208+
help="Path for the generated output header file."
209+
)
210+
parser.add_argument(
211+
"--output_source",
212+
default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_dynamic.c"),
213+
#required=True,
214+
help="Path for the generated output source file."
215+
)
216+
217+
args = parser.parse_args()
218+
219+
generate_function_pointers(
220+
args.windows_header,
221+
args.output_header,
222+
args.output_source
223+
)

0 commit comments

Comments
 (0)