Copia del wikiespejo

Este es un espejo del wiki de pascal.chat. La edición, el historial y el inicio de sesión están allí.

Creating bindings for C libraries/es

English · Español · Français · Русский · 中文(中国) · 日本語

Introducción

Esta página describe como crear Pascal bindings de librerías C. Para utilizar las librerias de C en Pascal hay que crear una traducción para cada función, tipo y variable de C. Para hacer esto se puede utilizar tanto una herramienta automatizada llamada h2pas como una conversión manual de las cabeceras.

H2PAS

Overview

La utilidad H2Pas traduce automáticamente la mayor parte de la cosas habituales de C. La creación se automatiza adicionalmente ayudada de una GUI desde Lazarus que utiliza h2pas y otras utilidades. Esto ayuda a crear un conjunto de reglas que pueden ser utilizadas para actualizar los bindings. Por tanto la próxima versión de la librería de C puese ser convertida más rápida y facilmente. Una característica agradable del wizard de h2pas es que utiliza ficheros temporales, por lo que el fichero original de cabeceras C permanece sin cambios.

Work flow

Install tools

La utilidad h2pas está incluida en cada instalación normal de fpc.

Para instalar el paquete h2paswizard en el IDE de Lazarus hay que ir a "Paquete->Instalar/Desinstalar Paquetes", seleccionar de la lista de la derecha el paquete H2PasWizard y hacer click en la sección 'Instalar', a continuación en Salvar y reconstruir el IDE. Una vez reiniciado el IDE ya tenemos una nueva entrada en el menú: Utilidaes -> h2pas

Fetch C header files

Los ficheros de cabecera de C (extensión .h) describen el interface de la librería en C. Normalmente no se encuentran con la librería, por lo que hay que obtener las fuentes o el paquete de desarrollo de la librería. Por ejemplo, los ficheros de cabeceras C correspondientes a las librerias gtk se encuentran en el paquete gtk+.devel.

Ejemplo: MPICH2

Descarga mpich2-1.0.3.tar.gz desde http://www-unix.mcs.anl.gov/mpi/mpich2/ y desempaquetalo. Los ficheros .h se encuentran en mpich2-1.0.3/src/include.

Puedes encontrar más información al respecto en MPICH for FPC aquí.

Create a working directory and name your bindings

Create a directory with a useful name. A name should not contain special characters, like spaces, umlaute, points or commas. Copy the .h files.

Example: MPICH2

The h2p directory will be used for the pascal files. The h2p/c_sources directory will be used for the .h files.

