Sign in Join
Wiki copytemporary

Creating bindings for C libraries/ru: what changed

Revision 2 to 3. 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
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`
94+```text
95+ MPI_INCLUDED
96+ MPIO_INCLUDE
97+ NEEDS_MPI_FINT
98+ MPI_BUILD_PROFILING
99+ MPICH_SUPPRESS_PROTOTYPES
100+ MPI_Wtime
101+ HAVE_MPICH2G2
102+ FOO
103+ HAVE_PRAGMA_HP_SEC_DEF
104+```
103105
104106Add the following to the *Defines* property:
105107
106-` MPICH2`\
107-` MPICH_NEW_MPIIO`
108+```text
109+ MPICH2
110+ MPICH_NEW_MPIIO
111+```
108112
109113## Writing your own converter tools
110114
111115### Using the tool "Search and replace"
112116
113117Many 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.
114118
115119### Example: rename an identifier Tguint to guint
116120
117121| Property | Value |
118122|-------------|-------------------------------|
119123| Caption | Rename Tguint to guint |
120124| SearchFor | Tguint |
121125| ReplaceWith | guint |
122126| Options | \[trtMatchCase,trtWholeWord\] |
123127
124128### Example: rename several identifiers
125129
126130Rename Tguint to guint, Tgulong to gulong, Tgint to gint:
127131
128132**Property**
129133
130134**Value**
131135
132136Caption
133137
134138Rename Tguint to guint
135139
136140SearchFor
137141
138142 T(guint|gint|gulong)
139143
140144ReplaceWith
141145
142146\$1
143147
144148Options
145149
146150\[trtMatchCase,trtWholeWord,trtRegExpr\]
147151
148152### Example: MPICH2
149153
150154#### Removing Makro MPI_DUP_FN
151155
152156The *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.
153157
154158Add a 'Search and replace' tool to the before H2Pas tools (at the end) with the following settings:
155159
156160**Property**
157161
158162**Value**
159163
160164Caption
161165
162166Remove dummy define MPI_DUP_FN
163167
164168Name
165169
166170RemoveDummyDefineMPI_DUP_FN
167171
168172SearchFor
169173
170174 ^#define\s+MPI_DUP_FN\s+MPIR_Dup_fn
171175
172176ReplaceWith
173177
174178Options
175179
176180\[trtMatchCase,trtRegExpr\]
177181
178182#### Removing typecasts to MPI_DUP_FN
179183
180184Add a 'Search and replace' tool to the before H2Pas tools (at the end) with the following settings:
181185
182186**Property**
183187
184188**Value**
185189
186190Caption
187191
188192Remove MPI_DUP_FN Makros
189193
190194Name
191195
192196Remove_MPI_DUP_Func
193197
194198SearchFor
195199
196200 ^#define .*MPI_DUP_FN\).*$
197201
198202ReplaceWith
199203
200204Options
201205
202206\[trtMatchCase,trtRegExpr\]
203207
204208#### Removing functions MPIO_Test, PMPIO_Test, MPIO_Wait and PMPIO_Wait
205209
206210The MPIO_Test, PMPIO_Test, MPIO_Wait and PMPIO_Wait functions are defined twice and redefined via macro. So, the functions can be removed:
207211
208212Add a 'Search and replace' tool to the before H2Pas tools (at the end) with the following settings:
209213
210214**Property**
211215
212216**Value**
213217
214218Caption
215219
216220 Remove Func P?MPIO_(Test|Wait)
217221
218222Name
219223
220224RemoveFuncP_MPIO_Test_Wait
221225
222226SearchFor
223227
224228 ^int P?MPIO_(Test|Wait).*$
225229
226230ReplaceWith
227231
228232Options
229233
230234\[trtMatchCase,trtRegExpr\]
231235
232236## Improving an existing tool
233237
234238You found a bug and want to fix it or you want to extend one of the above tools. Great!
235239
236240Most 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:
237241
238242``` bash
239243./testh2pastool files/h2pastest.pas
240244```
241245
242246## Writing a custom tool
243247
244248You 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):
245249
246250``` pascal
247251uses
248252 Classes, ..., IDETextConverter;
249253
250254type
251255 TYourToolClass = class(TCustomTextConverterTool)
252256 public
253257 class function ClassDescription: string; override;
254258 function Execute(aText: TIDETextConverter): TModalResult; override;
255259 end;
256260
257261procedure Register;
258262
259263implementation
260264
261265procedure Register;
262266begin
263267 TextConverterToolClasses.RegisterClass(TYourToolClass);
264268end;
265269```
266270
267271Do 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.
268272
269273## Future work / Missing stuff
270274
271275- A tool to convert static linked procedures to dynamic linked procedure variables
272276- 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.
273277- Find missing identifiers and let the user check which one to comment or replace with base types.
274278- Create a list of macro functions, which were half translated.
275279
276280C:
277281
278282``` c
279283typedef struct page
280284{
281285 u32int present : 1; // Page present in memory
282286 u32int rw : 1; // Read-only if clear, readwrite if set
283287 u32int user : 1; // Supervisor level only if clear
284288 u32int accessed : 1; // Has the page been accessed since last refresh?
285289 u32int dirty : 1; // Has the page been written to since last refresh?
286290 u32int unused : 7; // Amalgamation of unused and reserved bits
287291 u32int frame : 20; // Frame address (shifted right 12 bits)
288292} page_t;
289293```
290294
291295Pascal:
292296
293297``` pascal
294298type
295299 Unsigned_7 = 0 .. (1 shl 7) - 1;
296300 Unsigned_20 = 0 .. (1 shl 20) - 1;
297301
298302type
299303 page_t = bitpacked record
300304 present : boolean;
301305 rw : boolean;
302306 user : boolean;
303307 accessed : boolean;
304308 dirty : boolean;
305309 unused : Unsigned_7;
306310 frame : Unsigned_20;
307311 end;
308312```
309313
310314# Convert C to Pascal with lua script
311315
312316I'm impressed by the power of the lua scripting language and how easy it is to do funny things with it.
313317
314318``` lua
315319--
316320-- Please send any improvement to mingodadATgmailDOTcom.
317321--
318322
319323inFileName = arg[1] or 'g:\\tmp\\plua\\clua\\lvm.c'
320324
321325fh = assert(io.open(inFileName))
322326cbody = fh:read('*a')
323327fh:close()
324328
325329pasbody = cbody
326330
327331function split(str, pat)
328332 local t = {} -- NOTE: use {n = 0} in Lua-5.0
329333 local fpat = '(.-)' .. pat
330334 local last_end = 1
331335 local s, e, cap = str:find(fpat, 1)
332336 while s do
333337 if s ~= 1 or cap ~= '' then
334338 table.insert(t, cap)
335339 end
336340 last_end = e+1
337341 s, e, cap = str:find(fpat, last_end)
338342 end
339343 if last_end <= #str then
340344 cap = str:sub(last_end)
341345 table.insert(t, cap)
342346 end
343347 return t
344348end
345349
346350function trim(s)
347351 -- from PiL2 20.4
348352 return (s:gsub('^%s*(.-)%s*$', '%1'))
349353end
350354
351355function trimChars(str, c1, c2)
352356 return str:gsub('^%' .. c1 .. '*(.-)%' .. c2 .. '*$', '%1')
353357end
354358
355359function getPascalType(cType)
356360 if cType == 'void' then
357361 return 'pointer'
358362 elseif cType == 'int' then
359363 return 'integer'
360364 elseif cType == 'short' then
361365 return 'Shortint'
362366 elseif cType == 'define' then
363367 return 'is_define'
364368 else
365369 return cType
366370 end
367371end
368372
369373--check var name and type
370374--if var starts with a capital letter
371375-- add 'T' informt of it
372376--if var is preceded by * then add a 'P' for type
373377
374378function checkVarType(aType, aVar)
375379 aType = getPascalType(aType)
376380 aType = aType:gsub('^(%u)', 'T%1')
377381 aVar = aVar:gsub('^(%*)', function(m)
378382 aType = 'P' .. aType
379383 return ''
380384 end)
381385 aVar = aVar:gsub('(%[[^]]+%])', function(m)
382386 aType = 'array' .. m .. ' of ' .. aType
383387 return ''
384388 end)
385389 return aType, aVar
386390end
387391
388392function varDeclaration(aType, aVar)
389393 aType, aVar = checkVarType(aType, aVar)
390394 return aVar .. ' : ' .. aType
391395end
392396
393397function checkDeclarationOrGoto(m1, m2, m3)
394398 if m2 ~= 'goto' then
395399 return '\n' .. m1 .. varDeclaration(m2, m3) .. ';'
396400 else
397401 return '\n' .. m1 .. m2 .. ' ' .. m3 .. ';'
398402 end
399403end
400404
401405function addProcFunc(mtype, mname, mparams, mbody)
402406 local str, isFunc, rType, k, v
403407 if mtype == 'void' then
404408 str = '\nprocedure '
405409 isFunc = false
406410 else
407411 str = '\nfunction '
408412 isFunc = true
409413 end
410414 mtype, mname = checkVarType(mtype, mname)
411415 local vparams = split(trimChars(mparams, '(',')'), ',')
412416 str = str .. mname .. '('
413417
414418 local tparams = {}
415419 for k, v in pairs(vparams) do
416420 local vparam = split(trim(v), ' ')
417421 if #vparam > 2 then
418422 table.insert(tparams, vparam[1] .. ' ' .. varDeclaration(vparam[2], vparam[3]))
419423 else
420424 --print(vparam[1], vparam[2])
421425 table.insert(tparams, varDeclaration(vparam[1], vparam[2] or 'is_define'))
422426 end
423427 end
424428
425429 str = str .. table.concat(tparams, '; ') .. ')'
426430 if isFunc then
427431 if mtype == 'int' then
428432 rType = 'integer'
429433 else
430434 rType = mtype
431435 end
432436 str = str .. ':' .. rType
433437 end
434438
435439 if mbody then
436440 local tblLabels = {}
437441 for v in mbody:gmatch('\n[\t ]*([%w_]+)[\t ]*:[^=]') do
438442 if v ~= 'default' then
439443 table.insert(tblLabels, v)
440444 end
441445 end
442446
443447 if #tblLabels > 0 then
444448 str = str .. ';\nlabel\n '.. table.concat(tblLabels, ', ')
445449 end
446450
447451 local declarations = {}
448452
449453 mbody = mbody:gsub('(\n[\t ]*)const[\t ]+([%w_]+)[\t ]+(%*?[%w_]+)[\t ]*:%=[\t ]*([^;\n]+;)','%1 %2 %3 := %4')
450454
451455 mbody = mbody:gsub('(\n[\t ]*)([%w_]+)[\t ]+(%*?[%w_]+)[\t ]*:%=[\t ]*([^;\n]+;)', function(m1, m2, m3, m4)
452456 table.insert(declarations, varDeclaration(m2, m3) .. ';' )
453457 return m1 .. m3 .. ' := ' .. m4
454458 end)
455459
456460 mbody = mbody:gsub('\n[\t ]*const[\t ]*([%w_]+)[\t ]+(%*?[%w_]+)[\t ]*;', function(m1,m2)
457461 table.insert(declarations, varDeclaration(m1, m2) .. ';')
458462 return ''
459463 end)
460464
461465 mbody = mbody:gsub('\n([\t ]*)([%w_]+)[\t ]+(%*?[%w_ ,]+);', function(m1,m2,m3)
462466 if m2 ~= 'goto' then
463467 table.insert(declarations, varDeclaration(m2, m3) .. ';')
464468 return ''
465469 else
466470 return '\n' .. m1 .. m2 .. ' ' .. m3 .. ';'
467471 end
468472 end)
469473
470474 if #declarations > 0 then
471475 local unique_decl = {}
472476 table.sort(declarations)
473477 local lastV = ''
474478 local k2, v2
475479 for k2,v2 in pairs(declarations) do
476480 if v2 ~= lastV then
477481 lastV = v2
478482 table.insert(unique_decl, v2)
479483 end
480484 end
481485 str = str .. ';\nvar\n '.. table.concat(unique_decl, '\n ') .. '\n'
482486 else
483487 str = str .. ';\n'
484488 end
485489
486490 str = str .. 'begin' .. trimChars(mbody, '{','}') .. 'end'
487491 end
488492 return str .. ';'
489493end
490494
491495--*************************************
492496--
493497-- Order of substitutions are important
494498--
495499--*************************************
496500
497501--=assignements statements
498502 pasbody = pasbody:gsub("([\t ]*)([%w_]+)[\t ]*=[\t ]*([^=;\n]+);", '%1%2 := %3;')
499503
500504--return
501505 pasbody = pasbody:gsub('([\t ]*)return[\t ]+([^;]+);', '%1exit(%2);')
502506
503507--NULL
504508 pasbody = pasbody:gsub('([^%w]+)NULL([^%w]+)', '%1nil%2')
505509
506510--void functions to procedure declaratios
507511 pasbody = pasbody:gsub('([%w_*]+)[\t ]+([%w_*]+)[\t ]*(%b())[%s]*(%b{})', addProcFunc)
508512 --pasbody = pasbody:gsub('\n([%w_]+)%s+([^%s]+)%s*(%b());', addProcFunc)
509513
510514--const/static
511515 tblLeaveCommented = {
512516 'const', 'static'
513517 }
514518 for k,v in pairs(tblLeaveCommented) do
515519 pasbody = pasbody:gsub('([\t ]*)(' .. v .. ')([\t ]*)', '%1{%2}%3')
516520 end
517521
518522--defines to const
519523 isConstEmmited = false
520524 function addConst(m1,m2)
521525 local sm
522526 sm = ''
523527 if not isConstEmmited then
524528 isConstEmmited = true
525529 sm = '\nconst'
526530 end
527531 return sm .. '\n\t' .. m1 .. ' = ' .. m2 .. ';'
528532 end
529533
530534 isConstEmmited = false
531535 pasbody = pasbody:gsub('\n#define%s+([^%s]+)%s+(%b())', '\nconst %1 = %2;')
532536 isConstEmmited = false
533537 pasbody = pasbody:gsub('\n#define%s+([^%s]+)[ \t]+([^%s]+)', '\nconst %1 = %2;')
534538
535539--includes
536540 tblUses = {}
537541 pasbody = pasbody:gsub('\n#[ \t]*(include)%s+([^\n]+)', function(m1,m2)
538542 for w in m2:gmatch('"([^.]+)%..+"') do
539543 table.insert(tblUses, w)
540544 end
541545 return '\n//#' .. m1 .. ' ' .. m2
542546 end)
543547 pasbody = '//uses ' .. table.concat(tblUses, ', ') .. ';\n' .. pasbody
544548
545549--defines to compiler directives
546550 pasbody = pasbody:gsub('\n#([^\n]+)', '\n{$%1}')
547551
548552--comments
549553 pasbody = pasbody:gsub('\n#', '\n//#')
550554 pasbody = pasbody:gsub('/%*', '(*')
551555 pasbody = pasbody:gsub('%*/', '*)')
552556
553557--declarations
554558 pasbody = pasbody:gsub('\n([\t ]*)([%w_]+)[\t ]+([%w_ ,]+);', checkDeclarationOrGoto)
555559
556560--structs
557561 function parseStruct(m1, m2)
558562 return '\n' .. m1 .. ' = record\n' .. trimChars(m2, '{', '}') .. '\nend'
559563 end
560564 pasbody = pasbody:gsub('\n[\t ]*struct[\t ]+([%w_]+)[\t ]*(%b{})', parseStruct)
561565
562566--if statements
563567 parseStatementCalled = false
564568
565569 function parseStatement(mSpace, mStatement, mCond, mBody)
566570 parseStatementCalled = true
567571 local vStatement
568572 if mStatement == 'if' then
569573 vStatement = mStatement .. ' ' .. mCond .. ' then'
570574 elseif mStatement == 'else' then
571575 return mSpace .. mStatement .. ' begin' .. trimChars(mCond, '{', '}') .. ' end;'
572576 elseif (mStatement == 'while') then
573577 vStatement = mStatement .. ' ' .. mCond .. ' do'
574578 elseif (mStatement == 'for') then
575579 vStatement = mStatement .. ' ' .. mCond .. ' do'
576580 --local forArgs = split(trimChars(mCond, '(', ')'), ';')
577581 --return mSpace .. (forArgs[1] or '') .. ';' .. mSpace ..'while (' ..
578582 -- (forArgs[2] or 'true') .. ') do' .. mSpace .. 'begin' .. mSpace .. trimChars(mBody, '{', '}') .. '\n' .. mSpace ..
579583 -- (forArgs[3] or '') .. ';\n' .. mSpace .. 'end;'
580584 else
581585 vStatement = mStatement .. ' ' .. mCond .. '\n'
582586 end
583587 return mSpace .. vStatement .. ' begin' .. trimChars(mBody, '{', '}') .. 'end;'
584588 end
585589
586590 -- for nested blocks
587591 parseStatementCalled = true
588592 while(parseStatementCalled) do
589593 parseStatementCalled = false
590594 pasbody = pasbody:gsub('([^%w_])(if)[\t ]*(%b())[\t ]*(%b{})', parseStatement)
591595 end
592596
593597 -- else
594598 pasbody = pasbody:gsub('([\t ]+)(else)[\t ]*(%b{})', parseStatement)
595599
596600 pasbody = pasbody:gsub("([^%w_])if[\t ]*(%b())[\t ]*([^;\n]+);", '%1if %2 then %3;')
597601
598602--?: short if
599603 pasbody = pasbody:gsub("([^?])?([^:;\n]):[\t ]*([^%s;]+);", ' iff(%1, %2, %3) ')
600604
601605--while loop
602606 -- for nested blocks
603607 parseStatementCalled = true
604608 while(parseStatementCalled) do
605609 parseStatementCalled = false
606610 pasbody = pasbody:gsub('([^%w_])(while)[\t ]*(%b())[\t ]*(%b{})', parseStatement)
607611 end
608612
609613--for loop
610614 -- for nested blocks
611615 parseStatementCalled = true
612616 while(parseStatementCalled) do
613617 parseStatementCalled = false
614618 pasbody = pasbody:gsub('([^%w_])(for)[\t ]*(%b())%s*(%b{})', parseStatement)
615619 end
616620
617621--do/while
618622 -- for nested blocks
619623 parseStatementCalled = true
620624 while(parseStatementCalled) do
621625 parseStatementCalled = false
622626 pasbody = pasbody:gsub("([^%w_])do[\t ]*(%b{})[\t ]*while([^;\n]+);",
623627 function(m1, m2, m3)
624628 parseStatementCalled = true
625629 return m1 .. 'repeat ' .. trimChars(m2, '{', '}') .. 'until not' .. m3
626630 end)
627631 end
628632
629633--switch/case statements
630634
631635 --begin/end enclose blocks around
632636 -- for nested blocks
633637 parseStatementCalled = true
634638 while(parseStatementCalled) do
635639 parseStatementCalled = false
636640 pasbody = pasbody:gsub("([^%w_]case[\t ]+[^:]+)[\t ]*:[%s]*(%b{})",
637641 function(m1, m2, m3)
638642 parseStatementCalled = true
639643 return m1 .. ': begin' .. trimChars(m2, '{', '}') .. 'end;'
640644 end)
641645 pasbody = pasbody:gsub("([^%w_]default[\t ]*):[%s]*(%b{})",
642646 function(m1, m2, m3)
643647 parseStatementCalled = true
644648 return m1 .. ': begin' .. trimChars(m2, '{', '}') .. 'end;'
645649 end)
646650 end
647651
648652 --breaks remove
649653 pasbody = pasbody:gsub("([^%w_]case[\t ]+[^:]+)[\t ]*:[%s]*(.-)break;", '%1: %2')
650654
651655 --case
652656 pasbody = pasbody:gsub("([^%w_])case[\t ]+([^:]+):", '%1%2:')
653657
654658 -- for nested blocks
655659 parseStatementCalled = true
656660 while(parseStatementCalled) do
657661 parseStatementCalled = false
658662 pasbody = pasbody:gsub("([^%w_])switch[\t ]*(%b())[\t ]*(%b{})",
659663 function(m1, m2, m3)
660664 parseStatementCalled = true
661665 return m1 .. 'case ' .. m2 .. ' of ' .. trimChars(m3, '{', '}') .. 'end;'
662666 end)
663667 end
664668
665669 --pasbody = pasbody:gsub("([\t ]*)case[\t ]+('.')[\t ]*:\n", '%1%2:\n')
666670 --pasbody = pasbody:gsub("([\t ]*)case[\t ]+(%d+)[\t ]*:\n", '%1%2:\n')
667671 --pasbody = pasbody:gsub("([\t ]*)(case)[\t ]+('.')[\t ]*:[\t ]*(%b{})", '%1%2 : %3')
668672
669673--pre/pos increments
670674 -- (n)++ -> PosInc(n)
671675 pasbody = pasbody:gsub("(%([^%s+]+%))%+%+", 'PosInc%1')
672676 -- n*++ -> PosInc(n*)
673677 pasbody = pasbody:gsub("([^%s*+]+)%+%+", 'PosInc(%1)')
674678 -- ++(n) -> PreInc(n)
675679 pasbody = pasbody:gsub("%+%+(%([^%s;+]+%))", 'PreInc%1')
676680 -- ++*n -> PreInc(n)
677681 pasbody = pasbody:gsub("%+%+([^%s*;+]+)", 'PreInc(%1)')
678682
679683 -- (n)--
680684 pasbody = pasbody:gsub("(%([^%s-]+%))%-%-", 'PosDec%1')
681685 -- n*--
682686 pasbody = pasbody:gsub("([^%s*-]+)%-%-", 'PosDec(%1)')
683687 -- --(n)
684688 pasbody = pasbody:gsub("%-%-(%([^%s;-]+%))", 'PreDec%1')
685689 -- --n
686690 pasbody = pasbody:gsub("%-%-([^%s;-]+)", 'PreDec(%1)')
687691
688692--boolean operators
689693 tblBooleanOperators = {
690694 {'||', 'or'},
691695 {'&&', 'and'},
692696 }
693697 for k,v in pairs(tblBooleanOperators) do
694698 pasbody = pasbody:gsub('([\t ]*)' .. v[1] .. '([\t ]*)', '%1) ' .. v[2] .. ' (%2')
695699 end
696700
697701--pointers
698702 pasbody = pasbody:gsub("%->", '.')
699703 pasbody = pasbody:gsub("%(%*([%w_]+)", '(%1^')
700704 pasbody = pasbody:gsub("%*%*([%w_]+)", '%1^^')
701705 pasbody = pasbody:gsub("&([%w_]+)", '@%1')
702706 pasbody = pasbody:gsub("%*([%w_]+)[\t ]*:[\t ]*([%w_]+);", '%1 : ^%2;')
703707 pasbody = pasbody:gsub("%^char", 'pchar')
704708 --pasbody = pasbody:gsub("%*([%w_]+)[\t ]*:=[\t ]*([^;\n]+);", '%1^ := %2;')
705709
706710--bit operators
707711 tblBitOperators = {
708712 {'<<', 'shl'},
709713 {'>>', 'shr'},
710714 {'|', 'or'},
711715 {'&', 'and'},
712716 {'~', 'not'},
713717 }
714718
715719 for k,v in pairs(tblBitOperators) do
716720 pasbody = pasbody:gsub('([\t ]*)' .. v[1] .. '([\t ]*)', '%1 ' .. v[2] .. ' %2')
717721 end
718722
719723--==eq operator
720724 pasbody = pasbody:gsub("([\t ]*)%=%=([\t ]*)", '%1 = %2')
721725
722726--!= not eq operator
723727 pasbody = pasbody:gsub("([\t ]*)!%=([\t ]*)", '%1 <> %2')
724728
725729--! unary
726730 pasbody = pasbody:gsub('([\t ]*)!([\t ]*)', '%1 not %2')
727731
728732--blocks
729733 --pasbody = pasbody:gsub("(%b{})", function(m1) return 'begin' .. trimChars(m1, '{', '}') .. 'end;' end)
730734 --pasbody = pasbody:gsub("(%b{})", function(m1) return 'begin' .. trimChars(m1, '{', '}') .. 'end;' end)
731735
732736--strings and escaped characters
733737 tblEscapedCahrs = {
734738 {'\\"', '"'},
735739 {'\\\\', '\\'},
736740 {'\\t', "'#9"},
737741 {'\\n', "'#10'"},
738742 {'\\f', "'#12'"},
739743 }
740744 pasbody = pasbody:gsub('%b""', function(m1)
741745 return "'" .. trimChars(m1:gsub("'", "''") , '"', '"') .. "'"
742746 end)
743747 for k,v in pairs(tblEscapedCahrs) do
744748 pasbody = pasbody:gsub(v[1], v[2])
745749 end
746750
747751--common functions/procedure
748752 tblOrigFuncNewFunc = {
749753 {'printf', 'format'},
750754 {'malloc', 'GetMem'},
751755 {'free', 'Dispose'},
752756 {'memcpy', 'Move'},
753757 }
754758
755759 for k,v in pairs(tblOrigFuncNewFunc) do
756760 pasbody = pasbody:gsub('([\t ]+)' .. v[1] .. '%(', '%1' .. v[2] .. '(')
757761 end
758762
759763--typedefs
760764 isTypeEmmited = false
761765 function addType(m1,m2)
762766 local sm
763767 sm = ''
764768 if not isTypeEmmited then
765769 isTypeEmmited = true
766770 sm = '\ntype'
767771 end
768772 return sm .. '\n\t' .. m2 .. ' = ' .. m1 .. ';'
769773 end
770774 --pasbody = pasbody:gsub('\ntypedef%s+struct%s+([^%s]+)%s+([^;]+)', addType)
771775 --pasbody = pasbody:gsub('\ntypedef%s+([^%s]+)%s+([^;]-)', addType)
772776 pasbody = pasbody:gsub('\n[\t ]*typedef[\t ]+struct[\t ]+([%w_]+)[\t ]+([^;]+)', '\n%2 = %1')
773777 pasbody = pasbody:gsub('\n[\t ]*typedef[\t ]+struct%s+(%b{})%s+([%w_]+);',
774778 function(m1, m2) return m2 .. ' = record' .. trimChars(m1, '{', '}') .. 'end;' end)
775779
776780-- print the results
777781print(pasbody)
778782```
779783
780784Here is an example of a c file converted.
781785
782786``` pascal
783787//uses lua, llimits, lmem, lstate, lzio;
784788(*
785789** $Id: lzio.c,v 1.31.1.1 2007/12/27 13:02:25 roberto Exp $
786790** a generic input stream interface
787791** See Copyright Notice in lua.h
788792*)
789793
790794//#include <string.h>
791795
792796{$define lzio_c}
793797{$define LUA_CORE}
794798
795799//#include 'lua.h'
796800
797801//#include 'llimits.h'
798802//#include 'lmem.h'
799803//#include 'lstate.h'
800804//#include 'lzio.h'
801805
802806function luaZ_fill(z : PTZIO):integer;
803807var
804808 L : Plua_State;
805809 buff : Pchar;
806810 size : size_t;
807811begin
808812 *L := z.L;
809813 lua_unlock(L);
810814 buff := z.reader(L, z.data, @size);
811815 lua_lock(L);
812816 if (buff = nil ) or ( size = 0) then exit(EOZ);
813817 z.n := size - 1;
814818 z.p := buff;
815819 exit(char2int(PosInc^((z.p))));
816820end;
817821
818822function luaZ_lookahead(z : PTZIO):integer;
819823begin
820824 if (z.n = 0) then begin
821825 if (luaZ_fill(z) = EOZ)
822826 exit(EOZ);
823827 else begin
824828 PosInc(z.n); (* luaZ_fill removed first byte; put back it *)
825829 z-PosDec(>p);
826830 end;
827831 end;
828832 exit(char2int(z^.p));
829833end;
830834
831835procedure luaZ_init(L : Plua_State; z : PTZIO; reader : lua_Reader; data : Ppointer);
832836begin
833837 z.L := L;
834838 z.reader := reader;
835839 z.data := data;
836840 z.n := 0;
837841 z.p := nil;
838842end;
839843
840844(* --------------------------------------------------------------- read --- *)
841845
842846function luaZ_read(z : PTZIO; b : Ppointer; n : size_t):size_t;
843847var
844848 m : size_t;
845849begin
846850 while (n) do begin
847851 if (luaZ_lookahead(z) = EOZ)
848852 exit(n); (* exit(number of missing bytes *)
849853 m = (n <= z.n) ? n : z.n); (* min. between n and z.n *)
850854 Move(b, z.p, m);
851855 z.n -= m;
852856 z.p += m;
853857 b := (char *)b + m;
854858 n -= m;
855859 end;
856860 exit(0);
857861end;
858862
859863(* ------------------------------------------------------------------------ *)
860864
861865function luaZ_openspace(L : Plua_State; buff : PTMbuffer; n : size_t):Pchar;
862866begin
863867 if (n > buff.buffsize) then begin
864868 if (n < LUA_MINBUFFER) then n := LUA_MINBUFFER;
865869 luaZ_resizebuffer(L, buff, n);
866870 end;
867871 exit(buff.buffer);
868872end;
869873```
870874
871875# Manual conversion of C headers to Pascal
872876
873877In 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.
874878
875879## Converting constants
876880
877881C:
878882
879883``` c
880884#define SOME_CONSTANT 5
881885#define ANOTHER_CONSTANT 6
882886```
883887
884888Pascal:
885889
886890``` pascal
887891const
888892 SOME_CONSTANT = 5;
889893 ANOTHER_CONSTANT = 6;
890894```
891895
892896## Converting exported routines
893897
894898The most basic routines can be translated like this:
895899
896900C:
897901
898902``` c
899903int somekind_of_method(int* param_first, int* param_second)
900904void another_method(char*)
901905```
902906
903907Pascal:
904908
905909``` pascal
906910uses ctypes;
907911
908912const
909913 MyLib = 'mylib';
910914
911915function somekind_of_method(param_first, param_second: pcint): cint; external MyLib;
912916procedure another_method_renamed(param1: PChar); external MyLib name 'another_method';
913917```
914918
915919Note 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.
916920
917921Also, 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".
918922
919923Finally, note that if the return value is **void**, then we should use a procedure instead of function.
920924
921925See also the [[http://www.freepascal.org/docs-html/ref/refse85.html External Functions]] section of the reference guide.
922926
923927# Publish your bindings on Lazarus-CCR or Free Pascal
924928
925929You 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.
926930
927931# See also
928932
929933- [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
930934- [[Common problems when converting C header files]]
931935- [[Pascal for C users]]
932936- [[Lazarus/FPC Libraries]]
933937- [[SWIG]]
934938
935939---
936940*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