Sign in Join
Wiki copytemporary

Creating bindings for C libraries: what changed

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

Title: Creating bindings for C libraries

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