bash
mkdir -p h2p/c_sources
cp mpich2-1.0.3/src/include/*.h h2p/c_sources/

Create a new h2pas project with the h2pas wizard

Open the h2pas wizard: "Tools -> h2pas". This will open a window. You can switch between this window and the other IDE windows. Your last h2pas project will be loaded automatically. To create a new project click on "Settings -> New/Clear settings". Then click on the bottom button "Save settings" and choose a filename.

Note: If this is the first time you have used the h2pas wizard, you need to use the wizard's "Settings" tab to tell the wizard where to find the h2pas.exe converter program. The h2pas.exe converter lives in the same directory as the FreePascal compiler fpc.exe - if you downloaded FPC as part of Lazarus it will be in ...\Lazarus\fpc*version*\bin. Then save the settings so you don't have to do this again.

Example: MPICH2

Click on "Settings -> New/Clear settings". Then click on the bottom button "Save settings" and save it as h2p/mpi2.h2p.

Add the .h files to the h2pas-project

In the "C header files" page you can add/delete the .h files. You can enable/disable .h files to convert only part of the files.

Example: MPICH2

Click on "C header files -> Add .h files ..." and select "mpi.h" and "mpio.h". They will be enabled automatically. Select the mpi.h file and click "Merge all but this", so that the wizard combines all header files into one unit (mpi.pas).

Setup the h2pas options

Under "h2pas Options" you can set the parameters for the h2pas program.

Example: MPICH2

Run the wizard

Click on the bottom button "Run h2pas". This will copy the <example>.h file to a temporary <example>.tmp.h file and run the tools listed under "Before h2pas". Then it runs h2pas to convert the <example>.tmp.h into <example>.inc or <example>.pas or whatever output extension you setup on the h2pas page. Then it runs the tools listed under "After h2pas" on the output files.

If h2pas finds a syntax error, the IDE will open the example.tmp.h file and jumps to the error line. h2pas often only reports 'syntax error', which is very unspecific. See Common problems when converting C header files.

Example: MPICH2

The h2pas wizard already contains all tools to convert all the specials of this header file, so h2pas runs without errors. But the created unit is not yet ready. Read further.

Add the following to the Undefines property:

text
 MPI_INCLUDED
 MPIO_INCLUDE
 NEEDS_MPI_FINT
 MPI_BUILD_PROFILING
 MPICH_SUPPRESS_PROTOTYPES
 MPI_Wtime
 HAVE_MPICH2G2
 FOO
 HAVE_PRAGMA_HP_SEC_DEF

Add the following to the Defines property:

text
 MPICH2
 MPICH_NEW_MPIIO

Writing your own converter tools

Using the tool "Search and replace"

Many things like renaming a variable can be done by the Search and replace tool. Add the tool via the Add new tool button on either the 'Before h2pas' or the 'After h2pas' page. Then set the SearchFor, ReplaceWith, Options and Caption property.

Example: rename an identifier Tguint to guint

Property Value
Caption Rename Tguint to guint
SearchFor Tguint
ReplaceWith guint
Options [trtMatchCase,trtWholeWord]

Example: rename several identifiers

Rename Tguint to guint, Tgulong to gulong, Tgint to gint:

Property

Value

Caption

Rename Tguint to guint

SearchFor

pascal
T(guint|gint|gulong)

ReplaceWith

$1

Options

[trtMatchCase,trtWholeWord,trtRegExpr]

Example: MPICH2

Removing Makro MPI_DUP_FN

The MPI_DUP_FN is defined two times in the mpi C headers. Once as a macro to something that does not exist, and later to a real function. So, the first should be deleted.

Add a 'Search and replace' tool to the before H2Pas tools (at the end) with the following settings:

Property

Value

Caption

Remove dummy define MPI_DUP_FN

Name

RemoveDummyDefineMPI_DUP_FN

SearchFor

pascal
^#define\s+MPI_DUP_FN\s+MPIR_Dup_fn

ReplaceWith

Options

[trtMatchCase,trtRegExpr]

Removing typecasts to MPI_DUP_FN

Add a 'Search and replace' tool to the before H2Pas tools (at the end) with the following settings:

Property

Value

Caption

Remove MPI_DUP_FN Makros

Name

Remove_MPI_DUP_Func

SearchFor

pascal
^#define .*MPI_DUP_FN\).*$

ReplaceWith

Options

[trtMatchCase,trtRegExpr]

Removing functions MPIO_Test, PMPIO_Test, MPIO_Wait and PMPIO_Wait

The MPIO_Test, PMPIO_Test, MPIO_Wait and PMPIO_Wait functions are defined twice and redefined via macro. So, the functions can be removed:

Add a 'Search and replace' tool to the before H2Pas tools (at the end) with the following settings:

Property

Value

Caption

pascal
Remove Func P?MPIO_(Test|Wait)

Name

RemoveFuncP_MPIO_Test_Wait

SearchFor

pascal
^int P?MPIO_(Test|Wait).*$

ReplaceWith

Options

[trtMatchCase,trtRegExpr]

Improving an existing tool

You found a bug and want to fix it or you want to extend one of the above tools. Great!

Most of the above tools are defined in the h2pasconvert unit, which is part of the h2paswizard package. Basically a tool needs a classname, a description and an Execute method. To test/debug a tool outside the IDE and save a lot of compilation time, see the project components/simpleideintf/examples/testh2pastool.lpi. Compile it and start it on a console/terminal with the filename of a .h file as first command parameter. For example:

bash
./testh2pastool files/h2pastest.pas

Writing a custom tool

You can write your own conversion tools and register them in the IDE easily. Start a package and a add a new unit (say unit1) for your new class. See the existing tools as example and read the prior section. When you wrote a new tool and tested it with the above simpleideintf project, then register it in the IDE: Add a register procedure to your unit (unit1), like the following (pseudo code):

pascal
uses
  Classes, ..., IDETextConverter;

type
  TYourToolClass = class(TCustomTextConverterTool)
  public
    class function ClassDescription: string; override;
    function Execute(aText: TIDETextConverter): TModalResult; override;
  end;

procedure Register;

implementation

procedure Register;
begin
  TextConverterToolClasses.RegisterClass(TYourToolClass);
end;

Do not forget to enable the register checkbox of the unit in the package editor, otherwise the Register procedure will not be called by the IDE. Then install your package in the IDE.

Future work / Missing stuff

C:

c
typedef struct page
{
   u32int present    : 1;   // Page present in memory
   u32int rw         : 1;   // Read-only if clear, readwrite if set
   u32int user       : 1;   // Supervisor level only if clear
   u32int accessed   : 1;   // Has the page been accessed since last refresh?
   u32int dirty      : 1;   // Has the page been written to since last refresh?
   u32int unused     : 7;   // Amalgamation of unused and reserved bits
   u32int frame      : 20;  // Frame address (shifted right 12 bits)
} page_t;

Pascal:

pascal
type
    Unsigned_7  = 0 .. (1 shl 7)  - 1;
    Unsigned_20 = 0 .. (1 shl 20) - 1;

type
    page_t = bitpacked record
       present  : boolean;
       rw       : boolean;
       user     : boolean;
       accessed : boolean;
       dirty    : boolean;
       unused   : Unsigned_7;
       frame    : Unsigned_20;
    end;

Convert C to Pascal with lua script

I'm impressed by the power of the lua scripting language and how easy it is to do funny things with it.

lua
--
-- Please send any improvement to mingodadATgmailDOTcom.
--

inFileName = arg[1] or 'g:\\tmp\\plua\\clua\\lvm.c'

fh = assert(io.open(inFileName))
cbody = fh:read('*a')
fh:close()

pasbody = cbody

function split(str, pat)
   local t = {}  -- NOTE: use {n = 0} in Lua-5.0
   local fpat = '(.-)' .. pat
   local last_end = 1
   local s, e, cap = str:find(fpat, 1)
   while s do
      if s ~= 1 or cap ~= '' then
    table.insert(t, cap)
      end
      last_end = e+1
      s, e, cap = str:find(fpat, last_end)
   end
   if last_end <= #str then
      cap = str:sub(last_end)
      table.insert(t, cap)
   end
   return t
end

function trim(s)
  -- from PiL2 20.4
  return (s:gsub('^%s*(.-)%s*$', '%1'))
end

function trimChars(str, c1, c2)
    return str:gsub('^%' .. c1 .. '*(.-)%' .. c2 .. '*$', '%1')
end

function getPascalType(cType)
    if cType == 'void' then
        return 'pointer'
    elseif cType == 'int' then
        return 'integer'
    elseif cType == 'short' then
        return 'Shortint'
    elseif cType == 'define' then
        return 'is_define'
    else
        return cType
    end
end

--check var name and type
--if var starts with a capital letter
-- add 'T' informt of it
--if var is preceded by * then add a 'P' for type

function checkVarType(aType, aVar)
    aType = getPascalType(aType)
    aType = aType:gsub('^(%u)', 'T%1')
    aVar = aVar:gsub('^(%*)', function(m)
            aType = 'P' .. aType
            return ''
        end)
    aVar = aVar:gsub('(%[[^]]+%])', function(m)
            aType = 'array' .. m .. ' of ' .. aType
            return ''
        end)
    return aType, aVar
end

function varDeclaration(aType, aVar)
    aType, aVar = checkVarType(aType, aVar)
    return aVar .. ' : ' .. aType
end

function checkDeclarationOrGoto(m1, m2, m3)
    if m2 ~= 'goto' then
        return '\n' .. m1 .. varDeclaration(m2, m3) .. ';'
    else
        return '\n' .. m1 .. m2 .. ' ' .. m3 .. ';'
    end
end

function addProcFunc(mtype, mname, mparams, mbody)
    local str, isFunc, rType, k, v
    if mtype == 'void' then
        str = '\nprocedure '
        isFunc = false
    else
        str = '\nfunction '
        isFunc = true
    end
    mtype, mname = checkVarType(mtype, mname)
    local vparams = split(trimChars(mparams, '(',')'), ',')
    str = str .. mname .. '('

    local tparams = {}
    for k, v in pairs(vparams) do
        local vparam = split(trim(v), ' ')
        if #vparam > 2 then
            table.insert(tparams, vparam[1] .. ' ' .. varDeclaration(vparam[2], vparam[3]))
        else
            --print(vparam[1], vparam[2])
            table.insert(tparams, varDeclaration(vparam[1], vparam[2] or 'is_define'))
        end
    end

    str = str .. table.concat(tparams, '; ') .. ')'
    if isFunc then
        if mtype == 'int' then
            rType = 'integer'
        else
            rType = mtype
        end
        str = str .. ':' .. rType
    end

    if mbody then
        local tblLabels = {}
        for v in mbody:gmatch('\n[\t ]*([%w_]+)[\t ]*:[^=]') do
            if v ~= 'default' then
                table.insert(tblLabels, v)
            end
        end

        if #tblLabels > 0 then
            str = str .. ';\nlabel\n  '.. table.concat(tblLabels, ',  ')
        end

        local declarations = {}

        mbody = mbody:gsub('(\n[\t ]*)const[\t ]+([%w_]+)[\t ]+(%*?[%w_]+)[\t ]*:%=[\t ]*([^;\n]+;)','%1 %2 %3 := %4')

        mbody = mbody:gsub('(\n[\t ]*)([%w_]+)[\t ]+(%*?[%w_]+)[\t ]*:%=[\t ]*([^;\n]+;)', function(m1, m2, m3, m4)
            table.insert(declarations, varDeclaration(m2, m3) .. ';' )
            return m1 .. m3 .. ' := ' .. m4
        end)

        mbody = mbody:gsub('\n[\t ]*const[\t ]*([%w_]+)[\t ]+(%*?[%w_]+)[\t ]*;', function(m1,m2)
                table.insert(declarations, varDeclaration(m1, m2) .. ';')
                return ''
            end)

        mbody = mbody:gsub('\n([\t ]*)([%w_]+)[\t ]+(%*?[%w_ ,]+);', function(m1,m2,m3)
                if m2 ~= 'goto' then
                    table.insert(declarations, varDeclaration(m2, m3) .. ';')
                    return ''
                else
                    return '\n' .. m1 .. m2 .. ' ' .. m3 .. ';'
                end
            end)

        if #declarations > 0 then
            local unique_decl = {}
            table.sort(declarations)
            local lastV = ''
            local k2, v2
            for k2,v2 in pairs(declarations) do
                if v2 ~= lastV then
                    lastV = v2
                    table.insert(unique_decl, v2)
                end
            end
            str = str .. ';\nvar\n  '.. table.concat(unique_decl, '\n  ')  .. '\n'
        else
            str = str .. ';\n'
        end

        str = str .. 'begin' .. trimChars(mbody, '{','}') .. 'end'
    end
    return str .. ';'
end

--*************************************
--
-- Order of substitutions are important
--
--*************************************

--=assignements statements
    pasbody = pasbody:gsub("([\t ]*)([%w_]+)[\t ]*=[\t ]*([^=;\n]+);", '%1%2 := %3;')

--return
    pasbody = pasbody:gsub('([\t ]*)return[\t ]+([^;]+);', '%1exit(%2);')

--NULL
    pasbody = pasbody:gsub('([^%w]+)NULL([^%w]+)', '%1nil%2')

--void functions to procedure declaratios
    pasbody = pasbody:gsub('([%w_*]+)[\t ]+([%w_*]+)[\t ]*(%b())[%s]*(%b{})', addProcFunc)
    --pasbody = pasbody:gsub('\n([%w_]+)%s+([^%s]+)%s*(%b());', addProcFunc)

--const/static
    tblLeaveCommented = {
        'const', 'static'
    }
    for k,v in pairs(tblLeaveCommented) do
        pasbody = pasbody:gsub('([\t ]*)(' .. v .. ')([\t ]*)', '%1{%2}%3')
    end

--defines to const
    isConstEmmited = false
    function addConst(m1,m2)
        local sm
        sm = ''
        if not isConstEmmited then
            isConstEmmited = true
            sm = '\nconst'
        end
        return sm .. '\n\t' .. m1 .. ' = ' .. m2 .. ';'
    end

    isConstEmmited = false
    pasbody = pasbody:gsub('\n#define%s+([^%s]+)%s+(%b())', '\nconst %1 = %2;')
    isConstEmmited = false
    pasbody = pasbody:gsub('\n#define%s+([^%s]+)[ \t]+([^%s]+)', '\nconst %1 = %2;')

--includes
    tblUses = {}
    pasbody = pasbody:gsub('\n#[ \t]*(include)%s+([^\n]+)', function(m1,m2)
            for w in m2:gmatch('"([^.]+)%..+"') do
                table.insert(tblUses, w)
            end
            return '\n//#' .. m1 .. ' ' .. m2
        end)
    pasbody = '//uses ' .. table.concat(tblUses, ', ') .. ';\n' .. pasbody

--defines to compiler directives
    pasbody = pasbody:gsub('\n#([^\n]+)', '\n{$%1}')

--comments
    pasbody = pasbody:gsub('\n#', '\n//#')
    pasbody = pasbody:gsub('/%*', '(*')
    pasbody = pasbody:gsub('%*/', '*)')

