Page 1 of 1

export a function from exe

Posted: Thu Jan 10, 2013 6:00 am
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

Re: export a function from exe

Posted: Mon Feb 04, 2013 10:57 pm
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..

Re: export a function from exe

Posted: Tue Feb 05, 2013 5:02 am
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

Re: export a function from exe

Posted: Sun Feb 24, 2013 9:16 pm
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

Re: export a function from exe

Posted: Mon Feb 25, 2013 7:14 pm
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?

Re: export a function from exe

Posted: Mon Feb 25, 2013 8:06 pm
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

Re: export a function from exe

Posted: Mon Feb 25, 2013 8:57 pm
by Nico
thank you for the clarification