Sign in Join
Wiki copytemporary

Creating bindings for C libraries/es: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: Creating bindings for C libraries/es

OldNewLine
1+# Introducción
2+
3+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.
4+
5+# H2PAS
6+
7+## Overview
8+
9+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.
10+
11+## Work flow
12+
13+- Fetch the C header files you want to translate.
14+- Create a working directory and give your bindings a name
15+- Create a new h2pas project with the h2pas wizard.
16+- Add the .h files to the project.
17+- Setup the h2pas options.
18+- Run the wizard.
19+- Fix errors by adding text tools and run the wizard again.
20+- When h2pas runs without errors, test compilation and add optional tools to beautify the output.
21+- Write some test programs to test your bindings.
22+- Publish your bindings on lazarus-ccr or the FreePascal website.
23+
24+## Install tools
25+
26+La utilidad h2pas está incluida en cada instalación normal de fpc.
27+
28+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
29+
30+## Fetch C header files
31+
32+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.
33+
34+### Ejemplo: MPICH2
35+
36+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.
37+
38+Puedes encontrar más información al respecto en MPICH for FPC [[MPICH|aquí]].
39+
40+## Create a working directory and name your bindings
41+
42+Create a directory with a useful name. A name should not contain special characters, like spaces, umlaute, points or commas. Copy the .h files.
43+
44+### Example: MPICH2
45+
46+The h2p directory will be used for the pascal files. The h2p/c_sources directory will be used for the .h files.
47+
48+``` bash
49+mkdir -p h2p/c_sources
50+cp mpich2-1.0.3/src/include/*.h h2p/c_sources/
51+```
52+
53+## Create a new h2pas project with the h2pas wizard
54+
55+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.
56+
57+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.
58+
59+### Example: MPICH2
60+
61+Click on "Settings -\> New/Clear settings". Then click on the bottom button "Save settings" and save it as h2p/mpi2.h2p.
62+
63+## Add the .h files to the h2pas-project
64+
65+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.
66+
67+### Example: MPICH2
68+
69+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).
70+
71+## Setup the h2pas options
72+
73+Under "h2pas Options" you can set the parameters for the h2pas program.
74+
75+### Example: MPICH2
76+
77+- Enable -e, -D, -p, -w and disable all others.
78+- The -l library path is "mpich".
79+- Output extension is ".pas"
80+- Output directory is h2pas/, which is the default, so leave it empty
81+
82+## Run the wizard
83+
84+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.
85+
86+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]].
87+
88+### Example: MPICH2
89+
90+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.
91+
92+Add the following to the *Undefines* property:
93+
94+` MPI_INCLUDED`\
95+` MPIO_INCLUDE`\
96+` NEEDS_MPI_FINT`\
97+` MPI_BUILD_PROFILING`\
98+` MPICH_SUPPRESS_PROTOTYPES`\
99+` MPI_Wtime`\
100+` HAVE_MPICH2G2`\
101+` FOO`\
102+` HAVE_PRAGMA_HP_SEC_DEF`
103+
104+Add the following to the *Defines* property:
105+
106+` MPICH2`\
107+` MPICH_NEW_MPIIO`
108+
109+## Writing your own converter tools
110+
111+### Using the tool "Search and replace"
112+
113+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.
114+
115+### Example: rename an identifier Tguint to guint
116+
117+| Property | Value |
118+|-------------|-------------------------------|
119+| Caption | Rename Tguint to guint |
120+| SearchFor | Tguint |
121+| ReplaceWith | guint |
122+| Options | \[trtMatchCase,trtWholeWord\] |
123+
124+### Example: rename several identifiers
125+
126+Rename Tguint to guint, Tgulong to gulong, Tgint to gint:
127+
128+<table>
129+<thead>
130+<tr>
131+<th><p>Property</p></th>
132+<th><p>Value</p></th>
133+</tr>
134+</thead>
135+<tbody>
136+<tr>
137+<td><p>Caption</p></td>
138+<td><p>Rename Tguint to guint</p></td>
139+</tr>
140+<tr>
141+<td><p>SearchFor</p></td>
142+<td><pre><code>T(guint|gint|gulong)</code></pre></td>
143+</tr>
144+<tr>
145+<td><p>ReplaceWith</p></td>
146+<td><p>$1</p></td>
147+</tr>
148+<tr>
149+<td><p>Options</p></td>
150+<td><p>[trtMatchCase,trtWholeWord,trtRegExpr]</p></td>
151+</tr>
152+</tbody>
153+</table>
154+
155+### Example: MPICH2
156+
157+#### Removing Makro MPI_DUP_FN
158+
159+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.
160+
161+Add a 'Search and replace' tool to the before H2Pas tools (at the end) with the following settings:
162+
163+<table>
164+<thead>
165+<tr>
166+<th><p>Property</p></th>
167+<th><p>Value</p></th>
168+</tr>
169+</thead>
170+<tbody>
171+<tr>
172+<td><p>Caption</p></td>
173+<td><p>Remove dummy define MPI_DUP_FN</p></td>
174+</tr>
175+<tr>
176+<td><p>Name</p></td>
177+<td><p>RemoveDummyDefineMPI_DUP_FN</p></td>
178+</tr>
179+<tr>
180+<td><p>SearchFor</p></td>
181+<td><pre><code>^#define\s+MPI_DUP_FN\s+MPIR_Dup_fn</code></pre></td>
182+</tr>
183+<tr>
184+<td><p>ReplaceWith</p></td>
185+<td></td>
186+</tr>
187+<tr>
188+<td><p>Options</p></td>
189+<td><p>[trtMatchCase,trtRegExpr]</p></td>
190+</tr>
191+</tbody>
192+</table>
193+
194+#### Removing typecasts to MPI_DUP_FN
195+
196+Add a 'Search and replace' tool to the before H2Pas tools (at the end) with the following settings:
197+
198+<table>
199+<thead>
200+<tr>
201+<th><p>Property</p></th>
202+<th><p>Value</p></th>
203+</tr>
204+</thead>
205+<tbody>
206+<tr>
207+<td><p>Caption</p></td>
208+<td><p>Remove MPI_DUP_FN Makros</p></td>
209+</tr>
210+<tr>
211+<td><p>Name</p></td>
212+<td><p>Remove_MPI_DUP_Func</p></td>
213+</tr>
214+<tr>
215+<td><p>SearchFor</p></td>
216+<td><pre><code>^#define .*MPI_DUP_FN\).*$</code></pre></td>
217+</tr>
218+<tr>
219+<td><p>ReplaceWith</p></td>
220+<td></td>
221+</tr>
222+<tr>
223+<td><p>Options</p></td>
224+<td><p>[trtMatchCase,trtRegExpr]</p></td>
225+</tr>
226+</tbody>
227+</table>
228+
229+#### Removing functions MPIO_Test, PMPIO_Test, MPIO_Wait and PMPIO_Wait
230+
231+The MPIO_Test, PMPIO_Test, MPIO_Wait and PMPIO_Wait functions are defined twice and redefined via macro. So, the functions can be removed:
232+
233+Add a 'Search and replace' tool to the before H2Pas tools (at the end) with the following settings:
234+
235+<table>
236+<thead>
237+<tr>
238+<th><p>Property</p></th>
239+<th><p>Value</p></th>
240+</tr>
241+</thead>
242+<tbody>
243+<tr>
244+<td><p>Caption</p></td>
245+<td><pre><code>Remove Func P?MPIO_(Test|Wait)</code></pre></td>
246+</tr>
247+<tr>
248+<td><p>Name</p></td>
249+<td><p>RemoveFuncP_MPIO_Test_Wait</p></td>
250+</tr>
251+<tr>
252+<td><p>SearchFor</p></td>
253+<td><pre><code>^int P?MPIO_(Test|Wait).*$</code></pre></td>
254+</tr>
255+<tr>
256+<td><p>ReplaceWith</p></td>
257+<td></td>
258+</tr>
259+<tr>
260+<td><p>Options</p></td>
261+<td><p>[trtMatchCase,trtRegExpr]</p></td>
262+</tr>
263+</tbody>
264+</table>
265+
266+## Improving an existing tool
267+
268+You found a bug and want to fix it or you want to extend one of the above tools. Great!
269+
270+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:
271+
272+``` bash
273+./testh2pastool files/h2pastest.pas
274+```
275+
276+## Writing a custom tool
277+
278+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):
279+
280+``` pascal
281+uses
282+ Classes, ..., IDETextConverter;
283+
284+type
285+ TYourToolClass = class(TCustomTextConverterTool)
286+ public
287+ class function ClassDescription: string; override;
288+ function Execute(aText: TIDETextConverter): TModalResult; override;
289+ end;
290+
291+procedure Register;
292+
293+implementation
294+
295+procedure Register;
296+begin
297+ TextConverterToolClasses.RegisterClass(TYourToolClass);
298+end;
299+```
300+
301+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.
302+
303+## Future work / Missing stuff
304+
305+- A tool to convert static linked procedures to dynamic linked procedure variables
306+- A tool to fix functions without parameter names by searching the .c files. This could also add a comment, in which c file it found the function.
307+- Find missing identifiers and let the user check which one to comment or replace with base types.
308+- Create a list of macro functions, which were half translated.
309+
310+C:
311+
312+``` c
313+typedef struct page
314+{
315+ u32int present : 1; // Page present in memory
316+ u32int rw : 1; // Read-only if clear, readwrite if set
317+ u32int user : 1; // Supervisor level only if clear
318+ u32int accessed : 1; // Has the page been accessed since last refresh?
319+ u32int dirty : 1; // Has the page been written to since last refresh?
320+ u32int unused : 7; // Amalgamation of unused and reserved bits
321+ u32int frame : 20; // Frame address (shifted right 12 bits)
322+} page_t;
323+```
324+
325+Pascal:
326+
327+``` pascal
328+type
329+ Unsigned_7 = 0 .. (1 shl 7) - 1;
330+ Unsigned_20 = 0 .. (1 shl 20) - 1;
331+
332+type
333+ page_t = bitpacked record
334+ present : boolean;
335+ rw : boolean;
336+ user : boolean;
337+ accessed : boolean;
338+ dirty : boolean;
339+ unused : Unsigned_7;
340+ frame : Unsigned_20;
341+ end;
342+```
343+
344+# Convert C to Pascal with lua script
345+
346+I'm impressed by the power of the lua scripting language and how easy it is to do funny things with it.
347+
348+``` lua
349+--
350+-- Please send any improvement to mingodadATgmailDOTcom.
351+--
352+
353+inFileName = arg[1] or 'g:\\tmp\\plua\\clua\\lvm.c'
354+
355+fh = assert(io.open(inFileName))
356+cbody = fh:read('*a')
357+fh:close()
358+
359+pasbody = cbody
360+
361+function split(str, pat)
362+ local t = {} -- NOTE: use {n = 0} in Lua-5.0
363+ local fpat = '(.-)' .. pat
364+ local last_end = 1
365+ local s, e, cap = str:find(fpat, 1)
366+ while s do
367+ if s ~= 1 or cap ~= '' then
368+ table.insert(t, cap)
369+ end
370+ last_end = e+1
371+ s, e, cap = str:find(fpat, last_end)
372+ end
373+ if last_end <= #str then
374+ cap = str:sub(last_end)
375+ table.insert(t, cap)
376+ end
377+ return t
378+end
379+
380+function trim(s)
381+ -- from PiL2 20.4
382+ return (s:gsub('^%s*(.-)%s*$', '%1'))
383+end
384+
385+function trimChars(str, c1, c2)
386+ return str:gsub('^%' .. c1 .. '*(.-)%' .. c2 .. '*$', '%1')
387+end
388+
389+function getPascalType(cType)
390+ if cType == 'void' then
391+ return 'pointer'
392+ elseif cType == 'int' then
393+ return 'integer'
394+ elseif cType == 'short' then
395+ return 'Shortint'
396+ elseif cType == 'define' then
397+ return 'is_define'
398+ else
399+ return cType
400+ end
401+end
402+
403+--check var name and type
404+--if var starts with a capital letter
405+-- add 'T' informt of it
406+--if var is preceded by * then add a 'P' for type
407+
408+function checkVarType(aType, aVar)
409+ aType = getPascalType(aType)
410+ aType = aType:gsub('^(%u)', 'T%1')
411+ aVar = aVar:gsub('^(%*)', function(m)
412+ aType = 'P' .. aType
413+ return ''
414+ end)
415+ aVar = aVar:gsub('(%[[^]]+%])', function(m)
416+ aType = 'array' .. m .. ' of ' .. aType
417+ return ''
418+ end)
419+ return aType, aVar
420+end
421+
422+function varDeclaration(aType, aVar)
423+ aType, aVar = checkVarType(aType, aVar)
424+ return aVar .. ' : ' .. aType
425+end
426+
427+function checkDeclarationOrGoto(m1, m2, m3)
428+ if m2 ~= 'goto' then
429+ return '\n' .. m1 .. varDeclaration(m2, m3) .. ';'
430+ else
431+ return '\n' .. m1 .. m2 .. ' ' .. m3 .. ';'
432+ end
433+end
434+
435+function addProcFunc(mtype, mname, mparams, mbody)
436+ local str, isFunc, rType, k, v
437+ if mtype == 'void' then
438+ str = '\nprocedure '
439+ isFunc = false
440+ else
441+ str = '\nfunction '
442+ isFunc = true
443+ end
444+ mtype, mname = checkVarType(mtype, mname)
445+ local vparams = split(trimChars(mparams, '(',')'), ',')
446+ str = str .. mname .. '('
447+
448+ local tparams = {}
449+ for k, v in pairs(vparams) do
450+ local vparam = split(trim(v), ' ')
451+ if #vparam > 2 then
452+ table.insert(tparams, vparam[1] .. ' ' .. varDeclaration(vparam[2], vparam[3]))
453+ else
454+ --print(vparam[1], vparam[2])
455+ table.insert(tparams, varDeclaration(vparam[1], vparam[2] or 'is_define'))
456+ end
457+ end
458+
459+ str = str .. table.concat(tparams, '; ') .. ')'
460+ if isFunc then
461+ if mtype == 'int' then
462+ rType = 'integer'
463+ else
464+ rType = mtype
465+ end
466+ str = str .. ':' .. rType
467+ end
468+
469+ if mbody then
470+ local tblLabels = {}
471+ for v in mbody:gmatch('\n[\t ]*([%w_]+)[\t ]*:[^=]') do
472+ if v ~= 'default' then
473+ table.insert(tblLabels, v)
474+ end
475+ end
476+
477+ if #tblLabels > 0 then
478+ str = str .. ';\nlabel\n '.. table.concat(tblLabels, ', ')
479+ end
480+
481+ local declarations = {}
482+
483+ mbody = mbody:gsub('(\n[\t ]*)const[\t ]+([%w_]+)[\t ]+(%*?[%w_]+)[\t ]*:%=[\t ]*([^;\n]+;)','%1 %2 %3 := %4')
484+
485+ mbody = mbody:gsub('(\n[\t ]*)([%w_]+)[\t ]+(%*?[%w_]+)[\t ]*:%=[\t ]*([^;\n]+;)', function(m1, m2, m3, m4)
486+ table.insert(declarations, varDeclaration(m2, m3) .. ';' )
487+ return m1 .. m3 .. ' := ' .. m4
488+ end)
489+
490+ mbody = mbody:gsub('\n[\t ]*const[\t ]*([%w_]+)[\t ]+(%*?[%w_]+)[\t ]*;', function(m1,m2)
491+ table.insert(declarations, varDeclaration(m1, m2) .. ';')
492+ return ''
493+ end)
494+
495+ mbody = mbody:gsub('\n([\t ]*)([%w_]+)[\t ]+(%*?[%w_ ,]+);', function(m1,m2,m3)
496+ if m2 ~= 'goto' then
497+ table.insert(declarations, varDeclaration(m2, m3) .. ';')
498+ return ''
499+ else
500+ return '\n' .. m1 .. m2 .. ' ' .. m3 .. ';'
501+ end
502+ end)
503+
504+ if #declarations > 0 then
505+ local unique_decl = {}
506+ table.sort(declarations)
507+ local lastV = ''
508+ local k2, v2
509+ for k2,v2 in pairs(declarations) do
510+ if v2 ~= lastV then
511+ lastV = v2
512+ table.insert(unique_decl, v2)
513+ end
514+ end
515+ str = str .. ';\nvar\n '.. table.concat(unique_decl, '\n ') .. '\n'
516+ else
517+ str = str .. ';\n'
518+ end
519+
520+ str = str .. 'begin' .. trimChars(mbody, '{','}') .. 'end'
521+ end
522+ return str .. ';'
523+end
524+
525+--*************************************
526+--
527+-- Order of substitutions are important
528+--
529+--*************************************
530+
531+--=assignements statements
532+ pasbody = pasbody:gsub("([\t ]*)([%w_]+)[\t ]*=[\t ]*([^=;\n]+);", '%1%2 := %3;')
533+
534+--return
535+ pasbody = pasbody:gsub('([\t ]*)return[\t ]+([^;]+);', '%1exit(%2);')
536+
537+--NULL
538+ pasbody = pasbody:gsub('([^%w]+)NULL([^%w]+)', '%1nil%2')
539+
540+--void functions to procedure declaratios
541+ pasbody = pasbody:gsub('([%w_*]+)[\t ]+([%w_*]+)[\t ]*(%b())[%s]*(%b{})', addProcFunc)
542+ --pasbody = pasbody:gsub('\n([%w_]+)%s+([^%s]+)%s*(%b());', addProcFunc)
543+
544+--const/static
545+ tblLeaveCommented = {
546+ 'const', 'static'
547+ }
548+ for k,v in pairs(tblLeaveCommented) do
549+ pasbody = pasbody:gsub('([\t ]*)(' .. v .. ')([\t ]*)', '%1{%2}%3')
550+ end
551+
552+--defines to const
553+ isConstEmmited = false
554+ function addConst(m1,m2)
555+ local sm
556+ sm = ''
557+ if not isConstEmmited then
558+ isConstEmmited = true
559+ sm = '\nconst'
560+ end
561+ return sm .. '\n\t' .. m1 .. ' = ' .. m2 .. ';'
562+ end
563+
564+ isConstEmmited = false
565+ pasbody = pasbody:gsub('\n#define%s+([^%s]+)%s+(%b())', '\nconst %1 = %2;')
566+ isConstEmmited = false
567+ pasbody = pasbody:gsub('\n#define%s+([^%s]+)[ \t]+([^%s]+)', '\nconst %1 = %2;')
568+
569+--includes
570+ tblUses = {}
571+ pasbody = pasbody:gsub('\n#[ \t]*(include)%s+([^\n]+)', function(m1,m2)
572+ for w in m2:gmatch('"([^.]+)%..+"') do
573+ table.insert(tblUses, w)
574+ end
575+ return '\n//#' .. m1 .. ' ' .. m2
576+ end)
577+ pasbody = '//uses ' .. table.concat(tblUses, ', ') .. ';\n' .. pasbody
578+
579+--defines to compiler directives
580+ pasbody = pasbody:gsub('\n#([^\n]+)', '\n{$%1}')
581+
582+--comments
583+ pasbody = pasbody:gsub('\n#', '\n//#')
584+ pasbody = pasbody:gsub('/%*', '(*')
585+ pasbody = pasbody:gsub('%*/', '*)')
586+
587+--declarations
588+ pasbody = pasbody:gsub('\n([\t ]*)([%w_]+)[\t ]+([%w_ ,]+);', checkDeclarationOrGoto)
589+
590+--structs
591+ function parseStruct(m1, m2)
592+ return '\n' .. m1 .. ' = record\n' .. trimChars(m2, '{', '}') .. '\nend'
593+ end
594+ pasbody = pasbody:gsub('\n[\t ]*struct[\t ]+([%w_]+)[\t ]*(%b{})', parseStruct)
595+
596+--if statements
597+ parseStatementCalled = false
598+
599+ function parseStatement(mSpace, mStatement, mCond, mBody)
600+ parseStatementCalled = true
601+ local vStatement
602+ if mStatement == 'if' then
603+ vStatement = mStatement .. ' ' .. mCond .. ' then'
604+ elseif mStatement == 'else' then
605+ return mSpace .. mStatement .. ' begin' .. trimChars(mCond, '{', '}') .. ' end;'
606+ elseif (mStatement == 'while') then
607+ vStatement = mStatement .. ' ' .. mCond .. ' do'
608+ elseif (mStatement == 'for') then
609+ vStatement = mStatement .. ' ' .. mCond .. ' do'
610+ --local forArgs = split(trimChars(mCond, '(', ')'), ';')
611+ --return mSpace .. (forArgs[1] or '') .. ';' .. mSpace ..'while (' ..
612+ -- (forArgs[2] or 'true') .. ') do' .. mSpace .. 'begin' .. mSpace .. trimChars(mBody, '{', '}') .. '\n' .. mSpace ..
613+ -- (forArgs[3] or '') .. ';\n' .. mSpace .. 'end;'
614+ else
615+ vStatement = mStatement .. ' ' .. mCond .. '\n'
616+ end
617+ return mSpace .. vStatement .. ' begin' .. trimChars(mBody, '{', '}') .. 'end;'
618+ end
619+
620+ -- for nested blocks
621+ parseStatementCalled = true
622+ while(parseStatementCalled) do
623+ parseStatementCalled = false
624+ pasbody = pasbody:gsub('([^%w_])(if)[\t ]*(%b())[\t ]*(%b{})', parseStatement)
625+ end
626+
627+ -- else
628+ pasbody = pasbody:gsub('([\t ]+)(else)[\t ]*(%b{})', parseStatement)
629+
630+ pasbody = pasbody:gsub("([^%w_])if[\t ]*(%b())[\t ]*([^;\n]+);", '%1if %2 then %3;')
631+
632+--?: short if
633+ pasbody = pasbody:gsub("([^?])?([^:;\n]):[\t ]*([^%s;]+);", ' iff(%1, %2, %3) ')
634+
635+--while loop
636+ -- for nested blocks
637+ parseStatementCalled = true
638+ while(parseStatementCalled) do
639+ parseStatementCalled = false
640+ pasbody = pasbody:gsub('([^%w_])(while)[\t ]*(%b())[\t ]*(%b{})', parseStatement)
641+ end
642+
643+--for loop
644+ -- for nested blocks
645+ parseStatementCalled = true
646+ while(parseStatementCalled) do
647+ parseStatementCalled = false
648+ pasbody = pasbody:gsub('([^%w_])(for)[\t ]*(%b())%s*(%b{})', parseStatement)
649+ end
650+
651+--do/while
652+ -- for nested blocks
653+ parseStatementCalled = true
654+ while(parseStatementCalled) do
655+ parseStatementCalled = false
656+ pasbody = pasbody:gsub("([^%w_])do[\t ]*(%b{})[\t ]*while([^;\n]+);",
657+ function(m1, m2, m3)
658+ parseStatementCalled = true
659+ return m1 .. 'repeat ' .. trimChars(m2, '{', '}') .. 'until not' .. m3
660+ end)
661+ end
662+
663+--switch/case statements
664+
665+ --begin/end enclose blocks around
666+ -- for nested blocks
667+ parseStatementCalled = true
668+ while(parseStatementCalled) do
669+ parseStatementCalled = false
670+ pasbody = pasbody:gsub("([^%w_]case[\t ]+[^:]+)[\t ]*:[%s]*(%b{})",
671+ function(m1, m2, m3)
672+ parseStatementCalled = true
673+ return m1 .. ': begin' .. trimChars(m2, '{', '}') .. 'end;'
674+ end)
675+ pasbody = pasbody:gsub("([^%w_]default[\t ]*):[%s]*(%b{})",
676+ function(m1, m2, m3)
677+ parseStatementCalled = true
678+ return m1 .. ': begin' .. trimChars(m2, '{', '}') .. 'end;'
679+ end)
680+ end
681+
682+ --breaks remove
683+ pasbody = pasbody:gsub("([^%w_]case[\t ]+[^:]+)[\t ]*:[%s]*(.-)break;", '%1: %2')
684+
685+ --case
686+ pasbody = pasbody:gsub("([^%w_])case[\t ]+([^:]+):", '%1%2:')
687+
688+ -- for nested blocks
689+ parseStatementCalled = true
690+ while(parseStatementCalled) do
691+ parseStatementCalled = false
692+ pasbody = pasbody:gsub("([^%w_])switch[\t ]*(%b())[\t ]*(%b{})",
693+ function(m1, m2, m3)
694+ parseStatementCalled = true
695+ return m1 .. 'case ' .. m2 .. ' of ' .. trimChars(m3, '{', '}') .. 'end;'
696+ end)
697+ end
698+
699+ --pasbody = pasbody:gsub("([\t ]*)case[\t ]+('.')[\t ]*:\n", '%1%2:\n')
700+ --pasbody = pasbody:gsub("([\t ]*)case[\t ]+(%d+)[\t ]*:\n", '%1%2:\n')
701+ --pasbody = pasbody:gsub("([\t ]*)(case)[\t ]+('.')[\t ]*:[\t ]*(%b{})", '%1%2 : %3')
702+
703+--pre/pos increments
704+ -- (n)++ -> PosInc(n)
705+ pasbody = pasbody:gsub("(%([^%s+]+%))%+%+", 'PosInc%1')
706+ -- n*++ -> PosInc(n*)
707+ pasbody = pasbody:gsub("([^%s*+]+)%+%+", 'PosInc(%1)')
708+ -- ++(n) -> PreInc(n)
709+ pasbody = pasbody:gsub("%+%+(%([^%s;+]+%))", 'PreInc%1')
710+ -- ++*n -> PreInc(n)
711+ pasbody = pasbody:gsub("%+%+([^%s*;+]+)", 'PreInc(%1)')
712+
713+ -- (n)--
714+ pasbody = pasbody:gsub("(%([^%s-]+%))%-%-", 'PosDec%1')
715+ -- n*--
716+ pasbody = pasbody:gsub("([^%s*-]+)%-%-", 'PosDec(%1)')
717+ -- --(n)
718+ pasbody = pasbody:gsub("%-%-(%([^%s;-]+%))", 'PreDec%1')
719+ -- --n
720+ pasbody = pasbody:gsub("%-%-([^%s;-]+)", 'PreDec(%1)')
721+
722+--boolean operators
723+ tblBooleanOperators = {
724+ {'||', 'or'},
725+ {'&&', 'and'},
726+ }
727+ for k,v in pairs(tblBooleanOperators) do
728+ pasbody = pasbody:gsub('([\t ]*)' .. v[1] .. '([\t ]*)', '%1) ' .. v[2] .. ' (%2')
729+ end
730+
731+--pointers
732+ pasbody = pasbody:gsub("%->", '.')
733+ pasbody = pasbody:gsub("%(%*([%w_]+)", '(%1^')
734+ pasbody = pasbody:gsub("%*%*([%w_]+)", '%1^^')
735+ pasbody = pasbody:gsub("&([%w_]+)", '@%1')
736+ pasbody = pasbody:gsub("%*([%w_]+)[\t ]*:[\t ]*([%w_]+);", '%1 : ^%2;')
737+ pasbody = pasbody:gsub("%^char", 'pchar')
738+ --pasbody = pasbody:gsub("%*([%w_]+)[\t ]*:=[\t ]*([^;\n]+);", '%1^ := %2;')
739+
740+--bit operators
741+ tblBitOperators = {
742+ {'<<', 'shl'},
743+ {'>>', 'shr'},
744+ {'|', 'or'},
745+ {'&', 'and'},
746+ {'~', 'not'},
747+ }
748+
749+ for k,v in pairs(tblBitOperators) do
750+ pasbody = pasbody:gsub('([\t ]*)' .. v[1] .. '([\t ]*)', '%1 ' .. v[2] .. ' %2')
751+ end
752+
753+--==eq operator
754+ pasbody = pasbody:gsub("([\t ]*)%=%=([\t ]*)", '%1 = %2')
755+
756+--!= not eq operator
757+ pasbody = pasbody:gsub("([\t ]*)!%=([\t ]*)", '%1 <> %2')
758+
759+--! unary
760+ pasbody = pasbody:gsub('([\t ]*)!([\t ]*)', '%1 not %2')
761+
762+--blocks
763+ --pasbody = pasbody:gsub("(%b{})", function(m1) return 'begin' .. trimChars(m1, '{', '}') .. 'end;' end)
764+ --pasbody = pasbody:gsub("(%b{})", function(m1) return 'begin' .. trimChars(m1, '{', '}') .. 'end;' end)
765+
766+--strings and escaped characters
767+ tblEscapedCahrs = {
768+ {'\\"', '"'},
769+ {'\\\\', '\\'},
770+ {'\\t', "'#9"},
771+ {'\\n', "'#10'"},
772+ {'\\f', "'#12'"},
773+ }
774+ pasbody = pasbody:gsub('%b""', function(m1)
775+ return "'" .. trimChars(m1:gsub("'", "''") , '"', '"') .. "'"
776+ end)
777+ for k,v in pairs(tblEscapedCahrs) do
778+ pasbody = pasbody:gsub(v[1], v[2])
779+ end
780+
781+--common functions/procedure
782+ tblOrigFuncNewFunc = {
783+ {'printf', 'format'},
784+ {'malloc', 'GetMem'},
785+ {'free', 'Dispose'},
786+ {'memcpy', 'Move'},
787+ }
788+
789+ for k,v in pairs(tblOrigFuncNewFunc) do
790+ pasbody = pasbody:gsub('([\t ]+)' .. v[1] .. '%(', '%1' .. v[2] .. '(')
791+ end
792+
793+--typedefs
794+ isTypeEmmited = false
795+ function addType(m1,m2)
796+ local sm
797+ sm = ''
798+ if not isTypeEmmited then
799+ isTypeEmmited = true
800+ sm = '\ntype'
801+ end
802+ return sm .. '\n\t' .. m2 .. ' = ' .. m1 .. ';'
803+ end
804+ --pasbody = pasbody:gsub('\ntypedef%s+struct%s+([^%s]+)%s+([^;]+)', addType)
805+ --pasbody = pasbody:gsub('\ntypedef%s+([^%s]+)%s+([^;]-)', addType)
806+ pasbody = pasbody:gsub('\n[\t ]*typedef[\t ]+struct[\t ]+([%w_]+)[\t ]+([^;]+)', '\n%2 = %1')
807+ pasbody = pasbody:gsub('\n[\t ]*typedef[\t ]+struct%s+(%b{})%s+([%w_]+);',
808+ function(m1, m2) return m2 .. ' = record' .. trimChars(m1, '{', '}') .. 'end;' end)
809+
810+-- print the results
811+print(pasbody)
812+```
813+
814+Here is an example of a c file converted.
815+
816+``` pascal
817+//uses lua, llimits, lmem, lstate, lzio;
818+(*
819+** $Id: lzio.c,v 1.31.1.1 2007/12/27 13:02:25 roberto Exp $
820+** a generic input stream interface
821+** See Copyright Notice in lua.h
822+*)
823+
824+//#include <string.h>
825+
826+{$define lzio_c}
827+{$define LUA_CORE}
828+
829+//#include 'lua.h'
830+
831+//#include 'llimits.h'
832+//#include 'lmem.h'
833+//#include 'lstate.h'
834+//#include 'lzio.h'
835+
836+function luaZ_fill(z : PTZIO):integer;
837+var
838+ L : Plua_State;
839+ buff : Pchar;
840+ size : size_t;
841+begin
842+ *L := z.L;
843+ lua_unlock(L);
844+ buff := z.reader(L, z.data, @size);
845+ lua_lock(L);
846+ if (buff = nil ) or ( size = 0) then exit(EOZ);
847+ z.n := size - 1;
848+ z.p := buff;
849+ exit(char2int(PosInc^((z.p))));
850+end;
851+
852+function luaZ_lookahead(z : PTZIO):integer;
853+begin
854+ if (z.n = 0) then begin
855+ if (luaZ_fill(z) = EOZ)
856+ exit(EOZ);
857+ else begin
858+ PosInc(z.n); (* luaZ_fill removed first byte; put back it *)
859+ z-PosDec(>p);
860+ end;
861+ end;
862+ exit(char2int(z^.p));
863+end;
864+
865+procedure luaZ_init(L : Plua_State; z : PTZIO; reader : lua_Reader; data : Ppointer);
866+begin
867+ z.L := L;
868+ z.reader := reader;
869+ z.data := data;
870+ z.n := 0;
871+ z.p := nil;
872+end;
873+
874+(* --------------------------------------------------------------- read --- *)
875+
876+function luaZ_read(z : PTZIO; b : Ppointer; n : size_t):size_t;
877+var
878+ m : size_t;
879+begin
880+ while (n) do begin
881+ if (luaZ_lookahead(z) = EOZ)
882+ exit(n); (* exit(number of missing bytes *)
883+ m = (n <= z.n) ? n : z.n); (* min. between n and z.n *)
884+ Move(b, z.p, m);
885+ z.n -= m;
886+ z.p += m;
887+ b := (char *)b + m;
888+ n -= m;
889+ end;
890+ exit(0);
891+end;
892+
893+(* ------------------------------------------------------------------------ *)
894+
895+function luaZ_openspace(L : Plua_State; buff : PTMbuffer; n : size_t):Pchar;
896+begin
897+ if (n > buff.buffsize) then begin
898+ if (n < LUA_MINBUFFER) then n := LUA_MINBUFFER;
899+ luaZ_resizebuffer(L, buff, n);
900+ end;
901+ exit(buff.buffer);
902+end;
903+```
904+
905+# Conversión manual de las cabeceras de C a Pascal
906+
907+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.
908+
909+## Convirtiendo constantes
910+
911+C:
912+
913+``` c
914+#define SOME_CONSTANT 5
915+#define ANOTHER_CONSTANT 6
916+```
917+
918+Pascal:
919+
920+``` pascal
921+const
922+ SOME_CONSTANT = 5;
923+ ANOTHER_CONSTANT = 6;
924+```
925+
926+## Convirtiendo rutinas exportadas
927+
928+Las rutinas más básicas se pueden traducir como sigue:
929+
930+C:
931+
932+``` c
933+int somekind_of_method(int* param_first, int* param_second)
934+void another_method(char*)
935+```
936+
937+Pascal:
938+
939+``` pascal
940+uses ctypes;
941+
942+const
943+ MyLib = 'mylib';
944+
945+function somekind_of_method(param_first, param_second: pcint): cint; external MyLib;
946+procedure another_method_renamed(param1: PChar); external MyLib name 'another_method';
947+```
948+
949+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.
950+
951+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".
952+
953+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.
954+
955+Ver también[[http://www.freepascal.org/docs-html/ref/refse85.html Funciones Externas]] de la sección de la guía de referencia.
956+
957+# Publish your bindings on Lazarus-CCR or Free Pascal
958+
959+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.
960+
961+# See also
962+
963+- [1](http://gd.tuwien.ac.at/languages/pascal/fpc/docs-pdf/CinFreePascal.pdf) Short tutorial that describes the process of using C and C++ code in Free Pascal, including writing of wrapper code.
964+- \[<https://github.com/williamhunter/pascal-bindings-for-c/blob/master/docs/Creating%20Pascal%20bindings%20for%20C%20(v1.0>).pdf\] Another tutorial showing how to use object-code and libraries created in C.
965+- [[Common problems when converting C header files]]
966+- [[Pascal for C users]]
967+- [[Lazarus/FPC Libraries]]
968+- [[SWIG]]
969+
970+---
971+*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](/wiki-contact).*

Back to history