--declarations
    pasbody = pasbody:gsub('\n([\t ]*)([%w_]+)[\t ]+([%w_ ,]+);', checkDeclarationOrGoto)

--structs
    function parseStruct(m1, m2)
        return '\n' .. m1 .. ' = record\n' .. trimChars(m2, '{', '}') .. '\nend'
    end
    pasbody = pasbody:gsub('\n[\t ]*struct[\t ]+([%w_]+)[\t ]*(%b{})', parseStruct)

--if statements
    parseStatementCalled = false

    function parseStatement(mSpace, mStatement, mCond, mBody)
        parseStatementCalled = true
        local vStatement
        if mStatement == 'if' then
            vStatement = mStatement .. ' ' .. mCond .. ' then'
        elseif mStatement == 'else' then
            return mSpace .. mStatement .. ' begin' .. trimChars(mCond, '{', '}') .. ' end;'
        elseif (mStatement == 'while') then
            vStatement = mStatement .. ' ' .. mCond .. ' do'
        elseif (mStatement == 'for') then
            vStatement = mStatement .. ' ' .. mCond .. ' do'
            --local forArgs = split(trimChars(mCond, '(', ')'), ';')
            --return mSpace .. (forArgs[1] or '') .. ';' .. mSpace ..'while (' ..
            --  (forArgs[2] or 'true') .. ') do' .. mSpace .. 'begin' .. mSpace .. trimChars(mBody, '{', '}') .. '\n' .. mSpace ..
            --  (forArgs[3] or '') .. ';\n' .. mSpace .. 'end;'
        else
            vStatement = mStatement .. ' ' .. mCond .. '\n'
        end
        return mSpace .. vStatement .. ' begin' ..  trimChars(mBody, '{', '}') .. 'end;'
    end

    -- for nested blocks
    parseStatementCalled = true
    while(parseStatementCalled) do
        parseStatementCalled = false
        pasbody = pasbody:gsub('([^%w_])(if)[\t ]*(%b())[\t ]*(%b{})', parseStatement)
    end

    -- else
    pasbody = pasbody:gsub('([\t ]+)(else)[\t ]*(%b{})', parseStatement)

    pasbody = pasbody:gsub("([^%w_])if[\t ]*(%b())[\t ]*([^;\n]+);", '%1if %2 then %3;')

