Sign in Join
Wiki copytemporary

Creating bindings for C libraries/ru: what changed

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

OldNewLine
11# Введение
22
33Эта статья описывает, как подключить и использовать С-библиотеки в языке Паскаль. Для использования библиотек, написанных на языке С вы должны преобразовать все типы данных, функции и переменные в библиотеках в соответствии стандартам языка Паскаль. Выполнить данную операцию можно используя утилиту h2pas (или аналогичное ПО), либо вручную, переписав код.
44
55# H2PAS
66
77## Обзор
88
99[[H2Pas/ru|H2Pas]] это инструмент, который автоматически переводит C библиотеки. GUI для Lazarus, использующее h2pas и другие инструменты автоматизируют процесс. Он помогает создать набор правил, которые можно использовать чтобы обновить порт, отчего после обновления C библиотеки перевести ее будет намного проще. Приятной особенностью является то, что Мастер h2pas использует временные файлы, поэтому файлы заголовков остаются нетронутыми.
1010
1111## Процесс работы
1212
1313- Найдите C заголовки, которые вы хотите перевести.
1414- Создайте папку для работы и придумайте имя для портированной библиотеки.
1515- Создайте проект h2pas используя Мастер h2pas.
1616- Добавьте заголовочные файлы в проект.
1717- Настройте проект h2pas.
1818- Запустите мастер.
1919- Исправьте ошибки и запустите мастер еще раз.
2020- Когда h2pas запустится без ошибок, протестируйте сборку и отформатируйте код.
2121- Напишите пару программ для тестов.
2222- Опубликуйте порт на lazarus-ccr или на сайте FreePascal.
2323
2424## Установка инструментов
2525
2626Инструмент h2pas поставляется с каждой обычной установкой fpc
2727
2828Установите пакет h2paswizard в IDE Lazarus. Откройте "Package-\>Install/Uninstall Packages", выберите в правом списке пакет H2PasWizard и щёлкните 'Install selection', потом - 'Save and rebuild IDE'. Перезапустите IDE и у вас появится новый пункт меню: Tools -\> h2pas
2929
3030## Получение заголовочных файлов C
3131
3232Заголовочный файл C имеет расширение .h и описывает интерфейс C библиотеки. Обычно их не предоставляют вместе с библиотекой. Для этого нужно скачать исходный код или пакет разработчика библиотеки. Например заголовочные файлы библиотеки gtk находятся в gtk+-devel.
3333
3434### Пример: MPICH2
3535
3636Скачайте mpich2-1.0.3.tar.gz здесь: <http://www-unix.mcs.anl.gov/mpi/mpich2/> и распакуйте его. Файлы .h находятся в mpich2-1.0.3/src/include.
3737
3838Больше информации о MPICH для FPC вы можете найти [[MPICH|здесь]].
3939
4040## Создание папки для работы, выбор имени
4141
4242Создайте папку с подходящим названием. Имя не должно содержать специальных символов: пробелов, запятых и т.д. Скопируйте файлы .h в эту папку.
4343
4444### Пример: MPICH2
4545
4646Мы будем использовать папку h2p для файлов Pascal. h2p/c_sources будет использоваться для файлов .h.
4747
4848``` bash
4949mkdir -p h2p/c_sources
5050cp mpich2-1.0.3/src/include/*.h h2p/c_sources/
5151```
5252
5353## Создание проекта h2pas с помощью мастера
5454
5555Откройте мастер h2pas: "Tools -\> h2pas". Вы сможете переключаться между окном мастера и другими окнами IDE. Ваш последний проект h2pas откроется автоматически. Чтобы создать новый проект, нажмите "Settings -\> New/Clear settings". Потом нажмите на кнопку "Save settings" и выберете имя файла.
5656
5757Заметка: Если вы открываете мастер в первый раз, то вам нужно будет в меню настроек задать путь к h2pas.exe. Конвертер h2pas находится там-же где и компилятор FreePascal (fpc.exe), если вы скачали FPC вместе с Lazarus, то смотрите в ...\Lazarus\fpc\*version*\bin. Сохраните настройки чтоб не повторять те же действия снова.
5858
5959### Пример: MPICH2
6060
6161Нажмите "Settings -\> New/Clear settings".Потом нажмите на кнопку "Save settings" и сохраните как h2p/mpi2.h2p.
6262
6363## Add the .h files to the h2pas-project
6464
6565In 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.
6666
6767### Example: MPICH2
6868
6969Click 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).
7070
7171## Setup the h2pas options
7272
7373Under "h2pas Options" you can set the parameters for the h2pas program.
7474
7575### Example: MPICH2
7676
7777- Enable -e, -D, -p, -w and disable all others.
7878- The -l library path is "mpich".
7979- Output extension is ".pas"
8080- Output directory is h2pas/, which is the default, so leave it empty
8181
8282## Run the wizard
8383
8484Click 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.
8585
8686If 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]].
8787
8888### Example: MPICH2
8989
9090The 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.
9191
9292Add the following to the *Undefines* property:
9393
9494` MPI_INCLUDED`\
9595` MPIO_INCLUDE`\
9696` NEEDS_MPI_FINT`\
9797` MPI_BUILD_PROFILING`\
9898` MPICH_SUPPRESS_PROTOTYPES`\
9999` MPI_Wtime`\
100100` HAVE_MPICH2G2`\
101101` FOO`\
102102` HAVE_PRAGMA_HP_SEC_DEF`
103103
104104Add the following to the *Defines* property:
105105
106106` MPICH2`\
107107` MPICH_NEW_MPIIO`
108108
109109## Writing your own converter tools
110110
111111### Using the tool "Search and replace"
112112
113113Many 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.
114114
115115### Example: rename an identifier Tguint to guint
116116
117117| Property | Value |
118118|-------------|-------------------------------|
119119| Caption | Rename Tguint to guint |
120120| SearchFor | Tguint |
121121| ReplaceWith | guint |
122122| Options | \[trtMatchCase,trtWholeWord\] |
123123
124124### Example: rename several identifiers
125125
126126Rename Tguint to guint, Tgulong to gulong, Tgint to gint:
127127
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>
128+**Property**
154129
130+**Value**
131+
132+Caption
133+
134+Rename Tguint to guint
135+
136+SearchFor
137+
138+ T(guint|gint|gulong)
139+
140+ReplaceWith
141+
142+\$1
143+
144+Options
145+
146+\[trtMatchCase,trtWholeWord,trtRegExpr\]
147+
155148### Example: MPICH2
156149
157150#### Removing Makro MPI_DUP_FN
158151
159152The *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.
160153
161154Add a 'Search and replace' tool to the before H2Pas tools (at the end) with the following settings:
162155
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>
156+**Property**
193157
158+**Value**
159+
160+Caption
161+
162+Remove dummy define MPI_DUP_FN
163+
164+Name
165+
166+RemoveDummyDefineMPI_DUP_FN
167+
168+SearchFor
169+
170+ ^#define\s+MPI_DUP_FN\s+MPIR_Dup_fn
171+
172+ReplaceWith
173+
174+Options
175+
176+\[trtMatchCase,trtRegExpr\]
177+
194178#### Removing typecasts to MPI_DUP_FN
195179
196180Add a 'Search and replace' tool to the before H2Pas tools (at the end) with the following settings:
197181
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>
182+**Property**
228183
184+**Value**
185+
186+Caption
187+
188+Remove MPI_DUP_FN Makros
189+
190+Name
191+
192+Remove_MPI_DUP_Func
193+
194+SearchFor
195+
196+ ^#define .*MPI_DUP_FN\).*$
197+
198+ReplaceWith
199+
200+Options
201+
202+\[trtMatchCase,trtRegExpr\]
203+
229204#### Removing functions MPIO_Test, PMPIO_Test, MPIO_Wait and PMPIO_Wait
230205
231206The MPIO_Test, PMPIO_Test, MPIO_Wait and PMPIO_Wait functions are defined twice and redefined via macro. So, the functions can be removed:
232207
233208Add a 'Search and replace' tool to the before H2Pas tools (at the end) with the following settings:
234209
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>
210+**Property**
211+
212+**Value**
213+
214+Caption
215+
216+ Remove Func P?MPIO_(Test|Wait)
217+
218+Name
219+
220+RemoveFuncP_MPIO_Test_Wait
221+
222+SearchFor
223+
224+ ^int P?MPIO_(Test|Wait).*$
225+
226+ReplaceWith
227+
228+Options
229+
230+\[trtMatchCase,trtRegExpr\]
265231
266232## Improving an existing tool
267233
268234You found a bug and want to fix it or you want to extend one of the above tools. Great!
269235
270236Most 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:
271237
272238``` bash
273239./testh2pastool files/h2pastest.pas
274240```
275241
276242## Writing a custom tool
277243
278244You 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):
279245
280246``` pascal
281247uses
282248 Classes, ..., IDETextConverter;
283249
284250type
285251 TYourToolClass = class(TCustomTextConverterTool)
286252 public
287253 class function ClassDescription: string; override;
288254 function Execute(aText: TIDETextConverter): TModalResult; override;
289255 end;
290256
291257procedure Register;
292258
293259implementation
294260
295261procedure Register;
296262begin
297263 TextConverterToolClasses.RegisterClass(TYourToolClass);
298264end;
299265```
300266
301267Do 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.
302268
303269## Future work / Missing stuff
304270
305271- A tool to convert static linked procedures to dynamic linked procedure variables
306272- 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.
307273- Find missing identifiers and let the user check which one to comment or replace with base types.
308274- Create a list of macro functions, which were half translated.
309275
310276C:
311277
312278``` c
313279typedef struct page
314280{
315281 u32int present : 1; // Page present in memory
316282 u32int rw : 1; // Read-only if clear, readwrite if set
317283 u32int user : 1; // Supervisor level only if clear
318284 u32int accessed : 1; // Has the page been accessed since last refresh?
319285 u32int dirty : 1; // Has the page been written to since last refresh?
320286 u32int unused : 7; // Amalgamation of unused and reserved bits
321287 u32int frame : 20; // Frame address (shifted right 12 bits)
322288} page_t;
323289```
324290
325291Pascal:
326292
327293``` pascal
328294type
329295 Unsigned_7 = 0 .. (1 shl 7) - 1;
330296 Unsigned_20 = 0 .. (1 shl 20) - 1;
331297
332298type
333299 page_t = bitpacked record
334300 present : boolean;
335301 rw : boolean;
336302 user : boolean;
337303 accessed : boolean;
338304 dirty : boolean;
339305 unused : Unsigned_7;
340306 frame : Unsigned_20;
341307 end;
342308```
343309
344310# Convert C to Pascal with lua script
345311
346312I'm impressed by the power of the lua scripting language and how easy it is to do funny things with it.
347313
348314``` lua
349315--
350316-- Please send any improvement to mingodadATgmailDOTcom.
351317--
352318
353319inFileName = arg[1] or 'g:\\tmp\\plua\\clua\\lvm.c'
354320
355321fh = assert(io.open(inFileName))
356322cbody = fh:read('*a')
357323fh:close()
358324
359325pasbody = cbody
360326
361327function split(str, pat)
362328 local t = {} -- NOTE: use {n = 0} in Lua-5.0
363329 local fpat = '(.-)' .. pat
364330 local last_end = 1
365331 local s, e, cap = str:find(fpat, 1)
366332 while s do
367333 if s ~= 1 or cap ~= '' then
368334 table.insert(t, cap)
369335 end
370336 last_end = e+1
371337 s, e, cap = str:find(fpat, last_end)
372338 end
373339 if last_end <= #str then
374340 cap = str:sub(last_end)
375341 table.insert(t, cap)
376342 end
377343 return t
378344end
379345
380346function trim(s)
381347 -- from PiL2 20.4
382348 return (s:gsub('^%s*(.-)%s*$', '%1'))
383349end
384350
385351function trimChars(str, c1, c2)
386352 return str:gsub('^%' .. c1 .. '*(.-)%' .. c2 .. '*$', '%1')
387353end
388354
389355function getPascalType(cType)
390356 if cType == 'void' then
391357 return 'pointer'
392358 elseif cType == 'int' then
393359 return 'integer'
394360 elseif cType == 'short' then
395361 return 'Shortint'
396362 elseif cType == 'define' then
397363 return 'is_define'
398364 else
399365 return cType
400366 end
401367end
402368
403369--check var name and type
404370--if var starts with a capital letter
405371-- add 'T' informt of it
406372--if var is preceded by * then add a 'P' for type
407373
408374function checkVarType(aType, aVar)
409375 aType = getPascalType(aType)
410376 aType = aType:gsub('^(%u)', 'T%1')
411377 aVar = aVar:gsub('^(%*)', function(m)
412378 aType = 'P' .. aType
413379 return ''
414380 end)
415381 aVar = aVar:gsub('(%[[^]]+%])', function(m)
416382 aType = 'array' .. m .. ' of ' .. aType
417383 return ''
418384 end)
419385 return aType, aVar
420386end
421387
422388function varDeclaration(aType, aVar)
423389 aType, aVar = checkVarType(aType, aVar)
424390 return aVar .. ' : ' .. aType
425391end
426392
427393function checkDeclarationOrGoto(m1, m2, m3)
428394 if m2 ~= 'goto' then
429395 return '\n' .. m1 .. varDeclaration(m2, m3) .. ';'
430396 else
431397 return '\n' .. m1 .. m2 .. ' ' .. m3 .. ';'
432398 end
433399end
434400
435401function addProcFunc(mtype, mname, mparams, mbody)
436402 local str, isFunc, rType, k, v
437403 if mtype == 'void' then
438404 str = '\nprocedure '
439405 isFunc = false
440406 else
441407 str = '\nfunction '
442408 isFunc = true
443409 end
444410 mtype, mname = checkVarType(mtype, mname)
445411 local vparams = split(trimChars(mparams, '(',')'), ',')
446412 str = str .. mname .. '('
447413
448414 local tparams = {}
449415 for k, v in pairs(vparams) do
450416 local vparam = split(trim(v), ' ')
451417 if #vparam > 2 then
452418 table.insert(tparams, vparam[1] .. ' ' .. varDeclaration(vparam[2], vparam[3]))
453419 else
454420 --print(vparam[1], vparam[2])
455421 table.insert(tparams, varDeclaration(vparam[1], vparam[2] or 'is_define'))
456422 end
457423 end
458424
459425 str = str .. table.concat(tparams, '; ') .. ')'
460426 if isFunc then
461427 if mtype == 'int' then
462428 rType = 'integer'
463429 else
464430 rType = mtype
465431 end
466432 str = str .. ':' .. rType
467433 end
468434
469435 if mbody then
470436 local tblLabels = {}
471437 for v in mbody:gmatch('\n[\t ]*([%w_]+)[\t ]*:[^=]') do
472438 if v ~= 'default' then
473439 table.insert(tblLabels, v)
474440 end
475441 end
476442
477443 if #tblLabels > 0 then
478444 str = str .. ';\nlabel\n '.. table.concat(tblLabels, ', ')
479445 end
480446
481447 local declarations = {}
482448
483449 mbody = mbody:gsub('(\n[\t ]*)const[\t ]+([%w_]+)[\t ]+(%*?[%w_]+)[\t ]*:%=[\t ]*([^;\n]+;)','%1 %2 %3 := %4')
484450
485451 mbody = mbody:gsub('(\n[\t ]*)([%w_]+)[\t ]+(%*?[%w_]+)[\t ]*:%=[\t ]*([^;\n]+;)', function(m1, m2, m3, m4)
486452 table.insert(declarations, varDeclaration(m2, m3) .. ';' )
487453 return m1 .. m3 .. ' := ' .. m4
488454 end)
489455
490456 mbody = mbody:gsub('\n[\t ]*const[\t ]*([%w_]+)[\t ]+(%*?[%w_]+)[\t ]*;', function(m1,m2)
491457 table.insert(declarations, varDeclaration(m1, m2) .. ';')
492458 return ''
493459 end)
494460
495461 mbody = mbody:gsub('\n([\t ]*)([%w_]+)[\t ]+(%*?[%w_ ,]+);', function(m1,m2,m3)
496462 if m2 ~= 'goto' then
497463 table.insert(declarations, varDeclaration(m2, m3) .. ';')
498464 return ''
499465 else
500466 return '\n' .. m1 .. m2 .. ' ' .. m3 .. ';'
501467 end
502468 end)
503469
504470 if #declarations > 0 then
505471 local unique_decl = {}
506472 table.sort(declarations)
507473 local lastV = ''
508474 local k2, v2
509475 for k2,v2 in pairs(declarations) do
510476 if v2 ~= lastV then
511477 lastV = v2
512478 table.insert(unique_decl, v2)
513479 end
514480 end
515481 str = str .. ';\nvar\n '.. table.concat(unique_decl, '\n ') .. '\n'
516482 else
517483 str = str .. ';\n'
518484 end
519485
520486 str = str .. 'begin' .. trimChars(mbody, '{','}') .. 'end'
521487 end
522488 return str .. ';'
523489end
524490
525491--*************************************
526492--
527493-- Order of substitutions are important
528494--
529495--*************************************
530496
531497--=assignements statements
532498 pasbody = pasbody:gsub("([\t ]*)([%w_]+)[\t ]*=[\t ]*([^=;\n]+);", '%1%2 := %3;')
533499
534500--return
535501 pasbody = pasbody:gsub('([\t ]*)return[\t ]+([^;]+);', '%1exit(%2);')
536502
537503--NULL
538504 pasbody = pasbody:gsub('([^%w]+)NULL([^%w]+)', '%1nil%2')
539505
540506--void functions to procedure declaratios
541507 pasbody = pasbody:gsub('([%w_*]+)[\t ]+([%w_*]+)[\t ]*(%b())[%s]*(%b{})', addProcFunc)
542508 --pasbody = pasbody:gsub('\n([%w_]+)%s+([^%s]+)%s*(%b());', addProcFunc)
543509
544510--const/static
545511 tblLeaveCommented = {
546512 'const', 'static'
547513 }
548514 for k,v in pairs(tblLeaveCommented) do
549515 pasbody = pasbody:gsub('([\t ]*)(' .. v .. ')([\t ]*)', '%1{%2}%3')
550516 end
551517
552518--defines to const
553519 isConstEmmited = false
554520 function addConst(m1,m2)
555521 local sm
556522 sm = ''
557523 if not isConstEmmited then
558524 isConstEmmited = true
559525 sm = '\nconst'
560526 end
561527 return sm .. '\n\t' .. m1 .. ' = ' .. m2 .. ';'
562528 end
563529
564530 isConstEmmited = false
565531 pasbody = pasbody:gsub('\n#define%s+([^%s]+)%s+(%b())', '\nconst %1 = %2;')
566532 isConstEmmited = false
567533 pasbody = pasbody:gsub('\n#define%s+([^%s]+)[ \t]+([^%s]+)', '\nconst %1 = %2;')
568534
569535--includes
570536 tblUses = {}
571537 pasbody = pasbody:gsub('\n#[ \t]*(include)%s+([^\n]+)', function(m1,m2)
572538 for w in m2:gmatch('"([^.]+)%..+"') do
573539 table.insert(tblUses, w)
574540 end
575541 return '\n//#' .. m1 .. ' ' .. m2
576542 end)
577543 pasbody = '//uses ' .. table.concat(tblUses, ', ') .. ';\n' .. pasbody
578544
579545--defines to compiler directives
580546 pasbody = pasbody:gsub('\n#([^\n]+)', '\n{$%1}')
581547
582548--comments
583549 pasbody = pasbody:gsub('\n#', '\n//#')
584550 pasbody = pasbody:gsub('/%*', '(*')
585551 pasbody = pasbody:gsub('%*/', '*)')
586552
587553--declarations
588554 pasbody = pasbody:gsub('\n([\t ]*)([%w_]+)[\t ]+([%w_ ,]+);', checkDeclarationOrGoto)
589555
590556--structs
591557 function parseStruct(m1, m2)
592558 return '\n' .. m1 .. ' = record\n' .. trimChars(m2, '{', '}') .. '\nend'
593559 end
594560 pasbody = pasbody:gsub('\n[\t ]*struct[\t ]+([%w_]+)[\t ]*(%b{})', parseStruct)
595561
596562--if statements
597563 parseStatementCalled = false
598564
599565 function parseStatement(mSpace, mStatement, mCond, mBody)
600566 parseStatementCalled = true
601567 local vStatement
602568 if mStatement == 'if' then
603569 vStatement = mStatement .. ' ' .. mCond .. ' then'
604570 elseif mStatement == 'else' then
605571 return mSpace .. mStatement .. ' begin' .. trimChars(mCond, '{', '}') .. ' end;'
606572 elseif (mStatement == 'while') then
607573 vStatement = mStatement .. ' ' .. mCond .. ' do'
608574 elseif (mStatement == 'for') then
609575 vStatement = mStatement .. ' ' .. mCond .. ' do'
610576 --local forArgs = split(trimChars(mCond, '(', ')'), ';')
611577 --return mSpace .. (forArgs[1] or '') .. ';' .. mSpace ..'while (' ..
612578 -- (forArgs[2] or 'true') .. ') do' .. mSpace .. 'begin' .. mSpace .. trimChars(mBody, '{', '}') .. '\n' .. mSpace ..
613579 -- (forArgs[3] or '') .. ';\n' .. mSpace .. 'end;'
614580 else
615581 vStatement = mStatement .. ' ' .. mCond .. '\n'
616582 end
617583 return mSpace .. vStatement .. ' begin' .. trimChars(mBody, '{', '}') .. 'end;'
618584 end
619585
620586 -- for nested blocks
621587 parseStatementCalled = true
622588 while(parseStatementCalled) do
623589 parseStatementCalled = false
624590 pasbody = pasbody:gsub('([^%w_])(if)[\t ]*(%b())[\t ]*(%b{})', parseStatement)
625591 end
626592
627593 -- else
628594 pasbody = pasbody:gsub('([\t ]+)(else)[\t ]*(%b{})', parseStatement)
629595
630596 pasbody = pasbody:gsub("([^%w_])if[\t ]*(%b())[\t ]*([^;\n]+);", '%1if %2 then %3;')
631597
632598--?: short if
633599 pasbody = pasbody:gsub("([^?])?([^:;\n]):[\t ]*([^%s;]+);", ' iff(%1, %2, %3) ')
634600
635601--while loop
636602 -- for nested blocks
637603 parseStatementCalled = true
638604 while(parseStatementCalled) do
639605 parseStatementCalled = false
640606 pasbody = pasbody:gsub('([^%w_])(while)[\t ]*(%b())[\t ]*(%b{})', parseStatement)
641607 end
642608
643609--for loop
644610 -- for nested blocks
645611 parseStatementCalled = true
646612 while(parseStatementCalled) do
647613 parseStatementCalled = false
648614 pasbody = pasbody:gsub('([^%w_])(for)[\t ]*(%b())%s*(%b{})', parseStatement)
649615 end
650616
651617--do/while
652618 -- for nested blocks
653619 parseStatementCalled = true
654620 while(parseStatementCalled) do
655621 parseStatementCalled = false
656622 pasbody = pasbody:gsub("([^%w_])do[\t ]*(%b{})[\t ]*while([^;\n]+);",
657623 function(m1, m2, m3)
658624 parseStatementCalled = true
659625 return m1 .. 'repeat ' .. trimChars(m2, '{', '}') .. 'until not' .. m3
660626 end)
661627 end
662628
663629--switch/case statements
664630
665631 --begin/end enclose blocks around
666632 -- for nested blocks
667633 parseStatementCalled = true
668634 while(parseStatementCalled) do
669635 parseStatementCalled = false
670636 pasbody = pasbody:gsub("([^%w_]case[\t ]+[^:]+)[\t ]*:[%s]*(%b{})",
671637 function(m1, m2, m3)
672638 parseStatementCalled = true
673639 return m1 .. ': begin' .. trimChars(m2, '{', '}') .. 'end;'
674640 end)
675641 pasbody = pasbody:gsub("([^%w_]default[\t ]*):[%s]*(%b{})",
676642 function(m1, m2, m3)
677643 parseStatementCalled = true
678644 return m1 .. ': begin' .. trimChars(m2, '{', '}') .. 'end;'
679645 end)
680646 end
681647
682648 --breaks remove
683649 pasbody = pasbody:gsub("([^%w_]case[\t ]+[^:]+)[\t ]*:[%s]*(.-)break;", '%1: %2')
684650
685651 --case
686652 pasbody = pasbody:gsub("([^%w_])case[\t ]+([^:]+):", '%1%2:')
687653
688654 -- for nested blocks
689655 parseStatementCalled = true
690656 while(parseStatementCalled) do
691657 parseStatementCalled = false
692658 pasbody = pasbody:gsub("([^%w_])switch[\t ]*(%b())[\t ]*(%b{})",
693659 function(m1, m2, m3)
694660 parseStatementCalled = true
695661 return m1 .. 'case ' .. m2 .. ' of ' .. trimChars(m3, '{', '}') .. 'end;'
696662 end)
697663 end
698664
699665 --pasbody = pasbody:gsub("([\t ]*)case[\t ]+('.')[\t ]*:\n", '%1%2:\n')
700666 --pasbody = pasbody:gsub("([\t ]*)case[\t ]+(%d+)[\t ]*:\n", '%1%2:\n')
701667 --pasbody = pasbody:gsub("([\t ]*)(case)[\t ]+('.')[\t ]*:[\t ]*(%b{})", '%1%2 : %3')
702668
703669--pre/pos increments
704670 -- (n)++ -> PosInc(n)
705671 pasbody = pasbody:gsub("(%([^%s+]+%))%+%+", 'PosInc%1')
706672 -- n*++ -> PosInc(n*)
707673 pasbody = pasbody:gsub("([^%s*+]+)%+%+", 'PosInc(%1)')
708674 -- ++(n) -> PreInc(n)
709675 pasbody = pasbody:gsub("%+%+(%([^%s;+]+%))", 'PreInc%1')
710676 -- ++*n -> PreInc(n)
711677 pasbody = pasbody:gsub("%+%+([^%s*;+]+)", 'PreInc(%1)')
712678
713679 -- (n)--
714680 pasbody = pasbody:gsub("(%([^%s-]+%))%-%-", 'PosDec%1')
715681 -- n*--
716682 pasbody = pasbody:gsub("([^%s*-]+)%-%-", 'PosDec(%1)')
717683 -- --(n)
718684 pasbody = pasbody:gsub("%-%-(%([^%s;-]+%))", 'PreDec%1')
719685 -- --n
720686 pasbody = pasbody:gsub("%-%-([^%s;-]+)", 'PreDec(%1)')
721687
722688--boolean operators
723689 tblBooleanOperators = {
724690 {'||', 'or'},
725691 {'&&', 'and'},
726692 }
727693 for k,v in pairs(tblBooleanOperators) do
728694 pasbody = pasbody:gsub('([\t ]*)' .. v[1] .. '([\t ]*)', '%1) ' .. v[2] .. ' (%2')
729695 end
730696
731697--pointers
732698 pasbody = pasbody:gsub("%->", '.')
733699 pasbody = pasbody:gsub("%(%*([%w_]+)", '(%1^')
734700 pasbody = pasbody:gsub("%*%*([%w_]+)", '%1^^')
735701 pasbody = pasbody:gsub("&([%w_]+)", '@%1')
736702 pasbody = pasbody:gsub("%*([%w_]+)[\t ]*:[\t ]*([%w_]+);", '%1 : ^%2;')
737703 pasbody = pasbody:gsub("%^char", 'pchar')
738704 --pasbody = pasbody:gsub("%*([%w_]+)[\t ]*:=[\t ]*([^;\n]+);", '%1^ := %2;')
739705
740706--bit operators
741707 tblBitOperators = {
742708 {'<<', 'shl'},
743709 {'>>', 'shr'},
744710 {'|', 'or'},
745711 {'&', 'and'},
746712 {'~', 'not'},
747713 }
748714
749715 for k,v in pairs(tblBitOperators) do
750716 pasbody = pasbody:gsub('([\t ]*)' .. v[1] .. '([\t ]*)', '%1 ' .. v[2] .. ' %2')
751717 end
752718
753719--==eq operator
754720 pasbody = pasbody:gsub("([\t ]*)%=%=([\t ]*)", '%1 = %2')
755721
756722--!= not eq operator
757723 pasbody = pasbody:gsub("([\t ]*)!%=([\t ]*)", '%1 <> %2')
758724
759725--! unary
760726 pasbody = pasbody:gsub('([\t ]*)!([\t ]*)', '%1 not %2')
761727
762728--blocks
763729 --pasbody = pasbody:gsub("(%b{})", function(m1) return 'begin' .. trimChars(m1, '{', '}') .. 'end;' end)
764730 --pasbody = pasbody:gsub("(%b{})", function(m1) return 'begin' .. trimChars(m1, '{', '}') .. 'end;' end)
765731
766732--strings and escaped characters
767733 tblEscapedCahrs = {
768734 {'\\"', '"'},
769735 {'\\\\', '\\'},
770736 {'\\t', "'#9"},
771737 {'\\n', "'#10'"},
772738 {'\\f', "'#12'"},
773739 }
774740 pasbody = pasbody:gsub('%b""', function(m1)
775741 return "'" .. trimChars(m1:gsub("'", "''") , '"', '"') .. "'"
776742 end)
777743 for k,v in pairs(tblEscapedCahrs) do
778744 pasbody = pasbody:gsub(v[1], v[2])
779745 end
780746
781747--common functions/procedure
782748 tblOrigFuncNewFunc = {
783749 {'printf', 'format'},
784750 {'malloc', 'GetMem'},
785751 {'free', 'Dispose'},
786752 {'memcpy', 'Move'},
787753 }
788754
789755 for k,v in pairs(tblOrigFuncNewFunc) do
790756 pasbody = pasbody:gsub('([\t ]+)' .. v[1] .. '%(', '%1' .. v[2] .. '(')
791757 end
792758
793759--typedefs
794760 isTypeEmmited = false
795761 function addType(m1,m2)
796762 local sm
797763 sm = ''
798764 if not isTypeEmmited then
799765 isTypeEmmited = true
800766 sm = '\ntype'
801767 end
802768 return sm .. '\n\t' .. m2 .. ' = ' .. m1 .. ';'
803769 end
804770 --pasbody = pasbody:gsub('\ntypedef%s+struct%s+([^%s]+)%s+([^;]+)', addType)
805771 --pasbody = pasbody:gsub('\ntypedef%s+([^%s]+)%s+([^;]-)', addType)
806772 pasbody = pasbody:gsub('\n[\t ]*typedef[\t ]+struct[\t ]+([%w_]+)[\t ]+([^;]+)', '\n%2 = %1')
807773 pasbody = pasbody:gsub('\n[\t ]*typedef[\t ]+struct%s+(%b{})%s+([%w_]+);',
808774 function(m1, m2) return m2 .. ' = record' .. trimChars(m1, '{', '}') .. 'end;' end)
809775
810776-- print the results
811777print(pasbody)
812778```
813779
814780Here is an example of a c file converted.
815781
816782``` pascal
817783//uses lua, llimits, lmem, lstate, lzio;
818784(*
819785** $Id: lzio.c,v 1.31.1.1 2007/12/27 13:02:25 roberto Exp $
820786** a generic input stream interface
821787** See Copyright Notice in lua.h
822788*)
823789
824790//#include <string.h>
825791
826792{$define lzio_c}
827793{$define LUA_CORE}
828794
829795//#include 'lua.h'
830796
831797//#include 'llimits.h'
832798//#include 'lmem.h'
833799//#include 'lstate.h'
834800//#include 'lzio.h'
835801
836802function luaZ_fill(z : PTZIO):integer;
837803var
838804 L : Plua_State;
839805 buff : Pchar;
840806 size : size_t;
841807begin
842808 *L := z.L;
843809 lua_unlock(L);
844810 buff := z.reader(L, z.data, @size);
845811 lua_lock(L);
846812 if (buff = nil ) or ( size = 0) then exit(EOZ);
847813 z.n := size - 1;
848814 z.p := buff;
849815 exit(char2int(PosInc^((z.p))));
850816end;
851817
852818function luaZ_lookahead(z : PTZIO):integer;
853819begin
854820 if (z.n = 0) then begin
855821 if (luaZ_fill(z) = EOZ)
856822 exit(EOZ);
857823 else begin
858824 PosInc(z.n); (* luaZ_fill removed first byte; put back it *)
859825 z-PosDec(>p);
860826 end;
861827 end;
862828 exit(char2int(z^.p));
863829end;
864830
865831procedure luaZ_init(L : Plua_State; z : PTZIO; reader : lua_Reader; data : Ppointer);
866832begin
867833 z.L := L;
868834 z.reader := reader;
869835 z.data := data;
870836 z.n := 0;
871837 z.p := nil;
872838end;
873839
874840(* --------------------------------------------------------------- read --- *)
875841
876842function luaZ_read(z : PTZIO; b : Ppointer; n : size_t):size_t;
877843var
878844 m : size_t;
879845begin
880846 while (n) do begin
881847 if (luaZ_lookahead(z) = EOZ)
882848 exit(n); (* exit(number of missing bytes *)
883849 m = (n <= z.n) ? n : z.n); (* min. between n and z.n *)
884850 Move(b, z.p, m);
885851 z.n -= m;
886852 z.p += m;
887853 b := (char *)b + m;
888854 n -= m;
889855 end;
890856 exit(0);
891857end;
892858
893859(* ------------------------------------------------------------------------ *)
894860
895861function luaZ_openspace(L : Plua_State; buff : PTMbuffer; n : size_t):Pchar;
896862begin
897863 if (n > buff.buffsize) then begin
898864 if (n < LUA_MINBUFFER) then n := LUA_MINBUFFER;
899865 luaZ_resizebuffer(L, buff, n);
900866 end;
901867 exit(buff.buffer);
902868end;
903869```
904870
905871# Manual conversion of C headers to Pascal
906872
907873In 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.
908874
909875## Converting constants
910876
911877C:
912878
913879``` c
914880#define SOME_CONSTANT 5
915881#define ANOTHER_CONSTANT 6
916882```
917883
918884Pascal:
919885
920886``` pascal
921887const
922888 SOME_CONSTANT = 5;
923889 ANOTHER_CONSTANT = 6;
924890```
925891
926892## Converting exported routines
927893
928894The most basic routines can be translated like this:
929895
930896C:
931897
932898``` c
933899int somekind_of_method(int* param_first, int* param_second)
934900void another_method(char*)
935901```
936902
937903Pascal:
938904
939905``` pascal
940906uses ctypes;
941907
942908const
943909 MyLib = 'mylib';
944910
945911function somekind_of_method(param_first, param_second: pcint): cint; external MyLib;
946912procedure another_method_renamed(param1: PChar); external MyLib name 'another_method';
947913```
948914
949915Note 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.
950916
951917Also, 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".
952918
953919Finally, note that if the return value is **void**, then we should use a procedure instead of function.
954920
955921See also the [[http://www.freepascal.org/docs-html/ref/refse85.html External Functions]] section of the reference guide.
956922
957923# Publish your bindings on Lazarus-CCR or Free Pascal
958924
959925You 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.
960926
961927# See also
962928
963929- [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 FreePascal, including writing of wrapper code
964930- [[Common problems when converting C header files]]
965931- [[Pascal for C users]]
966932- [[Lazarus/FPC Libraries]]
967933- [[SWIG]]
968934
969935---
970936*From the Free Pascal wiki, page "Creating bindings for C libraries/ru", 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