export a function from exe

Share your advanced PureBasic knowledge/code with the community.
User avatar
idle
Always Here
Always Here
Posts: 5836
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

export a function from exe

Post by idle »

Tip: If you ever need to export a function from an executable.

Once you've finished laying out your procedures take a look at the commented assembly
so you can add the alias for the public symbol and compile without the debugger.

For Linux
you need to use ImportC with the export-dynamic linker flag and procedureCDLL

Code: Select all

ImportC "-Wl,--export-dynamic" : EndImport 
;...
;...
!public _Procedure16 as 'button_quit_clicked'
ProcedureCDLL button_quit_clicked()
   gtk_main_quit()
EndProcedure


For windows you need to use the "/EXPORT:" linker flag probably a new line for each exported function
though it should work for both Import or ImportC and ProcedureDLL or ProcedureCDLL respectively
depending upon what your requirements are

Code: Select all

 
ImportC "/EXPORT:button_quit_clicked" : EndImport 
;...
!public _Procedure16 as 'button_quit_clicked'
ProcedureCDLL button_quit_clicked()
   gtk_main_quit()
EndProcedure
Windows 11, Manjaro, Raspberry Pi OS
Image
User avatar
mback2k
Enthusiast
Enthusiast
Posts: 257
Joined: Sun Dec 02, 2007 12:11 pm
Location: Germany

Re: export a function from exe

Post by mback2k »

This is an awesome tip! Having exported functions inside an executable makes it easy to debug programs with external tools without having debug symbols.

Thanks for sharing this! Now it would be nice to have a pre-processor or optionally have the PureBasic compiler do this for us.

Probably going to work on some macro/pre-processor now..
User avatar
idle
Always Here
Always Here
Posts: 5836
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: export a function from exe

Post by idle »

I've made a tool to do exports for linux but I haven't done it for windows
sent you a pm with the linux code
Windows 11, Manjaro, Raspberry Pi OS
Image
User avatar
mback2k
Enthusiast
Enthusiast
Posts: 257
Joined: Sun Dec 02, 2007 12:11 pm
Location: Germany

Re: export a function from exe

Post by mback2k »

idle wrote:I've made a tool to do exports for linux but I haven't done it for windows
sent you a pm with the linux code
Thanks for the code, idle. I followed a different approach and tried to create a custom PB IDE tool.

I came up with the following Python (2.7.x) code:

Code: Select all

import os.path
import sys
import re

PB_INCLUDE_PATH = re.compile(r'IncludePath "(.+)"')
PB_INCLUDE_FILE = re.compile(r'IncludeFile "(.+)"')
PB_XINCLUDE_FILE = re.compile(r'XIncludeFile "(.+)"')
PB_PROCEDURE = re.compile(r'Procedure(\.\w)? (\w+)\(')
PB_PROCEDUREC = re.compile(r'ProcedureC(\.\w)? (\w+)\(')
PB_PROCEDURE_DLL = re.compile(r'ProcedureDLL(\.\w)? (\w+)\(')
PB_PROCEDUREC_DLL = re.compile(r'ProcedureCDLL(\.\w)? (\w+)\(')
PB_PROCEDURE_CALL = re.compile(r'(\w+)\(')
PB_PROTOTYPE = re.compile(r'Prototype(\.\w)? (\w+)\(')
PB_MACRO = re.compile(r'Macro (\w+)\(')

def is_called(procedure, procedure_calls, checked):
    if not procedure in procedure_calls:
        return False
    parents = procedure_calls[procedure]
    if None in parents:
        parents = [None]
        return True
    if procedure in checked:
        return False
    checked.append(procedure)
    for parent in parents:
        if is_called(parent, procedure_calls, checked):
            parents = [None]
            return True
    return False