--?: short if
    pasbody = pasbody:gsub("([^?])?([^:;\n]):[\t ]*([^%s;]+);", ' iff(%1, %2, %3) ')

--while loop
    -- for nested blocks
    parseStatementCalled = true
    while(parseStatementCalled) do
        parseStatementCalled = false
        pasbody = pasbody:gsub('([^%w_])(while)[\t ]*(%b())[\t ]*(%b{})', parseStatement)
    end

--for loop
    -- for nested blocks
    parseStatementCalled = true
    while(parseStatementCalled) do
        parseStatementCalled = false
        pasbody = pasbody:gsub('([^%w_])(for)[\t ]*(%b())%s*(%b{})', parseStatement)
    end

--do/while
    -- for nested blocks
    parseStatementCalled = true
    while(parseStatementCalled) do
        parseStatementCalled = false
        pasbody = pasbody:gsub("([^%w_])do[\t ]*(%b{})[\t ]*while([^;\n]+);",
            function(m1, m2, m3)
                parseStatementCalled = true
                return m1 .. 'repeat ' .. trimChars(m2, '{', '}') .. 'until not' .. m3
            end)
    end

--switch/case statements

    --begin/end enclose blocks around
    -- for nested blocks
    parseStatementCalled = true
    while(parseStatementCalled) do
        parseStatementCalled = false
        pasbody = pasbody:gsub("([^%w_]case[\t ]+[^:]+)[\t ]*:[%s]*(%b{})",
            function(m1, m2, m3)
                parseStatementCalled = true
                return m1 .. ': begin' .. trimChars(m2, '{', '}') .. 'end;'
            end)
        pasbody = pasbody:gsub("([^%w_]default[\t ]*):[%s]*(%b{})",
            function(m1, m2, m3)
                parseStatementCalled = true
                return m1 .. ': begin' .. trimChars(m2, '{', '}') .. 'end;'
            end)
    end

    --breaks remove
    pasbody = pasbody:gsub("([^%w_]case[\t ]+[^:]+)[\t ]*:[%s]*(.-)break;", '%1: %2')

    --case
    pasbody = pasbody:gsub("([^%w_])case[\t ]+([^:]+):", '%1%2:')

    -- for nested blocks
    parseStatementCalled = true
    while(parseStatementCalled) do
        parseStatementCalled = false
        pasbody = pasbody:gsub("([^%w_])switch[\t ]*(%b())[\t ]*(%b{})",
            function(m1, m2, m3)
                parseStatementCalled = true
                return m1 .. 'case ' .. m2 .. ' of ' .. trimChars(m3, '{', '}') .. 'end;'
            end)
    end

    --pasbody = pasbody:gsub("([\t ]*)case[\t ]+('.')[\t ]*:\n", '%1%2:\n')
    --pasbody = pasbody:gsub("([\t ]*)case[\t ]+(%d+)[\t ]*:\n", '%1%2:\n')
    --pasbody = pasbody:gsub("([\t ]*)(case)[\t ]+('.')[\t ]*:[\t ]*(%b{})", '%1%2 : %3')

