Hey people, been playing around with this for a while, and it finally works
Should probably explain just for anyone who doesn't know already - the idea behind having precompiled code is that functions in your project which are completed and don't need changing can be saved in a compiled state. The benefit of doing this is that the compiler can then skip those functions each time the project is recompiled, instead of having to process them again and again for every minor change to your code. Using precompiled code has the most benefit when dealing with large function libraries, which can seriously slow down compile times when they have to be built every time.
Precompiling code is supported by languages like C++ - unfortunately, not so much in DBPro
Turns out it's possible though!
Here's the code for the system:
type funclisttype
name$ as string : ptr as dword
endtype
type dlldatatype
ptr as dword : size as dword : command_offset as dword
endtype
type globaloffsetstype
offset as dword : size as byte
endtype
dim dlldata() as dlldatatype : dim funclist() as funclisttype : dim precomp_arrayoffsets() as dword : dim precomp_globaloffsets() as globaloffsetstype
function precomp_setup(skip_stringbuffer as boolean, debugflag as boolean)
debugfile as integer : declare_funcptr as dword : template_funcptr as dword : buffer_funcptr as dword : byteread as byte : dwordread as dword : globaltype as byte : dllptr as dword : byteoffset_back as dword : byteoffset_front as dword : ptr_ignorestart as dword : funcsize as integer : count as integer : searchmode as byte : found as boolean : linenumber as dword
ptr_ignorestart = 268439552 : ptr_ignoresize = 217088 : empty array dlldata()
declare_funcptr = get ptr to function("precomp_declarelist") : template_funcptr = get ptr to function("precomp_dlltemplate") : buffer_funcptr = get ptr to function("precomp_buffertemplate")
if template_funcptr < declare_funcptr then exit prompt "Function precompile error - DLL template function should be put directly after the declare list!", "Sorry :(" : end
if buffer_funcptr < template_funcptr then exit prompt "Function precompile error - Buffer template function should be put directly after the DLL template!", "Sorry :(" : end
if is memory readable(peek dword(buffer_funcptr + 37)) = 0 then exit prompt "Function precompile error - Can't find string buffer ptr - template function should only contain the following code: " + lf$() + "'string$ as string = " + quote$() + "stringbuffer" + quote$() + lf$() + "dim precompile_array() as boolean'", "Sorry :(" : end
if is memory readable(peek dword(buffer_funcptr + 361)) = 0 then exit prompt "Function precompile error - Can't find array buffer ptr - template function should only contain the following code: " + lf$() + "'string$ as string = " + quote$() + "stringbuffer" + quote$() + lf$() + "dim precompile_array() as boolean'", "Sorry :(" : end
if debugflag = 1 then debugfile = find free datafile() : open datafile to write debugfile, "precomplog_setup.txt" : write datafile string debugfile, "Setting up function precompile data - Declare function ptr: " + str$(declare_funcptr) + "/template function ptr: " + str$(template_funcptr) + "/buffer function ptr: " + str$(buffer_funcptr)
`Scan the array buffer first, because it reads differently to everything else
if debugflag = 1 then write datafile string debugfile, "Scanning array buffer..."
dllptr = peek dword(buffer_funcptr + 361) : byteoffset_back = 4 : byteoffset_front = 4
for i = 0 to 1
if is memory readable(peek dword(dllptr - byteoffset_back)) = 0 or is memory writeable(peek dword(dllptr - byteoffset_back)) = 0 : exit : else : inc byteoffset_back, 4 : endif
i = 0
next i
for i = 0 to 1
if is memory readable(peek dword(dllptr + byteoffset_front)) = 0 or is memory writeable(peek dword(dllptr + byteoffset_front)) = 0 : exit : else : inc byteoffset_front, 4 : endif
i = 0
next i
array insert at bottom dlldata() : dlldata().ptr = dllptr - (byteoffset_back - 4) : dlldata().size = (byteoffset_back - 4) + byteoffset_front : count = 0
if debugflag = 1 then write datafile string debugfile, "Array buffer ptr: " + str$(dlldata().ptr) + "/size: " + str$(dlldata().size)
funcsize = buffer_funcptr - template_funcptr
for i = -2 to funcsize - 1
if peek byte(template_funcptr + i) = 187 or i < 0
if i = -2
dllptr = dlldata(0).ptr + dlldata(0).size
if debugflag = 1 then write datafile string debugfile, "Scanning global buffer..."
endif
if i = -1
if skip_stringbuffer = 0
dllptr = peek dword(buffer_funcptr + 37)
if debugflag = 1 then write datafile string debugfile, "Scanning string buffer..."
else
array insert at bottom dlldata() : dlldata().ptr = 0 : dlldata().size = 0 : inc count
if debugflag = 1 then write datafile string debugfile, "Skipping string buffer!"
endif
endif
if i => 0 then dllptr = peek dword(template_funcptr + i + 1)
if (is memory readable(dllptr) = 1 and is memory writeable(dllptr) = 0 and (dllptr < ptr_ignorestart or dllptr > ptr_ignorestart + (ptr_ignoresize - 1))) or i = -2 or (i = -1 and skip_stringbuffer = 0)
found = 0
if i => 0
if debugflag = 1
linenumber = 0
for k = i + 5 to funcsize - 1
if peek byte(template_funcptr + k) = 187 then exit
if peek byte(template_funcptr + k) = 66 then linenumber = peek word(template_funcptr + k + 2) : exit
next k
write datafile string debugfile, "Scanning DLL ptr at offset " + str$(i + 1) + " - line number in program: " + str$(linenumber)
endif
for k = 3 to count
if dllptr => dlldata(k).ptr and dllptr <= dlldata(k).ptr + (dlldata(k).size - 1)
found = 1 : if debugflag = 1 then write datafile string debugfile, "^ Ptr points to the same DLL added at index " + str$(k) + " (the one with ptr: " + str$(dlldata(k).ptr) + "/size: " + str$(dlldata(k).size) + ")"
exit
endif
next k
endif
if found = 0 `Add a new entry to the dll list if the dll pointer didn't point to an existing dll entry
`Read to the start of the dll in memory - jump in bytes of 100, then check each byte after it's gone past the start
byteoffset_back = 0 : byteoffset_front = 0 : searchmode = 0
if i > -2
byteoffset_back = 1
for k = 0 to 1
byteoffset_back = clamp(byteoffset_back + (100 - (101 * searchmode)), 0, 4294967295)
if (searchmode = 0 and (is memory readable(dllptr - byteoffset_back) = 0 or is memory writeable(dllptr - byteoffset_back) = clamp(i, -1, 0) + 1)) or (searchmode = 1 and is memory readable(dllptr - byteoffset_back) = 1 and is memory writeable(dllptr - byteoffset_back) = 1 - (clamp(i, -1, 0) + 1)) then inc searchmode
if searchmode = 2 then exit
k = 0
next k
searchmode = 0
endif
for k = 0 to 1
byteoffset_front = clamp(byteoffset_front + (100 - (101 * searchmode)), 0, 4294967295)
if (searchmode = 0 and (is memory readable(dllptr + byteoffset_front) = 0 or is memory writeable(dllptr + byteoffset_front) = clamp(i, -1, 0) + 1)) or (searchmode = 1 and is memory readable(dllptr + byteoffset_front) = 1 and is memory writeable(dllptr + byteoffset_front) = 1 - (clamp(i, -1, 0) + 1)) then inc searchmode
if searchmode = 2 then exit
k = 0
next k
array insert at bottom dlldata() : dlldata().ptr = dllptr - byteoffset_back : dlldata().size = byteoffset_back + (byteoffset_front + 1) : dlldata().command_offset = dllptr - dlldata().ptr : inc count
if debugflag = 1
if i = -2 then write datafile string debugfile, "Global buffer ptr: " + str$(dlldata().ptr) + "/size: " + str$(dlldata().size)
if i = -1 then write datafile string debugfile, "String buffer ptr: " + str$(dlldata().ptr) + "/size: " + str$(dlldata().size)
if i => 0 then write datafile string debugfile, str$(array count(dlldata())) + ": DLL ptr: " + str$(dlldata().ptr) + "/size: " + str$(dlldata().size) + " - command offset: " + str$(dlldata().command_offset)
endif
endif
endif
if i => 0 then inc i, 4
endif
next i
`Build the offset list for arrays and global variables
if debugflag = 1 then write datafile string debugfile, "Scanning declare function for array/global offsets..."
if array count(precomp_arrayoffsets()) = -1 and array count(precomp_globaloffsets()) = -1
funcsize = template_funcptr - declare_funcptr
for i = 0 to funcsize - 1
byteread = peek byte(declare_funcptr + i)
if (byteread => 160 and byteread <= 163) or byteread = 185 or byteread = 5
ptr = peek dword(declare_funcptr + i + 1)
for k = 0 to 1
if ptr => dlldata(k).ptr and ptr <= dlldata(k).ptr + (dlldata(k).size - 1)
byteoffset = ptr - dlldata(k).ptr
if k = 0
count = array count(precomp_arrayoffsets()) : found = 0
for n = 0 to count
if precomp_arrayoffsets(n) = byteoffset then found = 1 : exit
next n
if found = 0
array insert at bottom precomp_arrayoffsets() : precomp_arrayoffsets() = ptr - dlldata(k).ptr
if debugflag = 1 then write datafile string debugfile, "Found array ptr at offset " + str$(i + 1) + " - offset in array buffer: " + str$(precomp_arrayoffsets())
else
if debugflag = 1 then write datafile string debugfile, "Array ptr at offset " + str$(i + 1) + " is a duplicate, skipping!"
endif
else
count = array count(precomp_globaloffsets()) : found = 0
for n = 0 to count
if precomp_globaloffsets(n).offset = byteoffset then found = 1 : exit
next n
if found = 0
dwordread = peek dword(ptr) : globaltype = 0
if is memory readable(dwordread) = 1
if peek string(dwordread) = "string" then globaltype = 1
endif
if globaltype = 0
byteread = peek byte(ptr) : if byteread > 0 then globaltype = 2
endif
if globaltype > 0
array insert at bottom precomp_globaloffsets() : precomp_globaloffsets().offset = ptr - dlldata(k).ptr
if globaltype = 1 : precomp_globaloffsets().size = 4 : else : precomp_globaloffsets().size = byteread : endif
if debugflag = 1
if globaltype = 1 : write datafile string debugfile, "Found global string ptr at offset " + str$(i + 1) + " - offset in global buffer: " + str$(precomp_globaloffsets().offset) + " - size value: 4" : else : write datafile string debugfile, "Found global variable ptr at offset " + str$(i + 1) + " - offset in global buffer: " + str$(precomp_globaloffsets().offset) + " - size value given: " + str$(precomp_globaloffsets().size) : endif
endif
else
if debugflag = 1 then write datafile string debugfile, "WARNING - Global value at offset " + str$(i + 1) + " has no size written, so is being skipped!"
endif
else
if debugflag = 1 then write datafile string debugfile, "Global ptr at offset " + str$(i + 1) + " is a duplicate, skipping!"
endif
endif
endif
next k
inc i, 4
endif
next i
sort array precomp_arrayoffsets() : sort array precomp_globaloffsets(), 1
if debugflag = 1 then write datafile string debugfile, "Finished!" : close datafile debugfile
endif
endfunction
function precomp_compilefunctions(filename$ as string, function_startname$ as string, function_endname$ as string, debugflag as boolean)
file as integer : debugfile as integer : count as integer : dll_count as integer : func_startindex as integer : func_endindex as integer : func_firstline as dword : name$ as string : byteread as byte : found as boolean : foundbuffer as boolean : funcptr as dword : funcsize as dword : linestart as integer : ptr as dword : globaloffsets_count as integer : byteoffset as integer : ptr_ignorestart as dword : ptr_ignoresize as dword
ptr_ignorestart = 268439552 : ptr_ignoresize = 217088
count = get function count() : dll_count = array count(dlldata()) : function_startname$ = fast lower$(function_startname$) : function_endname$ = fast lower$(function_endname$) : arrayoffsets_count = array count(precomp_arrayoffsets()) : globaloffsets_count = array count(precomp_globaloffsets())
if dll_count = -1 then exit prompt "Function precompile error - The setup function needs to be called before compiling any functions!", "Sorry :(" : end
if dlldata(2).ptr = 0 then exit prompt "Function precompile error - The string buffer needs to be scanned at setup before compile any functions!", "Sorry :(" : end
if fast lower$(get function name$(count)) = function_endname$ then exit prompt "Function precompile error - The last function to compile (" + function_endname$ + ") mustn't also be the last function in your code!", "Sorry :(" : end
file = find free datafile() : open datafile to write file, filename$
if debugflag = 1 then debugfile = find free datafile() : open datafile to write debugfile, "precomplog_compile.txt" : write datafile string debugfile, "Compiling functions between '" + func_startname$ + "' and '" + func_endname$ + "..."
for i = 1 to count - 1
name$ = fast lower$(get function name$(i))
if name$ = function_startname$ then func_startindex = i : found = 1
if found = 1
funcptr = get ptr to function(i) : funcsize = get ptr to function(i + 1) - funcptr
if debugflag = 1 then write datafile string debugfile, "Compiling function '" + name$ + "' - size: " + str$(funcsize)
write datafile string file, name$ : write datafile dword file, funcsize
`Find the line number of the first line in the function
func_firstline = 0
for k = 0 to funcsize - 1
if peek byte(funcptr + k) = 66 then write datafile boolean file, 1 : func_firstline = peek dword(funcptr + k + 2) : write datafile dword file, func_firstline : exit
next k
if func_firstline = 0 then write datafile boolean file, 0
if debugflag = 1 then write datafile string debugfile, "Function first line: " + str$(func_firstline)
for k = 0 to funcsize - 1
byteread = peek byte(funcptr + k) : write datafile byte file, byteread
if byteread = 187 `DLL ptr
ptr = peek dword(funcptr + k + 1)
if is memory readable(ptr) = 1 and is memory writeable(ptr) = 0 and (ptr < ptr_ignorestart or ptr > ptr_ignorestart + (ptr_ignoresize - 1))
if debugflag = 1 then write datafile string debugfile, "Found DLL ptr '" + str$(ptr) + "' at offset " + str$(k + 1)
foundbuffer = 0
for n = 3 to dll_count
if ptr => dlldata(n).ptr and ptr <= dlldata(n).ptr + (dlldata(n).size - 1) then foundbuffer = 1 : exit
next n
if foundbuffer = 0
for n = k + 5 to funcsize - 1
if peek byte(funcptr + n) = 187 then exit
if peek byte(funcptr + n) = 66 then exit prompt "Function precompile error - Couldn't find entry in DLL data for pointer " + str$(ptr) + " at line " + str$(peek dword(funcptr + n + 2) - func_firstline) + " of function '" + name$ + "' (line " + str$(peek dword(funcptr + n + 2)) + " in the project) - forgotten to add the DLL to precompile_dlltemplate()? :s", "Sorry :(" : end
next n
exit prompt "Function precompile error - Couldn't find entry in DLL data for pointer " + str$(ptr) + " (line number unknown) of function '" + name$ + "' - forgotten to add the DLL to precompile_dlltemplate()? :s", "Sorry :(" : end
else
write datafile boolean file, 1
write datafile dword file, n : write datafile integer file, (ptr - dlldata(n).ptr) - dlldata(n).command_offset : inc k, 4
if debugflag = 1 then write datafile string debugfile, "^ points to DLL memory at index " + str$(n) + " - offset from command given in template: " + str$((ptr - dlldata(n).ptr) - dlldata(n).command_offset)
endif
else
write datafile boolean file, 0
endif
endif
if byteread = 184 `String buffer ptr
ptr = peek dword(funcptr + k + 1)
if ptr => dlldata(2).ptr and ptr <= dlldata(2).ptr + (dlldata(2).size - 1)
write datafile boolean file, 1 : write datafile string file, peek string(ptr) : inc k, 4 : if debugflag = 1 then write datafile string debugfile, "Found ptr to string buffer at offset " + str$(k + 1) + " - string: " + peek string(ptr)
else
write datafile boolean file, 0
endif
endif
if (byteread => 160 and byteread <= 163) or byteread = 185 or byteread = 5 or byteread = 13 or byteread = 29 `Variable/global/array buffer ptrs
ptr = peek dword(funcptr + k + 1) : foundbuffer = 0
for n = 1 to 3
if n = 1
if ptr => dlldata(0).ptr - 1500 and ptr <= dlldata(0).ptr - 4
write datafile byte file, 1 : write datafile word file, dlldata(0).ptr - ptr : foundbuffer = 1 : if debugflag = 1 then write datafile string debugfile, "Found ptr to variable buffer at offset " + str$(k + 1) + " - offset from array ptr: " + str$(ptr - dlldata(0).ptr) + "/preceding byte: " + str$(byteread)
exit
endif
else
if ptr => dlldata(n - 2).ptr and ptr <= dlldata(n - 2).ptr + (dlldata(n - 2).size - 1)
`Subtract array/global offsets
if n = 1
if debugflag = 1 then write datafile string debugfile, "Found ptr to array buffer at offset " + str$(k + 1) + " - preceding byte: " + str$(byteread)
for l = arrayoffsets_count to 0 step -1
if dlldata(n - 2).ptr + precomp_arrayoffsets(l) <= ptr then dec ptr, 4
next l
else
if debugflag = 1 then write datafile string debugfile, "Found ptr to global buffer at offset " + str$(k + 1) + " - integer at ptr: " + str$(peek integer(ptr)) + "/preceding byte: " + str$(byteread)
for l = globaloffsets_count to 0 step -1
if dlldata(n - 2).ptr + precomp_globaloffsets(l).offset <= ptr then dec ptr, precomp_globaloffsets(l).size
next l
endif
write datafile byte file, n : write datafile dword file, ptr - dlldata(n - 2).ptr : foundbuffer = 1 : exit
endif
endif
next n
if foundbuffer = 0 : write datafile byte file, 0 : else : inc k, 4 : endif
endif
if byteread = 15 `DBPro error message data (I think :s)
if peek byte(funcptr + k + 1) = 133 : write datafile boolean file, 1 : write datafile dword file, peek dword(funcptr + k + 2) : inc k, 5 : else : write datafile boolean file, 0 : endif
endif
next k
endif
if found = 1 and name$ = function_endname$ then func_endindex = i : found = 2 : exit
next i
if found = 0 then exit prompt "Function precompile error - Couldn't find start function '" + function_startname$ + "' while compiling functions!", "Sorry :(" : end
if found = 1 then exit prompt "Function precompile error - Couldn't find end function '" + function_endname$ + "' while compiling functions!", "Sorry :(" : end
write datafile string file, "*precomp_funcend"
if debugflag = 1 then write datafile string debugfile, "Finished compiling functions, searching for user function calls..."
for i = func_startindex to func_endindex
funcptr = get ptr to function(i) : funcsize = get ptr to function(i + 1) - funcptr
if debugflag = 1 then write datafile string debugfile, "Searching function " + get function name$(i)
for k = 0 to funcsize - 1
if peek byte(funcptr + k) = 232
ptr = funcptr + k + peek integer(funcptr + k + 1) + 5
if debugflag = 1 then write datafile string debugfile, "Found valid function call at offset " + str$(k + 1)
if function ptr is valid(ptr) = 1
for n = 1 to count
if get ptr to function(n) = ptr
write datafile dword file, k + 1 : write datafile string file, fast lower$(get function name$(n))
if debugflag = 1 then write datafile string debugfile, "^ call points to function '" + get function name$(n) + "'"
exit
endif
next n
inc k, 4
endif
endif
next k
write datafile dword file, 1
next i
write datafile dword file, 0 : close datafile file : if debugflag = 1 then write datafile string debugfile, "Finished!" : close datafile debugfile
endfunction
function precomp_loadfunctions(filename$ as string, debugflag as boolean)
file as integer : debugfile as integer : count as integer : arrayoffsets_count as integer : globaloffsets_count as integer : funclist_startindex as integer : funcname$ as string : funcsize as dword : byteread as byte : dlloffset as dword : dllsize as dword : found as boolean : byteoffset as integer : string$ as string : ptr as dword : dll_indexmissing as boolean
count = array count(dlldata()) : funclist_startindex = array count(funclist()) : dll_indexmissing = 0 : arrayoffsets_count = array count(precomp_arrayoffsets()) : globaloffsets_count = array count(precomp_globaloffsets())
if count = -1 then exit prompt "Function precompile error - the setup function needs to be called before loading any precompiled functions!", "Sorry :(" : end
file = find free datafile() : open datafile to read file, filename$
if debugflag = 1 then debugfile = find free datafile() : open datafile to write debugfile, "precomplog_load.txt" : write datafile string debugfile, "Loading functions from file '" + filename$ + "'..."
for i = 0 to 1
funcname$ = fast lower$(datafile string$(file))
if datafile end(file) = 1 then exit prompt "Function precompile error - Unexpected end of file while loading '" + filename$ + "' - file corrupted?", "Sorry :(" : end
if funcname$ = "*precomp_funcend" then exit
funcsize = datafile dword(file)
if datafile boolean(file) = 1 : func_firstline = datafile dword(file) : else : func_firstline = 0 : endif
if debugflag = 1 then write datafile string debugfile, "Loading function '" + funcname$ + "' - size: " + str$(funcsize)
array insert at bottom funclist() : funclist().name$ = funcname$ : funclist().ptr = alloc(funcsize)
for k = 0 to funcsize - 1
byteread = datafile byte(file)
poke byte funclist().ptr + k, byteread
if byteread = 66 and dll_indexmissing = 1 then byteread = datafile byte(file) : exit prompt "Function precompile error - Precompiled function '" + funcname$ + "' references DLL data index " + str$(dllindex) + " at line " + str$(datafile dword(file) - func_firstline) + ", but that index isn't valid anymore - precompile_dlltemplate function modified? :s", "Sorry :(" : end
if byteread = 187
if datafile boolean(file) = 1
dllindex = datafile dword(file) : dlloffset = datafile integer(file)
if debugflag = 1 then write datafile string debugfile, "Call to DLL at offset " + str$(k + 1) + " - array index: " + str$(dllindex) + "(ptr: " + str$(dlldata(dllindex).ptr) + ")/offset from command: " + str$(dlloffset)
if dllindex <= count
poke dword funclist().ptr + k + 1, dlldata(dllindex).ptr + (dlldata(dllindex).command_offset + dlloffset)
else
dll_indexmissing = 1 : if debugflag = 1 then write datafile string debugfile, "Error - DLL array index (" + str$(dllindex) + ") isn't valid in the DLL array (count: " + str$(array count(dlldata())) + ")"
endif
inc k, 4
endif
endif
if byteread = 184
if datafile boolean(file) = 1 then string$ = datafile string$(file) : ptr = alloc(fast len(string$) + 1) : poke string ptr, string$ : poke dword funclist().ptr + k + 1, ptr : inc k, 4 : if debugflag = 1 then write datafile string debugfile, "Found string '" + string$ + "' at offset " + str$(k - 3) + " - poked to ptr " + str$(ptr)
endif
if (byteread => 160 and byteread <= 163) or byteread = 185 or byteread = 5 or byteread = 13 or byteread = 29
byteread = datafile byte(file)
if byteread > 0
if byteread = 1
byteoffset = datafile word(file) : poke dword funclist().ptr + k + 1, dlldata(0).ptr - byteoffset : if debugflag = 1 then write datafile string debugfile, "Found call to variable buffer at offset " + str$(k + 1) + " - offset from array buffer: " + str$(-byteoffset) + "/preceding byte: " + str$(peek byte(funclist().ptr + k))
else
`Add array/global offsets
byteoffset = datafile dword(file)
if byteread = 2
if debugflag = 1 then write datafile string debugfile, "Found call to array buffer at offset " + str$(k + 1) + " - default buffer offset: " + str$(byteoffset) + "/preceding byte: " + str$(peek byte(funclist().ptr + k))
for n = 0 to arrayoffsets_count
if precomp_arrayoffsets(n) <= byteoffset : inc byteoffset, 4 : else : exit :endif
next n
else
if debugflag = 1 then write datafile string debugfile, "Found call to global buffer at offset " + str$(k + 1) + " - default buffer offset: " + str$(byteoffset) + "/preceding byte: " + str$(peek byte(funclist().ptr + k))
for n = 0 to globaloffsets_count
if precomp_globaloffsets(n).offset <= byteoffset : inc byteoffset, precomp_globaloffsets(n).size : else : exit :endif
next n
endif
if debugflag = 1 then write datafile string debugfile, "Final offset (after checking declare list): " + str$(byteoffset)
poke dword funclist().ptr + k + 1, dlldata(byteread - 2).ptr + byteoffset
endif
inc k, 4
endif
endif
if byteread = 15
if datafile boolean(file) = 1 then poke byte funclist().ptr + k + 1, 133 : poke dword funclist().ptr + k + 2, datafile dword(file) : inc k, 5 : if debugflag = 1 then write datafile string debugfile, "Skipped DBPro error data at offset " + str$(k) + "..."
endif
next k
register function ptr funclist().ptr
i = 0
next i
count = array count(funclist()) : func_index = funclist_startindex + 1
if debugflag = 1 then write datafile string debugfile, "Done loading, updating calls to user functions..."
for i = 0 to 1
byteoffset = datafile dword(file)
if datafile end(file) = 1 then exit prompt "Function precompile error - Unexpected end of file while loading '" + filename$ + "' - file corrupted?", "Sorry :(" : end
if byteoffset = 0 then exit
if byteoffset = 1
inc func_index
else
string$ = datafile string$(file) : ptr = get ptr to function(string$) : if debugflag = 1 then write datafile string debugfile, "Found call to function '" + string$ + "' at offset " + str$(byteoffset) + " of function '" + funclist(func_index).name$
if ptr = 0
if debugflag = 1 then write datafile string debugfile, "Can't find function in code, checking loaded functions..."
for n = 0 to count
if funclist(n).name$ = string$ then ptr = funclist(n).ptr : exit
next n
endif
if ptr = 0 then exit prompt "Function precompile error - Can't set call to user function '" + string$ + "' from precompiled function '" + funclist(func_index).name$ + "' because it can't be found - if it's been deleted or renamed, you need to recompile the functions!", "Sorry :(" : end
poke integer funclist(func_index).ptr + byteoffset, (ptr - funclist(func_index).ptr) - 4 - byteoffset ` : print "Updated call offset to " + str$(funclist(func_index).ptr + peek integer(funclist(func_index).ptr + byteoffset) + 4 + byteoffset)
endif
i = 0
next i
close datafile file : if debugflag = 1 then write datafile string debugfile, "Finished!" : close datafile debugfile
endfunction
function precomp_declarelist()
endfunction
function precomp_dlltemplate()
endfunction
function precomp_buffertemplate()
string$ as string = "stringbuffer"
dim precompile_array(0) as boolean : precompile_array(0) = 1
endfunction
function precomp_getfuncptr(name$ as string)
count as integer : ptr as dword
count = array count(funclist()) : name$ = fast lower$(name$)
for i = 0 to count
if funclist(i).name$ = name$ then ptr = funclist(i).ptr : exitfunction ptr
next i
exit prompt "Function precompile error - Can't get ptr to function '" + name$ + "', because it doesn't exist!", "Sorry :(" : end
endfunction ptr
Here's a small example to show it working - replace the blank
precomp_dlltemplate in the full code above with the one in this example when compiling it. Once you've run it once, the program should still work fine if you remove the functions for creating and updating the cube!
type funclisttype
name$ as string : ptr as dword
endtype
type dlldatatype
ptr as dword : size as dword : command_offset as dword
endtype
type globaloffsetstype
offset as dword : size as byte
endtype
sync on
dim dlldata() as dlldatatype : dim funclist() as funclisttype : dim precomp_arrayoffsets() as dword : dim precomp_globaloffsets() as globaloffsetstype
precomp_setup(0, 0)
if file exist("cubeheader.dbh") = 0 then precomp_compilefunctions("cubeheader.dbh", "cube_make", "cube_update", 0)
precomp_loadfunctions("cubeheader.dbh", 0)
call function ptr precomp_getfuncptr("cube_make")
do
call function ptr precomp_getfuncptr("cube_update")
sync
loop
function cube_make()
autocam off : position camera 0, 0, -180 `Camera DLL command
make object cube 1, 100 `Basic 3D DLL command
color object 1, rgb(255, 0, 0) `Basic 2D DLL command (RGB)
endfunction
function cube_update()
text 0, 0, " Inside a precompiled function :D" `Text DLL command
yrotate object 1, object angle y(1) + 0.05
endfunction
function precomp_dlltemplate()
make object sphere 1, 100 `Basic 3D DLL
box 0, 0, 0, 0 `Basic 2D DLL
autocam off `Camera DLL
text 0, 0, "" `Text DLL
endfunction
How to use it!
Basically, this system works by peeking the memory of compiled functions (accessed using the Matrix1Utils function ptr commands) and saving it to a header file. The code for the functions can then be removed from your project, and loaded from the header files at runtime. Apart from the fact you need to call them using 'call function ptr', functions loaded this way behave exactly the same as normal functions - they accept values, return values, run at the same speeds...
Depending on how much function code you remove from your project doing this, you should be able to massively speed up compile times, and, compared to how long the compiler normally takes to compile them, the time it takes to load functions from header files is pretty damn small - load times included, a spinning cube program using Evolved's Advanced Lighting library takes about 1 minute 30 seconds to compile on my machine. With the entire library saved to a header file, the program compiles/loads in about 6 seconds, with the whole library loading in less than half a second
Unfortunately, this is kind of hacky, and because of that, there's a trade off with using this system - having to structure your global variable and array declarations in weird ways
The reason for this is that function memory contains a number of pointers to different resources held internally by DBPro, such as DLLs, global variables, and arrays, which change each time your program is run - because precompiled functions are stored externally, any pointers they contain need to be updated when they're loaded. This means there needs to be a way for the system to identify the current pointers to these resources - hence the need for having some of your code in a specific structure!
Arrays:
Arrays are probably the worst part in terms of having to restructure your code. Apart from the one in the buffer template function,
all arrays (including ones not used by functions you have/want to have precompiled) need to be DIMmed once before precomp_setup is called! Internally, arrays declared in your code are stored in an area of memory called (by me, probably not the official name
) the array buffer - having arrays in your code which are DIMmed after the setup function is called causes the setup to wrongly judge the size of the buffer, which is likely to screw things up. Here's an example:
This is right:
dim array() as integer
dim array2() as integer
dim array3() as integer
precompile_setup(0, 0)
function arrayshouldntbehere()
endfunction
This isn't!
dim array() as integer
precompile_setup(0, 0)
dim array2() as integer
function arrayshouldntbehere()
dim array3() as integer
endfunction
Worth noting, it's totally fine to re-dim arrays with a different size or whatever anywhere else in your code, they just need to have been dimmed at least once before setup!
Another important thing - if you don't use precomp_declarelist (explained somewhere below), any functions you have precompiled will need to be recompiled if you add or remove any arrays in your code OR rename any existing arrays!
Global variables:
Global variables also get stored in a buffer, but having them declared before setup isn't necessary for judging it's size. It's fine to declare globals anywhere in your code, but it's important to move any global declarations out of functions you want to precompile, because you'll want to delete that function code afterwards - as with arrays, adding, removing or renaming any globals outside of the
precomp_declarelist function will change the layout of the global buffer, which messes things up! Note that for any global variables using user-defined types, adding or removing fields to the type will also screw things up!
That's pretty much the annoying part - using the actual system is pretty straightforward - well, not so much the declare list maybe, but the rest of it
Functions:
precomp_setup(skip_stringbuffer as boolean, debug as boolean)
This needs to be called before compiling or loading any functions. When compiling functions, the string buffer needs to be scanned, but you can speed up load time by skipping it if you're loading functions!
precomp_compilefunctions(filename$, debug func_startname$, func_endname$)
Saves the compiled functions between and including func_startname$ and func_endname$ to the header file filename$. An example is if you had three functions in your code, func_1(), func_2(), and func_3(), each declared after the other - to compile all these to a single header file, you'd use the line 'precomp_compilefunctions("headerfile.dbh", "func_1", "func_3")'
Worth noting, func_endname$ can't be the last function in your code (because the system works out the size of a function's memory by getting the pointer to the next one) - an easy way around it is just adding a blank function to the end of your code.
precomp_loadfunctions(filename$, debugflag)
Loads previously saved functions to memory from the header file filename$. There's no limit to how many header files you can have loaded, so having functions saved into seperate header files works fine!
precomp_declarelist()
Probably the worst thing about this system is the fact that adding, removing, or renaming any arrays or globals, or adding/removing fields from a type used by a global, will most likely screw everything up (90% of the time ending in an 'Application has stopped working' error, but the other 10% giving you weird problems where loaded functions read/write to the wrong variables/arrays) - the declare list tries to make life easier! Whether it actually does is your call
The idea is that any arrays or globals you declare inside precomp_declarelist can be added, removed, or renamed without affecting loaded functions. Don't forget though, any arrays declared in the function still need to be DIMmed before the setup is called, so you should put a call to the declarelist function before the setup function.
Declaring arrays in the function is simple - just DIM them like normal. With globals, it's a little more complicated
Each global variable declared needs to be set to a value, so that the setup function knows how much space they occupy. Here's the list of sizes you need to assign:
Byte/booleans should be set to 1
Words should be set to 2
Integers/Dwords should be set to 4
Double integers should be set to 8
Floats, because they're stored differently in memory, should be set to -6644994
Double floats should be set to -6644996
Strings should be set to "string"
Variables using a user defined type should have the first field (which can't be a float) set to the total size of all fields in the UDT - Note that with UDT fields, DBPro always assigns a minimum of 4 bytes, even if they're declared as a smaller datatype, but assigns 8 for doubles as normal. Strings count as 4 bytes!
Here's how the function should look declaring a mix of arrays and globals:
type exampletype
field1 as integer `Size: 4
field2 as dword `Size: 4
field3 as byte `Size: 4
field4 as double integer `Size: 8
endtype
function precomp_declarelist()
dim array() as integer
dim array2() as exampletype
global var1 as byte = 1
global var2 as integer = 4
global var3 as word = 2
global var4 as string = "string"
global var5 as exampletype : var5.field1 = 20 `Total size of the type
endfunction
Worth noting it's totally fine to mix using the declare list with declaring arrays/globals outside the list - but any ones declared outside mean saved functions need to be recompiled
Apart from having to count up the size of UDTs, the most annoying thing about using function is you have to be really careful to make sure you've got it right - setting the wrong size value to variables won't give any kind of warning error, it'll just crash the program/make things go weird, so it's kind of a nightmare for debugging :/
precomp_dlltemplate()
The DLL template function is used by precomp_setup to get pointers to DBPro DLLs. Basically, the DLL template function should contain a list of commands, with each command representing a DLL plugin used in functions you want to precompile. Sounds pretty confusing, sorry, so here's an example - imagine you wanted to precompile this function:
function cube()
make object cube 1, 100
endfunction
There's only one command here, 'make object cube', which uses the DBPro Basic3D DLL. To precompile and load this function, the DLL template needs to contain any one command from the Basic3D DLL, so it can update pointers to that DLL - so in this example, the DLL template could look like this:
function precomp_dlltemplate()
rotate object 1, 0, 0, 0 `Any other command from the Basic3D DLL would work fine too!
endfunction
A DLL only needs to have one command representing it in the DLL template, so you wouldn't need to add anything more to the template if the cube function contained any more Basic3D commands!
Once you've precompiled functions, changing or removing lines in the template means you'll need to recompile the functions again, or it'll assign the wrong DLL pointers - instant meltdown D:
precomp_buffertemplate()
This template function can just be left alone - it's used by the setup function to get pointers to the array and string buffers. It should always look like this:
function precomp_buffertemplate()
string$ as string = "stringbuffer"
dim precompile_array(0) as boolean : precompile_array(0) = 1
endfunction
precomp_getfuncptr(funcname$)
Returns the pointer of the loaded function
funcname$. Note that to call loaded functions, you need to use 'call function ptr' - using 'call function name' doesn't work
That's pretty much it! Here's the general pros and cons of using this system summed up:
Pros:
Massively increases compile times when used with large libraries such as Advanced Lighting
Allows for a truly unlimited number of functions - Credit to Chris Tate for discovering a weird bug which causes the compiler to crash when dealing with lots of functions!
Cons:
Pretty annoying and delicate to use
Loaded functions need to be called using 'call function ptr', which has a few limits itself (like a max of 10 arguments)
Sorry for the crazily long and probably really overdetailed post! To be honest my timing on finishing this is terrible, since it looks like DBPro will be getting an updated faster compiler after all, so it might not actually be worth anyone's time trying to get this working - hope it's useful to someone though
With more effort this whole system could probably be improved and made a lot easier to use, and I'm pretty sure there's potential in hacking DBPro functions by messing around with the memory - getting things like function overloading working probably wouldn''t be too hard! I'm probably done with coding for about a month now, getting this thing working has been a nightmare, but if anyone else wants to play around with the code, go for it
If you hit any problems with it, let me know!
Thanks
The code never bothered me anyway...