def process(path, compilefile, include=None, includes=[], procedures={}, procedure_calls={}):
    lines = []
    lline = 0

    with open(include or compilefile, 'r') as file:
        lines = file.readlines()
        procedure = None
        importing = False

        if include:
            include_path = os.path.dirname(include)
        else:
            include_path = path

        for lnum, line in enumerate(lines):
            line = line.strip()

            if line.startswith(';'):
                if 'IDE Options' in line:
                    lline = lnum
                continue

            pb_include_path = PB_INCLUDE_PATH.match(line)
            if pb_include_path:
                include_path = os.path.join(path, pb_include_path.group(1).strip())

            pb_include_file = PB_INCLUDE_FILE.match(line)
            if pb_include_file:
                include_file = os.path.join(include_path, pb_include_file.group(1).strip())
                process(path, compilefile, include_file, includes, procedures, procedure_calls)

            pb_xinclude_file = PB_XINCLUDE_FILE.match(line)
            if pb_xinclude_file:
                xinclude_file = os.path.join(include_path, pb_xinclude_file.group(1).strip())
                if not xinclude_file in includes:
                    includes.append(xinclude_file)
                    process(path, compilefile, xinclude_file, includes, procedures, procedure_calls)

            pb_procedure = PB_PROCEDURE.match(line)
            if pb_procedure:
                procedure = pb_procedure.group(2).strip()
                procedures[procedure] = {'name': procedure, 'number': len(procedures)*2, 'type': ''}
                continue

            pb_procedure = PB_PROCEDUREC.match(line)
            if pb_procedure:
                procedure = pb_procedurec.group(2).strip()
                procedures[procedure] = {'name': procedure, 'number': len(procedures)*2, 'type': 'C'}
                continue

            pb_procedure = PB_PROCEDURE_DLL.match(line)
            if pb_procedure:
                procedure = pb_procedure.group(2).strip()
                procedures[procedure] = {'name': procedure, 'number': len(procedures)*2, 'type': ''}
                continue

            pb_procedure = PB_PROCEDUREC_DLL.match(line)
            if pb_procedure:
                procedure = pb_procedurec.group(2).strip()
                procedures[procedure] = {'name': procedure, 'number': len(procedures)*2, 'type': 'C'}
                continue

            pb_prototype = PB_PROTOTYPE.match(line)
            if pb_prototype:
                procedure = pb_prototype.group(2).strip()
                procedures[procedure] = {'name': procedure, 'number': len(procedures)*2}
                continue

            pb_macro = PB_MACRO.match(line)
            if pb_macro:
                procedure = pb_macro.group(1).strip()
                continue

            if 'EndProcedure' in line:
                procedure = None
                continue

            if 'Procedure' in line:
                continue

            if 'EndMacro' in line:
                procedure = None
                continue

            if 'Macro' in line:
                continue

            if 'EndImport' in line:
                importing = False
                continue

            if 'Import' in line:
                importing = True
                continue

            pb_procedure_calls = PB_PROCEDURE_CALL.finditer(line)
            for pb_procedure_call in pb_procedure_calls:
                procedure_call = pb_procedure_call.group(1).strip()
                if importing:
                    procedures[procedure_call] = {'name': procedure_call, 'number': len(procedures)*2}
                else:
                    if procedure_call in procedure_calls:
                        procedure_calls[procedure_call].append(procedure)
                    else:
                        procedure_calls[procedure_call] = [procedure]

    if not include:
        if procedures:
            procedures = procedures.values()
            procedures = filter(lambda procedure: 'type' in procedure, procedures)
            procedures = sorted(procedures, key=lambda procedure: procedure['number'], reverse=True)

            for procedure in procedures:
                lines.insert(lline, ';> _Procedure%(number)d = %(name)s\n' % procedure)

            procedures = filter(lambda procedure: is_called(procedure['name'], procedure_calls, []), procedures)

            if procedures:
                lines.insert(lline, '!SYS_EndDataSection:}\n')

                for procedure in procedures:
                    lines.insert(lline, '  !public _Procedure%(number)d as \'%(name)s\'\n' % procedure)

                lines.insert(lline, '!macro SYS_EndDataSection name{\n')

                for procedure in procedures:
                    lines.insert(lline, 'Import%(type)s "/EXPORT:%(name)s" : EndImport\n' % procedure)

            with open(compilefile, 'w') as file:
                file.writelines(lines)

def main(argv):
    if len(argv) >= 3:
        process(argv[1], argv[2])

if __name__ == '__main__':
    main(sys.argv)
Configure it like shown in the following screenshot:
Image

Unfortunately there seems to be a limit on the number of exports pbcompiler can handle.
I tried to find those limits per PureBasic edition using the following code:

Code: Select all

import subprocess
import time

TEMPLATE = '''
ProcedureDLL Test%(num)d()
  MessageRequester("Test", "Hello World!", #PB_MessageRequester_Ok)
EndProcedure

Import "/EXPORT:Test%(num)d"
EndImport

!public _Procedure%(num)d As 'Test%(num)d'
'''

if __name__ == '__main__':
    x86 = None
    x64 = None
    for max in range(50, 200, 2):
        print max
        with open('gentest.pb', 'w+') as file:
            for num in range(0, max*2, 2):
                file.write(TEMPLATE % {'num': num})
        if not x86:
            try:
                subprocess.check_output([r'C:\Program Files (x86)\PureBasic\Compilers\pbcompiler.exe', '/EXE', 'gentest_x86.exe', 'gentest.pb'])
                time.sleep(0.2)
            except:
                x86 = max
        if not x64:
            try:
                subprocess.check_output([r'C:\Program Files\PureBasic\Compilers\pbcompiler.exe', '/EXE', 'gentest_x64.exe', 'gentest.pb'])
                time.sleep(0.2)
            except:
                x64 = max
        if x86 and x64:
            break
    print 'PureBasic x86 max =', x86
    print 'PureBasic x64 max =', x64
This is the result on my machine:

Code: Select all

PureBasic x86 max = 92
PureBasic x64 max = 194
Nico
Enthusiast
Enthusiast
Posts: 274
Joined: Sun Jan 11, 2004 11:34 am
Location: France

Re: export a function from exe

Post by Nico »

idle wrote:Tip: If you ever need to export a function from an executable.

Once you've finished laying out your procedures take a look at the commented assembly
so you can add the alias for the public symbol and compile without the debugger.

For Linux
you need to use ImportC with the export-dynamic linker flag and procedureCDLL

Code: Select all

ImportC "-Wl,--export-dynamic" : EndImport 
;...
;...
!public _Procedure16 as 'button_quit_clicked'
ProcedureCDLL button_quit_clicked()
   gtk_main_quit()
EndProcedure


For windows you need to use the "/EXPORT:" linker flag probably a new line for each exported function
though it should work for both Import or ImportC and ProcedureDLL or ProcedureCDLL respectively
depending upon what your requirements are

Code: Select all

 
ImportC "/EXPORT:button_quit_clicked" : EndImport 
;...
!public _Procedure16 as 'button_quit_clicked'
ProcedureCDLL button_quit_clicked()
   gtk_main_quit()
EndProcedure

But, how to call this function in another programm?
User avatar
idle
Always Here
Always Here
Posts: 5836
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: export a function from exe

Post by idle »

But, how to call a function in another program?
I don't know if you can! Generally it's used to call a function in an exe from a shared library
that's loaded into your process. In my case I needed to export a gtk function
Windows 11, Manjaro, Raspberry Pi OS
Image
Nico
Enthusiast
Enthusiast
Posts: 274
Joined: Sun Jan 11, 2004 11:34 am
Location: France

Re: export a function from exe

Post by Nico »

thank you for the clarification
Post Reply