--pre/pos increments
    -- (n)++ -> PosInc(n)
    pasbody = pasbody:gsub("(%([^%s+]+%))%+%+", 'PosInc%1')
    -- n*++ -> PosInc(n*)
    pasbody = pasbody:gsub("([^%s*+]+)%+%+", 'PosInc(%1)')
    -- ++(n) -> PreInc(n)
    pasbody = pasbody:gsub("%+%+(%([^%s;+]+%))", 'PreInc%1')
    -- ++*n -> PreInc(n)
    pasbody = pasbody:gsub("%+%+([^%s*;+]+)", 'PreInc(%1)')

    -- (n)--
    pasbody = pasbody:gsub("(%([^%s-]+%))%-%-", 'PosDec%1')
    -- n*--
    pasbody = pasbody:gsub("([^%s*-]+)%-%-", 'PosDec(%1)')
    -- --(n)
    pasbody = pasbody:gsub("%-%-(%([^%s;-]+%))", 'PreDec%1')
    -- --n
    pasbody = pasbody:gsub("%-%-([^%s;-]+)", 'PreDec(%1)')

--boolean operators
    tblBooleanOperators = {
        {'||', 'or'},
        {'&&', 'and'},
    }
    for k,v in pairs(tblBooleanOperators) do
        pasbody = pasbody:gsub('([\t ]*)' .. v[1] .. '([\t ]*)', '%1) ' .. v[2] .. ' (%2')
    end

