16
16
"""Generate stubs and function pointers for Windows SDK"""
17
17
18
18
import argparse
19
+ import hashlib
19
20
import os
20
21
import re
21
22
import sys
22
23
23
24
HEADER_GUARD_PREFIX = "FIREBASE_ANALYTICS_SRC_WINDOWS_"
24
- INCLUDE_PATH = "src/windows/ "
25
+ INCLUDE_PATH = "src/"
25
26
INCLUDE_PREFIX = "analytics/" + INCLUDE_PATH
26
27
COPYRIGHT_NOTICE = """// Copyright 2025 Google LLC
27
28
//
39
40
40
41
"""
41
42
42
- def generate_function_pointers (header_file_path , output_h_path , output_c_path ):
43
+
44
+ def hash_file (filename ):
45
+ sha256_hash = hashlib .sha256 ()
46
+ with open (filename , "rb" ) as file :
47
+ while chunk := file .read (4096 ):
48
+ sha256_hash .update (chunk )
49
+ return sha256_hash .digest ()
50
+
51
+ def generate_function_pointers (dll_file_path , header_file_path , output_h_path , output_c_path ):
43
52
"""
44
53
Parses a C header file to generate a self-contained header with typedefs,
45
54
extern function pointer declarations, and a source file with stub functions,
46
55
initialized pointers, and a dynamic loading function for Windows.
47
56
48
57
Args:
58
+ dll_file_path (str): The path to the DLL file.
49
59
header_file_path (str): The path to the input C header file.
50
60
output_h_path (str): The path for the generated C header output file.
51
61
output_c_path (str): The path for the generated C source output file.
52
62
"""
63
+ print (f"Reading DLL file: { dll_file_path } " )
64
+ dll_hash = hash_file (dll_file_path ) # This is binary
65
+
66
+ # --- Manage known hashes ---
67
+ hash_file_path = os .path .join (os .path .dirname (dll_file_path ), "known_dll_hashes.txt" )
68
+ known_hex_hashes = []
69
+ try :
70
+ with open (hash_file_path , 'r' ) as f :
71
+ for line in f :
72
+ known_hex_hashes .append (line .strip ())
73
+ except FileNotFoundError :
74
+ print (f"Info: '{ hash_file_path } ' not found, will be created." )
75
+ pass # File doesn't exist, list remains empty
76
+
77
+ current_dll_hex_hash = dll_hash .hex ()
78
+ if current_dll_hex_hash not in known_hex_hashes :
79
+ known_hex_hashes .append (current_dll_hex_hash )
80
+
81
+ with open (hash_file_path , 'w' ) as f :
82
+ for hex_hash in known_hex_hashes :
83
+ f .write (hex_hash + '\n ' )
84
+ print (f"Updated known hashes in: { hash_file_path } " )
85
+ # --- End of manage known hashes ---
86
+
53
87
print (f"Reading header file: { header_file_path } " )
54
88
try :
55
89
with open (header_file_path , 'r' , encoding = 'utf-8' ) as f :
@@ -64,7 +98,7 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
64
98
includes = re .findall (r"#include\s+<.*?>" , header_content )
65
99
66
100
# Find all typedefs, including their documentation comments
67
- typedefs = re .findall (r"/\*\*(?:[\s\S]*?)\*/\s*typedef[\s\S]*?;\s*" , header_content )
101
+ typedefs = re .findall (r"(?: /\*\*(?:[\s\S]*?)\*/\s*)? typedef[\s\S]*?;\s*" , header_content )
68
102
69
103
# --- Extract function prototypes ---
70
104
function_pattern = re .compile (
@@ -83,18 +117,18 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
83
117
return_type = match .group (1 ).strip ()
84
118
function_name = match .group (2 ).strip ()
85
119
params_str = match .group (3 ).strip ()
86
-
120
+
87
121
cleaned_params_for_decl = re .sub (r'\s+' , ' ' , params_str ) if params_str else ""
88
122
stub_name = f"Stub_{ function_name } "
89
123
90
124
# Generate return statement for the stub
91
125
if "void" in return_type :
92
126
return_statement = " // No return value."
93
127
elif "*" in return_type :
94
- return_statement = f' return ({ return_type } )(&g_stub_memory);'
128
+ return_statement = f' return ({ return_type } )(&g_stub_memory[0] );'
95
129
else : # bool, int64_t, etc.
96
130
return_statement = " return 1;"
97
-
131
+
98
132
stub_function = (
99
133
f"// Stub for { function_name } \n "
100
134
f"static { return_type } { stub_name } ({ params_str } ) {{\n "
@@ -111,7 +145,7 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
111
145
112
146
pointer_init = f"{ return_type } (*ptr_{ function_name } )({ cleaned_params_for_decl } ) = &{ stub_name } ;"
113
147
pointer_initializations .append (pointer_init )
114
-
148
+
115
149
function_details_for_loader .append ((function_name , return_type , cleaned_params_for_decl ))
116
150
117
151
print (f"Found { len (pointer_initializations )} functions. Generating output files..." )
@@ -123,13 +157,14 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
123
157
f .write (f"// Generated from { os .path .basename (header_file_path )} by { os .path .basename (sys .argv [0 ])} \n \n " )
124
158
f .write (f"#ifndef { header_guard } \n " )
125
159
f .write (f"#define { header_guard } \n \n " )
160
+ f .write (f"#define ANALYTICS_API // filter out from header copy\n \n " )
126
161
f .write ("#include <stdbool.h> // needed for bool type in pure C\n \n " )
127
-
162
+
128
163
f .write ("// --- Copied from original header ---\n " )
129
164
f .write ("\n " .join (includes ) + "\n \n " )
130
165
f .write ("" .join (typedefs ))
131
166
f .write ("// --- End of copied section ---\n \n " )
132
-
167
+
133
168
f .write ("#ifdef __cplusplus\n " )
134
169
f .write ('extern "C" {\n ' )
135
170
f .write ("#endif\n \n " )
@@ -139,15 +174,21 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
139
174
f .write ("\n \n " )
140
175
f .write ("\n " .join (macro_definitions ))
141
176
f .write ("\n // clang-format on\n " )
142
- f .write ("\n \n // --- Dynamic Loader Declaration for Windows ---\n " )
143
- f .write ("#if defined(_WIN32)\n " )
144
- f .write ('#include <windows.h> // For HMODULE\n ' )
145
- f .write ('// Load Google Analytics functions from the given DLL handle into function pointers.\n ' )
146
- f .write (f'// Returns the number of functions successfully loaded (out of { len (function_details_for_loader )} ).\n ' )
147
- f .write ("int FirebaseAnalytics_LoadAnalyticsFunctions(HMODULE dll_handle);\n \n " )
177
+ f .write (f'\n // Number of Google Analytics functions expected to be loaded from the DLL.' )
178
+ f .write ('\n extern const int FirebaseAnalytics_DynamicFunctionCount;\n ' );
179
+ f .write ("\n // --- Dynamic Loader Declaration for Windows ---\n " )
180
+ f .write ("#if defined(_WIN32)\n \n " )
181
+ f .write ('#include <windows.h>\n ' )
182
+ f .write ('\n // Array of known Google Analytics Windows DLL SHA256 hashes (hex strings).\n ' )
183
+ f .write ('extern const char* FirebaseAnalytics_KnownWindowsDllHashes[];\n ' )
184
+ f .write ('// Count of known Google Analytics Windows DLL SHA256 hashes.\n ' )
185
+ f .write ('extern const int FirebaseAnalytics_KnownWindowsDllHashCount;\n \n ' )
186
+ f .write ('// Load Analytics functions from the given DLL handle into function pointers.\n ' )
187
+ f .write (f'// Returns the number of functions successfully loaded.\n ' )
188
+ f .write ("int FirebaseAnalytics_LoadDynamicFunctions(HMODULE dll_handle);\n \n " )
148
189
f .write ('// Reset all function pointers back to stubs.\n ' )
149
- f .write ("void FirebaseAnalytics_UnloadAnalyticsFunctions (void);\n \n " )
150
- f .write ("#endif // defined(_WIN32)\n " )
190
+ f .write ("void FirebaseAnalytics_UnloadDynamicFunctions (void);\n \n " )
191
+ f .write ("#endif // defined(_WIN32)\n " )
151
192
f .write ("\n #ifdef __cplusplus\n " )
152
193
f .write ("}\n " )
153
194
f .write ("#endif\n \n " )
@@ -159,18 +200,34 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
159
200
with open (output_c_path , 'w' , encoding = 'utf-8' ) as f :
160
201
f .write (f"{ COPYRIGHT_NOTICE } " )
161
202
f .write (f"// Generated from { os .path .basename (header_file_path )} by { os .path .basename (sys .argv [0 ])} \n \n " )
162
- f .write (f'#include "{ INCLUDE_PREFIX } { os .path .basename (output_h_path )} "\n ' )
203
+ f .write (f'#include "{ INCLUDE_PREFIX } { os .path .basename (output_h_path )} "\n \n ' )
163
204
f .write ('#include <stddef.h>\n \n ' )
164
- f .write ("// clang-format off\n \n " )
165
- f .write ("static void* g_stub_memory = NULL;\n \n " )
166
- f .write ("// --- Stub Function Definitions ---\n " )
205
+ f .write ("// A nice big chunk of stub memory that can be returned by stubbed Create methods.\n " )
206
+ f .write ("static char g_stub_memory[256] = {0};\n \n " )
207
+ f .write ("// clang-format off\n " )
208
+ f .write (f'\n // Number of Google Analytics functions expected to be loaded from the DLL.' )
209
+ f .write (f'\n const int FirebaseAnalytics_DynamicFunctionCount = { len (function_details_for_loader )} ;\n \n ' );
210
+ f .write ("#if defined(_WIN32)\n " )
211
+ f .write ('// Array of known Google Analytics Windows DLL SHA256 hashes (hex strings).\n ' )
212
+ f .write ('const char* FirebaseAnalytics_KnownWindowsDllHashes[] = {\n ' )
213
+ if known_hex_hashes :
214
+ for i , hex_hash in enumerate (known_hex_hashes ):
215
+ f .write (f' "{ hex_hash } "' )
216
+ if i < len (known_hex_hashes ) - 1 :
217
+ f .write (',' )
218
+ f .write ('\n ' )
219
+ f .write ('};\n \n ' )
220
+ f .write ('// Count of known Google Analytics Windows DLL SHA256 hashes.\n ' )
221
+ f .write (f'const int FirebaseAnalytics_KnownWindowsDllHashCount = { len (known_hex_hashes )} ;\n ' )
222
+ f .write ("#endif // defined(_WIN32)\n " )
223
+ f .write ("\n // --- Stub Function Definitions ---\n " )
167
224
f .write ("\n \n " .join (stub_functions ))
168
225
f .write ("\n \n \n // --- Function Pointer Initializations ---\n " )
169
226
f .write ("\n " .join (pointer_initializations ))
170
227
f .write ("\n \n // --- Dynamic Loader Function for Windows ---\n " )
171
228
loader_lines = [
172
229
'#if defined(_WIN32)' ,
173
- 'int FirebaseAnalytics_LoadAnalyticsFunctions (HMODULE dll_handle) {' ,
230
+ 'int FirebaseAnalytics_LoadDynamicFunctions (HMODULE dll_handle) {' ,
174
231
' int count = 0;\n ' ,
175
232
' if (!dll_handle) {' ,
176
233
' return count;' ,
@@ -188,7 +245,7 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
188
245
loader_lines .extend (proc_check )
189
246
loader_lines .append ('\n return count;' )
190
247
loader_lines .append ('}\n ' )
191
- loader_lines .append ('void FirebaseAnalytics_UnloadAnalyticsFunctions (void) {' )
248
+ loader_lines .append ('void FirebaseAnalytics_UnloadDynamicFunctions (void) {' )
192
249
for name , ret_type , params in function_details_for_loader :
193
250
loader_lines .append (f' ptr_{ name } = &Stub_{ name } ;' );
194
251
loader_lines .append ('}\n ' )
@@ -203,29 +260,31 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
203
260
parser = argparse .ArgumentParser (
204
261
description = "Generate C stubs and function pointers from a header file."
205
262
)
263
+ parser .add_argument (
264
+ "--windows_dll" ,
265
+ default = os .path .join (os .path .dirname (sys .argv [0 ]), "windows/analytics_win.dll" ),
266
+ help = "Path to the DLL file to calculate a hash."
267
+ )
206
268
parser .add_argument (
207
269
"--windows_header" ,
208
270
default = os .path .join (os .path .dirname (sys .argv [0 ]), "windows/include/public/c/analytics.h" ),
209
- #required=True,
210
271
help = "Path to the input C header file."
211
272
)
212
273
parser .add_argument (
213
274
"--output_header" ,
214
- default = os .path .join (os .path .dirname (sys .argv [0 ]), INCLUDE_PATH , "analytics_dynamic.h" ),
215
- #required=True,
275
+ default = os .path .join (os .path .dirname (sys .argv [0 ]), INCLUDE_PATH , "analytics_desktop_dynamic.h" ),
216
276
help = "Path for the generated output header file."
217
277
)
218
278
parser .add_argument (
219
279
"--output_source" ,
220
- default = os .path .join (os .path .dirname (sys .argv [0 ]), INCLUDE_PATH , "analytics_dynamic.c" ),
221
- #required=True,
280
+ default = os .path .join (os .path .dirname (sys .argv [0 ]), INCLUDE_PATH , "analytics_desktop_dynamic.c" ),
222
281
help = "Path for the generated output source file."
223
282
)
224
-
225
283
args = parser .parse_args ()
226
-
284
+
227
285
generate_function_pointers (
228
- args .windows_header ,
229
- args .output_header ,
286
+ args .windows_dll ,
287
+ args .windows_header ,
288
+ args .output_header ,
230
289
args .output_source
231
290
)
0 commit comments