Creating Bindings For C Libraries - Free Pascal Wiki
Creating Bindings For C Libraries - Free Pascal Wiki
│ English (en) │ español (es) │ français (fr) │ ⽇本語 (ja) │ русский (ru) │
Contents
▪ 1 Introduction
▪ 2 H2PAS
▪ 2.1 Overview
▪ 2.2 Work flow
▪ 2.3 Install tools
▪ 2.4 Fetch C header files
▪ 2.4.1 Example: MPICH2
▪ 2.5 Create a working directory and name your bindings
▪ 2.5.1 Example: MPICH2
▪ 2.6 Create a new h2pas project with the h2pas wizard
▪ 2.6.1 Example: MPICH2
▪ 2.7 Add the .h files to the h2pas-project
▪ 2.7.1 Example: MPICH2
▪ 2.8 Setup the h2pas options
▪ 2.8.1 Example: MPICH2
▪ 2.9 Run the wizard
▪ 2.9.1 Example: MPICH2
▪ 2.10 Writing your own converter tools
▪ 2.10.1 Using the tool "Search and replace"
▪ 2.10.1.1 Option: trtRegExpr
▪ 2.10.2 Example: rename an identifier Tguint to guint
▪ 2.10.3 Example: rename several identifiers
▪ 2.10.4 Example: MPICH2
▪ 2.10.4.1 Removing Makro MPI_DUP_FN
▪ 2.10.4.2 Removing typecasts to MPI_DUP_FN
▪ 2.10.4.3 Removing functions MPIO_Test, PMPIO_Test,
MPIO_Wait and PMPIO_Wait
▪ 2.11 Improving an existing tool
▪ 2.12 Writing a custom tool
▪ 2.13 Future work / Missing stuff
▪ 3 Convert C to Pascal with lua script
▪ 4 Manual conversion of C headers to Pascal
▪ 4.1 Converting constants
▪ 4.2 Converting exported routines
▪ 5 Publish your bindings on Lazarus-CCR or Free Pascal
▪ 6 See also
Introduction
This page describes how to create Pascal bindings for C libraries. In order to use C
libraries in Pascal, you have to create a pascal translation for every C function, type
and variable. To do that one can either utilized an automated tool, like h2pas or do a
manual headers conversion
H2PAS
Overview
The H2Pas tool that automatically translates many common C things. A GUI for
Lazarus using h2pas and other tools additionally automates the creation. It helps to
create a rule set, which can be used to update the bindings. So the next version of the c
library can be converted far more easier. A nice feature of the h2pas wizard is that it
uses temporary files, so the c header files remain unchanged.
Work flow
▪ Fetch the C header files you want to translate.
▪ Create a working directory and give your bindings a name
▪ Create a new h2pas project with the h2pas wizard.
▪ Add the .h files to the project.
▪ Setup the h2pas options.
▪ Run the wizard.
▪ Fix errors by adding text tools and run the wizard again.
▪ When h2pas runs without errors, test compilation and add optional tools to
beautify the output.
▪ Write some test programs to test your bindings.
▪ Publish your bindings on lazarus-ccr or the FreePascal website.
Install tools
The h2pas tool comes with every normal fpc installation.
Example: MPICH2
You can find more information about MPICH for FPC here.
Example: MPICH2
The h2p directory will be used for the pascal files. The h2p/c_sources directory will be
used for the .h files.
mkdir -p h2p/c_sources
cp mpich2-1.0.3/src/include/*.h h2p/c_sources/
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.
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).
Example: MPICH2
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.
MPI_INCLUDED
MPIO_INCLUDE
NEEDS_MPI_FINT
MPI_BUILD_PROFILING
MPICH_SUPPRESS_PROTOTYPES
MPI_Wtime
HAVE_MPICH2G2
FOO
HAVE_PRAGMA_HP_SEC_DEF
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.
Option: trtRegExpr
If you set this option to True, it means that you want to use regular expressions. This
allows for plenty of possibilities to adapt and/or beautify the source code by
metacharacters and quantifiers. A list of the metacharacters and quantifiers and
examples are found in this article: IDE_regular_expressions.
Property Value
SearchFor Tguint
ReplaceWith guint
Options [trtMatchCase,trtWholeWord]
Property Value
SearchFor T(guint|gint|gulong)
ReplaceWith $1
Options [trtMatchCase,trtWholeWord,trtRegExpr]
Example: MPICH2
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
Name RemoveDummyDefineMPI_DUP_FN
SearchFor ^#define\s+MPI_DUP_FN\s+MPIR_Dup_fn
ReplaceWith
Options [trtMatchCase,trtRegExpr]
Add a 'Search and replace' tool to the before H2Pas tools (at the end) with the following
settings:
Property Value
Name Remove_MPI_DUP_Func
ReplaceWith
Options [trtMatchCase,trtRegExpr]
Add a 'Search and replace' tool to the before H2Pas tools (at the end) with the following
settings:
Property Value
Name RemoveFuncP_MPIO_Test_Wait
ReplaceWith
Options [trtMatchCase,trtRegExpr]
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:
./testh2pastool files/h2pastest.pas
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.
C:
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;
--
-- Please send any improvement to mingodadATgmailDOTcom.
--
fh = assert(io.open(inFileName))
cbody = fh:read('*a')
fh:close()
pasbody = cbody
function trim(s)
-- from PiL2 20.4
return (s:gsub('^%s*(.-)%s*$', '%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
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
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
local declarations = {}
--*************************************
--
-- 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')
--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
--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
-- else
pasbody = pasbody:gsub('([\t ]+)(else)[\t ]*(%b{})', parseStatement)
--?: 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
--breaks remove
pasbody = pasbody:gsub("([^%w_]case[\t ]+[^:]+)[\t ]*:[%s]*(.-)break;", '%1: %2')
--case
pasbody = pasbody:gsub("([^%w_])case[\t ]+([^:]+):", '%1%2:')
--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'},
}
--==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)
--common functions/procedure
tblOrigFuncNewFunc = {
{'printf', 'format'},
{'malloc', 'GetMem'},
{'free', 'Dispose'},
{'memcpy', 'Move'},
}
--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)
//#include <string.h>
{$define lzio_c}
{$define LUA_CORE}
//#include 'lua.h'
//#include 'llimits.h'
//#include 'lmem.h'
//#include 'lstate.h'
//#include 'lzio.h'
(* ------------------------------------------------------------------------ *)
Converting constants
C:
#define SOME_CONSTANT 5
#define ANOTHER_CONSTANT 6
Pascal:
const
SOME_CONSTANT = 5;
ANOTHER_CONSTANT = 6;
Pascal:
uses ctypes;
const
MyLib = 'mylib';
Note that one should use the ctypes unit together with the types that it declares to
convert C headers instead of trying to use the basic Pascal types directly. This ensures
that the bindings are correct in all platforms, as the C types might have different sizes
on different architectures and operating systems.
Also, note that pointers to basic C types can be converted to types declared in ctypes,
for example, int* is a pointer to a C int, so we can use the type pcint (pointer to a c
integer). The C type int can be converted to cint and in Pascal we can shorten the
declaration of parameters which are declared one after as folows: "param_first,
param_second: pcint".
Finally, note that if the return value is void, then we should use a procedure instead of
function.