--pointers
    pasbody = pasbody:gsub("%->", '.')
    pasbody = pasbody:gsub("%(%*([%w_]+)", '(%1^')
    pasbody = pasbody:gsub("%*%*([%w_]+)", '%1^^')
    pasbody = pasbody:gsub("&([%w_]+)", '@%1')
    pasbody = pasbody:gsub("%*([%w_]+)[\t ]*:[\t ]*([%w_]+);", '%1 : ^%2;')
    pasbody = pasbody:gsub("%^char", 'pchar')
    --pasbody = pasbody:gsub("%*([%w_]+)[\t ]*:=[\t ]*([^;\n]+);", '%1^ := %2;')

--bit operators
    tblBitOperators = {
        {'<<', 'shl'},
        {'>>', 'shr'},
        {'|', 'or'},
        {'&', 'and'},
        {'~', 'not'},
    }

    for k,v in pairs(tblBitOperators) do
        pasbody = pasbody:gsub('([\t ]*)' .. v[1] .. '([\t ]*)', '%1 ' .. v[2] .. ' %2')
    end

--==eq operator
    pasbody = pasbody:gsub("([\t ]*)%=%=([\t ]*)", '%1 = %2')

--!= not eq operator
    pasbody = pasbody:gsub("([\t ]*)!%=([\t ]*)", '%1 <> %2')

--! unary
    pasbody = pasbody:gsub('([\t ]*)!([\t ]*)', '%1 not %2')

--blocks
    --pasbody = pasbody:gsub("(%b{})", function(m1) return 'begin' ..  trimChars(m1, '{', '}') .. 'end;' end)
    --pasbody = pasbody:gsub("(%b{})", function(m1) return 'begin' ..  trimChars(m1, '{', '}') .. 'end;' end)

--strings and escaped characters
    tblEscapedCahrs = {
        {'\\"', '"'},
        {'\\\\', '\\'},
        {'\\t', "'#9"},
        {'\\n', "'#10'"},
        {'\\f', "'#12'"},
    }
    pasbody = pasbody:gsub('%b""', function(m1)
            return "'" .. trimChars(m1:gsub("'", "''") , '"', '"')  .. "'"
        end)
    for k,v in pairs(tblEscapedCahrs) do
        pasbody = pasbody:gsub(v[1], v[2])
    end

--common functions/procedure
    tblOrigFuncNewFunc = {
        {'printf', 'format'},
        {'malloc', 'GetMem'},
        {'free', 'Dispose'},
        {'memcpy', 'Move'},
    }

    for k,v in pairs(tblOrigFuncNewFunc) do
        pasbody = pasbody:gsub('([\t ]+)' .. v[1] .. '%(', '%1' .. v[2] .. '(')
    end

--typedefs
    isTypeEmmited = false
    function addType(m1,m2)
        local sm
        sm = ''
        if not isTypeEmmited then
            isTypeEmmited = true
            sm = '\ntype'
        end
        return sm .. '\n\t' .. m2 .. ' = ' .. m1 .. ';'
    end
    --pasbody = pasbody:gsub('\ntypedef%s+struct%s+([^%s]+)%s+([^;]+)', addType)
    --pasbody = pasbody:gsub('\ntypedef%s+([^%s]+)%s+([^;]-)', addType)
    pasbody = pasbody:gsub('\n[\t ]*typedef[\t ]+struct[\t ]+([%w_]+)[\t ]+([^;]+)', '\n%2 = %1')
    pasbody = pasbody:gsub('\n[\t ]*typedef[\t ]+struct%s+(%b{})%s+([%w_]+);',
        function(m1, m2) return m2 .. ' = record' .. trimChars(m1, '{', '}') .. 'end;' end)

-- print the results
print(pasbody)

Here is an example of a c file converted.

pascal
//uses lua, llimits, lmem, lstate, lzio;
(*
** $Id: lzio.c,v 1.31.1.1 2007/12/27 13:02:25 roberto Exp $
** a generic input stream interface
** See Copyright Notice in lua.h
*)

//#include <string.h>

{$define lzio_c}
{$define LUA_CORE}

//#include 'lua.h'

//#include 'llimits.h'
//#include 'lmem.h'
//#include 'lstate.h'
//#include 'lzio.h'

function luaZ_fill(z : PTZIO):integer;
var
  L : Plua_State;
  buff : Pchar;
  size : size_t;
begin
  *L := z.L;
  lua_unlock(L);
  buff := z.reader(L, z.data, @size);
  lua_lock(L);
  if (buff  =  nil ) or ( size  =  0) then exit(EOZ);
  z.n := size - 1;
  z.p := buff;
  exit(char2int(PosInc^((z.p))));
end;

function luaZ_lookahead(z : PTZIO):integer;
begin
  if (z.n  =  0) then begin
    if (luaZ_fill(z)  =  EOZ)
      exit(EOZ);
    else begin
      PosInc(z.n);  (* luaZ_fill removed first byte; put back it *)
      z-PosDec(>p);
     end;
  end;
  exit(char2int(z^.p));
end;

procedure luaZ_init(L : Plua_State; z : PTZIO; reader : lua_Reader; data : Ppointer);
begin
  z.L := L;
  z.reader := reader;
  z.data := data;
  z.n := 0;
  z.p := nil;
end;

(* --------------------------------------------------------------- read --- *)

function luaZ_read(z : PTZIO; b : Ppointer; n : size_t):size_t;
var
  m : size_t;
begin
  while (n) do begin
    if (luaZ_lookahead(z)  =  EOZ)
      exit(n);  (* exit(number of missing bytes *)
    m = (n <= z.n) ? n : z.n);  (* min. between n and z.n *)
    Move(b, z.p, m);
    z.n -= m;
    z.p += m;
    b := (char *)b + m;
    n -= m;
  end;
  exit(0);
end;

(* ------------------------------------------------------------------------ *)

function luaZ_openspace(L : Plua_State; buff : PTMbuffer; n : size_t):Pchar;
begin
  if (n > buff.buffsize) then begin
    if (n < LUA_MINBUFFER) then n := LUA_MINBUFFER;
    luaZ_resizebuffer(L, buff, n);
  end;
  exit(buff.buffer);
end;

Conversión manual de las cabeceras de C a Pascal

En este caso uno debe aplicar sus conocimientos tanto e C como de Pascal para crear las cabeceras equivalentes. Se puede aprender a realizar dicha conversión estudiando los siguientes ejemplos.

Convirtiendo constantes

C:

c
#define SOME_CONSTANT 5
#define ANOTHER_CONSTANT 6

Pascal:

pascal
const
  SOME_CONSTANT = 5;
  ANOTHER_CONSTANT = 6;

Convirtiendo rutinas exportadas

Las rutinas más básicas se pueden traducir como sigue:

C:

c
int somekind_of_method(int* param_first, int* param_second)
void another_method(char*)

Pascal:

pascal
uses ctypes;

const
  MyLib = 'mylib';

function somekind_of_method(param_first, param_second: pcint): cint; external MyLib;
procedure another_method_renamed(param1: PChar); external MyLib name 'another_method';

Ten en cuenta que se debe utilizar la unidad ctypes junto con los tipos que declara para convertir las cabeceras C en lugar de tratar de utilizar directamente los tipos básicos de Pascal. Esto asegura que los bindings son correctos en todas las plataformas, ya que los tipos C pueden tener fiferentes tamaños para cada arquitectura y sistema operativo.

También hay que tener en cuenta que printers a los tipos básicos de C pueden convertirse a los tipos declarados en ctypes, por ejemplo, int* es un puntero a un entero C, por lo que se puede utilizar el tipo pcint (puntero a un entero en C). El tipo entero (int) puede convertirse a cint y en Pascal se puede shorten la declaración de parámetros que han sido declarados posteriormente tal como sigue: "param_first, param_second: pcint".

Finalmente hay que tener en cuenta que si el valor retornado es void, que por tanto no retorna valor alguno, entonces deberíamos utilizar un procedimiento en lugar de una función.

Ver tambiénhttp://www.freepascal.org/docs-html/ref/refse85.html Funciones Externas de la sección de la guía de referencia.

Publish your bindings on Lazarus-CCR or Free Pascal

You can submit your binding code to the Lazarus CCR repository (see e.g. Using the Lazarus-ccr SVN repository) so others can use (and maintain) it, too. If the bindings are useful for general, cross-platform work, they could be included in the FreePascal distribution as well.

See also


From the Free Pascal wiki, page "Creating bindings for C libraries/es", as it stood on 2020-02-12 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's Licensing page. If you have concerns about this material, message the admin.

Revisión 3, último cambio por WikiArchive .