Sign in Join
Wiki copytemporary

Creating bindings for C libraries/zh CN: what changed

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

Title: Creating bindings for C libraries/zh CN

OldNewLine
1+# 简介
2+
3+本页介绍了如何为“C语言库”创建“Pascal绑定”项目。为了在Pascal中使用C语言库,需要为每个C函数、类型和变量创建Pascal转译代码。这可以用h2pas之类的自动化工具来实现,或者对头文件进行人工转换。
4+
5+# H2PAS
6+
7+## 概述
8+
9+[[H2Pas]]工具可以对很多常见C语言元素进行自动转译。Lazarus提供了图形用户界面(GUI),可用h2pas和其他工具将创建过程自动化。这会创建一个用于更新绑定项目的规则集。这样后续版本的C库转换起来将会更加容易。h2pas向导程序的一个好特性就是使用临时文件,这样C语言头文件就能保持不变。
10+
11+## 工作步骤
12+
13+ * 获取需要转译的C语言头文件。
14+ * 创建工作目录并命名绑定项目。
15+ * 用向导程序新建h2pas项目。
16+ * 将.h文件加入项目。
17+ * 配置h2pas参数。
18+ * 运行向导程序。
19+ * 用文本添加工具修正错误,再次运行向导。
20+ * 如果h2pas运行没有错误了,尝试编译并加入其它工具以美化输出结果。
21+ * 编写测试程序对绑定项目进行测试。
22+ * 在lazarus-ccr或者FreePascal发布绑定项目。
23+
24+## 安装工具
25+
26+正常安装fpc都会自带h2pas工具。
27+
28+在Lazarus IDE中安装h2paswizard软件包。打开“软件包->安装/卸载软件包”,从右侧列表选中H2PasWizard 并点击“安装选中的”,然后点击“保存并重新构建IDE”。 重启IDE,就会出现新的菜单项:工具 -> h2pas。
29+
30+## 获取C语言头文件
31+
32+C语言.h头文件描述了C程序库的接口,通常不会和C程序库一起提供。必须得有源代码或者程序库的开发包才行。例如gtk库的头文件就在gtk+-devel包中。
33+
34+### 示例:MPICH2
35+
36+从http://www-unix.mcs.anl.gov/mpi/mpich2/下载mpich2-1.0.3.tar.gz并解压。.h文件都在mpich2-1.0.3/src/include下。 关于FPC的MPICH,更多信息请参阅[[MPICH|here]]。
37+
38+## 创建工作目录并命名绑定项目
39+
40+请用有意义的名字创建一个目录。目录名不能包含特殊字符,如空格、变音字符、句点和逗号。 将.h文件拷入目录。
41+
42+### 示例:MPICH2
43+
44+Pascal文件将存放于h2p目录。 .h文件将存放于h2p/c_sources目录。
45+```
46+mkdir -p h2p/c_sources
47+ cp mpich2-1.0.3/src/include/*.h h2p/c_sources/
48+```
49+
50+## 用h2pas向导程序新建h2pas项目
51+
52+从“工具 -> h2pas”打开向导程序。这会打开一个窗口。可以和其他IDE窗口之间来回切换。最近的2pas项目会自动加载。如要新建一个项目,请点击“设置 -> 新建/清除设置”。然后点击底部的“保存设置”按钮,选取文件名。
53+
54+注意:如果是第一次使用h2pas向导程序,需用“设置”页确认h2pas.exe转换程序的位置。转换程序h2pas.exe位于FreePascal编译器fpc.exe所在的目录中,如果连同Lazarus下载了FPC,则会位于...\Lazarus\fpc\_version_ \bin目录。请保存设置,下次就不必再设置了。
55+
56+### 示例:MPICH2
57+
58+请点击“设置 -> 新建/清除设置”。然后点击底部的“保存设置”按钮,保存为h2p/mpi2.h2p。
59+
60+## 在h2pas项目中添加.h文件
61+
62+在“C 语言头文件”页可以添加/删除.h文件。可以启用/禁用某些.h文件,以便只转换部分文件。
63+
64+### 示例:MPICH2
65+
66+点击“C 语言头文件 -> 添加 .h 文件 ...”,并选取“mpi.h”和“mpio.h”文件。他们会自动启用。选中mpi.h并点击“合并除此以外的所有内容”,这样向导程序就会将所有头文件并入一个单元文件(mpi.pas)
67+
68+## 设置h2pas参数
69+
70+在“H2Pas 选项”页,可以设置h2pas程序的参数。
71+
72+### 示例:MPICH2
73+
74+ * 启用-e、-D、-p、-w,并禁用其他所有选项。
75+ * -l 库名称路径设为“mpich”。
76+ * 输出扩展名为“.pas”。
77+ * 输出目录默认是h2pas/,所以保持为空即可。
78+
79+## 运行向导程序
80+
81+点击底部的“运行H2Pas”按钮。 这会将<example>.h文件拷贝成临时文件<example>.tmp.h,并运行“H2Pas 之前”页列出的工具。然后会运行h2pas将<example>.tmp.h转换为<example>.inc或<example>.pas,或者别的什么在“设置”页设置的输出扩展名。然后对输出文件运行“H2Pas 之后”页列出的工具。
82+
83+如果h2pas发现语法错误,将会用IDE打开example.tmp.h文件,并跳转到出错的行。通常h2pas只会报“syntax error”,非常宽泛。请参阅[[Common problems when converting C header files|转换C语言头文件时的常见问题]]。
84+
85+### 示例:MPICH2
86+
87+h2pas向导程序已包含的工具足以转换该头文件的所有内容,因此这里不会报错。但单元文件还没创建好,请继续。
88+
89+请切换到“H2Pas 之后”页,加入“Reduce compiler directives in pascal file”工具。
90+
91+在 _Undefines_ 属性中加入:
92+```
93+MPI_INCLUDED
94+ MPIO_INCLUDE
95+ NEEDS_MPI_FINT
96+ MPI_BUILD_PROFILING
97+ MPICH_SUPPRESS_PROTOTYPES
98+ MPI_Wtime
99+ HAVE_MPICH2G2
100+ FOO
101+ HAVE_PRAGMA_HP_SEC_DEF
102+```
103+
104+
105+在 _Defines_ 属性中加入:
106+```
107+MPICH2
108+ MPICH_NEW_MPIIO
109+```
110+
111+## 自行选用转换程序中的工具
112+
113+### 使用“搜索和替换”工具
114+
115+很多类似修改变量名之类的活儿都能用**搜索和替换** 工具完成。这通过“H2Pas 之前”或“H2Pas 之后”页中的'添加新工具'按钮就能加入。 然后设置**SearchFor** 、**ReplaceWith** 、 **Options** 和**Caption** 属性即可。
116+
117+#### trtRegExpr选项
118+
119+如果**Options** 中的trtRegExpr设为**True** ,则表示要使用[[IDE regular expressions|正则表达式]]。这样就能利用**元字符** 和**限定符** 对源代码进行复杂的调整和美化。[[IDE regular expressions]]中给出了**元字符** 和**限定符** 的清单及示例。
120+
121+### 示例:将标识符Tguint更名为guint
122+
123+属性 | 值
124+---|---
125+Caption | Rename Tguint to guint
126+SearchFor | Tguint
127+ReplaceWith | guint
128+Options | [trtMatchCase,trtWholeWord]
129+
130+### 示例:为多个标识符更名
131+
132+Tguint更名为guint,Tgulong更名为gulong,Tgint更名为gint:
133+
134+属性 | 值
135+---|---
136+Caption | Rename Tguint to guint
137+SearchFor |
138+```
139+T(guint|gint|gulong)
140+```
141+
142+ReplaceWith | $1
143+Options | [trtMatchCase,trtWholeWord,trtRegExpr]
144+
145+### 示例:MPICH2
146+
147+#### 删除MPI_DUP_FN宏
148+
149+_MPI_DUP_FN_ 在mpi头文件中定义了两次。前一次是个空的宏定义,后一次是个实际函数。因此,第一次的定义应该删除。
150+
151+在“H2Pas 之前”加入**搜索和替换** 工具(加到最后),并设置如下:
152+
153+属性 | 值
154+---|---
155+Caption | Remove dummy define MPI_DUP_FN
156+Name | RemoveDummyDefineMPI_DUP_FN
157+SearchFor |
158+```
159+^#define\s+MPI_DUP_FN\s+MPIR_Dup_fn
160+```
161+
162+ReplaceWith |
163+Options | [trtMatchCase,trtRegExpr]
164+
165+#### 删除针对MPI_DUP_FN的类型转换
166+
167+在“H2Pas 之前”加入**搜索和替换** 工具(加到最后),并设置如下:
168+
169+属性 | 值
170+---|---
171+Caption | Remove MPI_DUP_FN Makros
172+Name | Remove_MPI_DUP_Func
173+SearchFor |
174+```
175+^#define .*MPI_DUP_FN\).*$
176+```
177+
178+ReplaceWith |
179+Options | [trtMatchCase,trtRegExpr]
180+
181+#### 删除 MPIO_Test、PMPIO_Test、MPIO_Wait 和 PMPIO_Wait 函数
182+
183+MPIO_Test、PMPIO_Test、MPIO_Wait和PMPIO_Wait都定义了两次,同时定义了宏。因此函数定义应该删除:
184+
185+在“H2Pas 之前”加入**搜索和替换** 工具(加到最后),并设置如下:
186+
187+属性 | 值
188+---|---
189+Caption |
190+```
191+Remove Func P?MPIO_(Test|Wait)
192+```
193+
194+Name | RemoveFuncP_MPIO_Test_Wait
195+SearchFor |
196+```
197+^int P?MPIO_(Test|Wait).*$
198+```
199+
200+ReplaceWith |
201+Options | [trtMatchCase,trtRegExpr]
202+
203+## 改进已有转换工具
204+
205+发现缺陷后想要修复,或者扩展上述工具。很好!
206+
207+大部分转换工具的代码都在 h2paswizard 包的 h2pasconvert 单元中。 转换工具程序至少需要类名、说明和**Execute** 方法。 若要脱离 IDE 调试代码并节省大量的编译时间,请参阅项目 components/simpleideintf/examples/testh2pastool.lpi。 请编译后在控制台/终端中启动,并将 .h 文件名作为第一个命令行参数。例如:
208+```
209+./testh2pastool files/h2pastest.pas
210+```
211+
212+## 编写自定义转换工具
213+
214+自行编写转换工具并在 IDE 中注册,也都很容易。 启动一个软件包并加入新的单元文件(比如 unit1),定义一个类。已有工具的代码可以作为示例,请参阅前文。 新的转换工具编写完成并用上述 simpleideintf 项目测试通过后,即可在 IDE 中注册了: 在单元文件(unit1)中添加 **register** 过程,类似以下代码(伪代码):
215+```
216+uses
217+ Classes, ..., IDETextConverter;
218+
219+ type
220+ TYourToolClass = class(TCustomTextConverterTool)
221+ public
222+ class function ClassDescription: string; override;
223+ function Execute(aText: TIDETextConverter): TModalResult; override;
224+ end;
225+
226+ procedure Register;
227+
228+ implementation
229+
230+ procedure Register;
231+ begin
232+ TextConverterToolClasses.RegisterClass(TYourToolClass);
233+ end;
234+```
235+
236+别忘了在软件包编辑器中为对应单元勾选 **register** ,否则 IDE 不会去调用 Register 过程。 然后在 IDE 中安装此软件包。
237+
238+## 未来任务/未尽事宜
239+
240+ * 将静态链接函数转换为动态链接函数变量的工具
241+ * 修正参数名缺失函数的工具,可通过检索 .c 文件完成修复。还可加入注释,注明找到对应函数的 c 文件名。
242+ * 查到缺失的标识符,并让用户确认是否要注释掉或用基础类型取而代之。
243+ * 建立一份已完成部分转译的宏清单。
244+
245+C:
246+```
247+typedef struct page
248+ {
249+ u32int present : 1; // Page present in memory
250+ u32int rw : 1; // Read-only if clear, readwrite if set
251+ u32int user : 1; // Supervisor level only if clear
252+ u32int accessed : 1; // Has the page been accessed since last refresh?
253+ u32int dirty : 1; // Has the page been written to since last refresh?
254+ u32int unused : 7; // Amalgamation of unused and reserved bits
255+ u32int frame : 20; // Frame address (shifted right 12 bits)
256+ } page_t;
257+```
258+
259+Pascal:
260+```
261+type
262+ Unsigned_7 = 0 .. (1 shl 7) - 1;
263+ Unsigned_20 = 0 .. (1 shl 20) - 1;
264+
265+ type
266+ page_t = bitpacked record
267+ present : boolean;
268+ rw : boolean;
269+ user : boolean;
270+ accessed : boolean;
271+ dirty : boolean;
272+ unused : Unsigned_7;
273+ frame : Unsigned_20;
274+ end;
275+```
276+
277+# 用 Lua 脚本将 C 转换为 Pascal
278+
279+我对 Lua 脚本语言的强大功能印象深刻,很容易去完成一些有趣的事情。
280+```
281+--
282+ -- Please send any improvement to mingodadATgmailDOTcom.
283+ --
284+
285+ inFileName = arg[1] or 'g:\\tmp\\plua\\clua\\lvm.c'
286+
287+ fh = assert(io.open(inFileName))
288+ cbody = fh:read('*a')
289+ fh:close()
290+
291+ pasbody = cbody
292+
293+ function split(str, pat)
294+ local t = {} -- NOTE: use {n = 0} in Lua-5.0
295+ local fpat = '(.-)' .. pat
296+ local last_end = 1
297+ local s, e, cap = str:find(fpat, 1)
298+ while s do
299+ if s ~= 1 or cap ~= '' then
300+ table.insert(t, cap)
301+ end
302+ last_end = e+1
303+ s, e, cap = str:find(fpat, last_end)
304+ end
305+ if last_end <= #str then
306+ cap = str:sub(last_end)
307+ table.insert(t, cap)
308+ end
309+ return t
310+ end
311+
312+ function trim(s)
313+ -- from PiL2 20.4
314+ return (s:gsub('^%s*(.-)%s*$', '%1'))
315+ end
316+
317+ function trimChars(str, c1, c2)
318+ return str:gsub('^%' .. c1 .. '*(.-)%' .. c2 .. '*$', '%1')
319+ end
320+
321+ function getPascalType(cType)
322+ if cType == 'void' then
323+ return 'pointer'
324+ elseif cType == 'int' then
325+ return 'integer'
326+ elseif cType == 'short' then
327+ return 'Shortint'
328+ elseif cType == 'define' then
329+ return 'is_define'
330+ else
331+ return cType
332+ end
333+ end
334+
335+
336+ --check var name and type
337+ --if var starts with a capital letter
338+ -- add 'T' informt of it
339+ --if var is preceded by * then add a 'P' for type
340+
341+ function checkVarType(aType, aVar)
342+ aType = getPascalType(aType)
343+ aType = aType:gsub('^(%u)', 'T%1')
344+ aVar = aVar:gsub('^(%*)', function(m)
345+ aType = 'P' .. aType
346+ return ''
347+ end)
348+ aVar = aVar:gsub('(%[[^]]+%])', function(m)
349+ aType = 'array' .. m .. ' of ' .. aType
350+ return ''
351+ end)
352+ return aType, aVar
353+ end
354+
355+ function varDeclaration(aType, aVar)
356+ aType, aVar = checkVarType(aType, aVar)
357+ return aVar .. ' : ' .. aType
358+ end
359+
360+ function checkDeclarationOrGoto(m1, m2, m3)
361+ if m2 ~= 'goto' then
362+ return '\n' .. m1 .. varDeclaration(m2, m3) .. ';'
363+ else
364+ return '\n' .. m1 .. m2 .. ' ' .. m3 .. ';'
365+ end
366+ end
367+
368+ function addProcFunc(mtype, mname, mparams, mbody)
369+ local str, isFunc, rType, k, v
370+ if mtype == 'void' then
371+ str = '\nprocedure '
372+ isFunc = false
373+ else
374+ str = '\nfunction '
375+ isFunc = true
376+ end
377+ mtype, mname = checkVarType(mtype, mname)
378+ local vparams = split(trimChars(mparams, '(',')'), ',')
379+ str = str .. mname .. '('
380+
381+ local tparams = {}
382+ for k, v in pairs(vparams) do
383+ local vparam = split(trim(v), ' ')
384+ if #vparam > 2 then
385+ table.insert(tparams, vparam[1] .. ' ' .. varDeclaration(vparam[2], vparam[3]))
386+ else
387+ --print(vparam[1], vparam[2])
388+ table.insert(tparams, varDeclaration(vparam[1], vparam[2] or 'is_define'))
389+ end
390+ end
391+
392+ str = str .. table.concat(tparams, '; ') .. ')'
393+ if isFunc then
394+ if mtype == 'int' then
395+ rType = 'integer'
396+ else
397+ rType = mtype
398+ end
399+ str = str .. ':' .. rType
400+ end
401+
402+ if mbody then
403+ local tblLabels = {}
404+ for v in mbody:gmatch('\n[\t ]*([%w_]+)[\t ]*:[^=]') do
405+ if v ~= 'default' then
406+ table.insert(tblLabels, v)
407+ end
408+ end
409+
410+ if #tblLabels > 0 then
411+ str = str .. ';\nlabel\n '.. table.concat(tblLabels, ', ')
412+ end
413+
414+ local declarations = {}
415+
416+ mbody = mbody:gsub('(\n[\t ]*)const[\t ]+([%w_]+)[\t ]+(%*?[%w_]+)[\t ]*:%=[\t ]*([^;\n]+;)','%1 %2 %3 := %4')
417+
418+ mbody = mbody:gsub('(\n[\t ]*)([%w_]+)[\t ]+(%*?[%w_]+)[\t ]*:%=[\t ]*([^;\n]+;)', function(m1, m2, m3, m4)
419+ table.insert(declarations, varDeclaration(m2, m3) .. ';' )
420+ return m1 .. m3 .. ' := ' .. m4
421+ end)
422+
423+ mbody = mbody:gsub('\n[\t ]*const[\t ]*([%w_]+)[\t ]+(%*?[%w_]+)[\t ]*;', function(m1,m2)
424+ table.insert(declarations, varDeclaration(m1, m2) .. ';')
425+ return ''
426+ end)
427+
428+ mbody = mbody:gsub('\n([\t ]*)([%w_]+)[\t ]+(%*?[%w_ ,]+);', function(m1,m2,m3)
429+ if m2 ~= 'goto' then
430+ table.insert(declarations, varDeclaration(m2, m3) .. ';')
431+ return ''
432+ else
433+ return '\n' .. m1 .. m2 .. ' ' .. m3 .. ';'
434+ end
435+ end)
436+
437+
438+ if #declarations > 0 then
439+ local unique_decl = {}
440+ table.sort(declarations)
441+ local lastV = ''
442+ local k2, v2
443+ for k2,v2 in pairs(declarations) do
444+ if v2 ~= lastV then
445+ lastV = v2
446+ table.insert(unique_decl, v2)
447+ end
448+ end
449+ str = str .. ';\nvar\n '.. table.concat(unique_decl, '\n ') .. '\n'
450+ else
451+ str = str .. ';\n'
452+ end
453+
454+ str = str .. 'begin' .. trimChars(mbody, '{','}') .. 'end'
455+ end
456+ return str .. ';'
457+ end
458+
459+ --*************************************
460+ --
461+ -- Order of substitutions are important
462+ --
463+ --*************************************
464+
465+ --=assignements statements
466+ pasbody = pasbody:gsub("([\t ]*)([%w_]+)[\t ]*=[\t ]*([^=;\n]+);", '%1%2 := %3;')
467+
468+ --return
469+ pasbody = pasbody:gsub('([\t ]*)return[\t ]+([^;]+);', '%1exit(%2);')
470+
471+ --NULL
472+ pasbody = pasbody:gsub('([^%w]+)NULL([^%w]+)', '%1nil%2')
473+
474+ --void functions to procedure declaratios
475+ pasbody = pasbody:gsub('([%w_*]+)[\t ]+([%w_*]+)[\t ]*(%b())[%s]*(%b{})', addProcFunc)
476+ --pasbody = pasbody:gsub('\n([%w_]+)%s+([^%s]+)%s*(%b());', addProcFunc)
477+
478+ --const/static
479+ tblLeaveCommented = {
480+ 'const', 'static'
481+ }
482+ for k,v in pairs(tblLeaveCommented) do
483+ pasbody = pasbody:gsub('([\t ]*)(' .. v .. ')([\t ]*)', '%1{%2}%3')
484+ end
485+
486+ --defines to const
487+ isConstEmmited = false
488+ function addConst(m1,m2)
489+ local sm
490+ sm = ''
491+ if not isConstEmmited then
492+ isConstEmmited = true
493+ sm = '\nconst'
494+ end
495+ return sm .. '\n\t' .. m1 .. ' = ' .. m2 .. ';'
496+ end
497+
498+ isConstEmmited = false
499+ pasbody = pasbody:gsub('\n#define%s+([^%s]+)%s+(%b())', '\nconst %1 = %2;')
500+ isConstEmmited = false
501+ pasbody = pasbody:gsub('\n#define%s+([^%s]+)[ \t]+([^%s]+)', '\nconst %1 = %2;')
502+
503+ --includes
504+ tblUses = {}
505+ pasbody = pasbody:gsub('\n#[ \t]*(include)%s+([^\n]+)', function(m1,m2)
506+ for w in m2:gmatch('"([^.]+)%..+"') do
507+ table.insert(tblUses, w)
508+ end
509+ return '\n//#' .. m1 .. ' ' .. m2
510+ end)
511+ pasbody = '//uses ' .. table.concat(tblUses, ', ') .. ';\n' .. pasbody
512+
513+ --defines to compiler directives
514+ pasbody = pasbody:gsub('\n#([^\n]+)', '\n{$%1}')
515+
516+ --comments
517+ pasbody = pasbody:gsub('\n#', '\n//#')
518+ pasbody = pasbody:gsub('/%*', '(*')
519+ pasbody = pasbody:gsub('%*/', '*)')
520+
521+ --declarations
522+ pasbody = pasbody:gsub('\n([\t ]*)([%w_]+)[\t ]+([%w_ ,]+);', checkDeclarationOrGoto)
523+
524+ --structs
525+ function parseStruct(m1, m2)
526+ return '\n' .. m1 .. ' = record\n' .. trimChars(m2, '{', '}') .. '\nend'
527+ end
528+ pasbody = pasbody:gsub('\n[\t ]*struct[\t ]+([%w_]+)[\t ]*(%b{})', parseStruct)
529+
530+ --if statements
531+ parseStatementCalled = false
532+
533+ function parseStatement(mSpace, mStatement, mCond, mBody)
534+ parseStatementCalled = true
535+ local vStatement
536+ if mStatement == 'if' then
537+ vStatement = mStatement .. ' ' .. mCond .. ' then'
538+ elseif mStatement == 'else' then
539+ return mSpace .. mStatement .. ' begin' .. trimChars(mCond, '{', '}') .. ' end;'
540+ elseif (mStatement == 'while') then
541+ vStatement = mStatement .. ' ' .. mCond .. ' do'
542+ elseif (mStatement == 'for') then
543+ vStatement = mStatement .. ' ' .. mCond .. ' do'
544+ --local forArgs = split(trimChars(mCond, '(', ')'), ';')
545+ --return mSpace .. (forArgs[1] or '') .. ';' .. mSpace ..'while (' ..
546+ -- (forArgs[2] or 'true') .. ') do' .. mSpace .. 'begin' .. mSpace .. trimChars(mBody, '{', '}') .. '\n' .. mSpace ..
547+ -- (forArgs[3] or '') .. ';\n' .. mSpace .. 'end;'
548+ else
549+ vStatement = mStatement .. ' ' .. mCond .. '\n'
550+ end
551+ return mSpace .. vStatement .. ' begin' .. trimChars(mBody, '{', '}') .. 'end;'
552+ end
553+
554+ -- for nested blocks
555+ parseStatementCalled = true
556+ while(parseStatementCalled) do
557+ parseStatementCalled = false
558+ pasbody = pasbody:gsub('([^%w_])(if)[\t ]*(%b())[\t ]*(%b{})', parseStatement)
559+ end
560+
561+ -- else
562+ pasbody = pasbody:gsub('([\t ]+)(else)[\t ]*(%b{})', parseStatement)
563+
564+ pasbody = pasbody:gsub("([^%w_])if[\t ]*(%b())[\t ]*([^;\n]+);", '%1if %2 then %3;')
565+
566+ --?: short if
567+ pasbody = pasbody:gsub("([^?])?([^:;\n]):[\t ]*([^%s;]+);", ' iff(%1, %2, %3) ')
568+
569+ --while loop
570+ -- for nested blocks
571+ parseStatementCalled = true
572+ while(parseStatementCalled) do
573+ parseStatementCalled = false
574+ pasbody = pasbody:gsub('([^%w_])(while)[\t ]*(%b())[\t ]*(%b{})', parseStatement)
575+ end
576+
577+ --for loop
578+ -- for nested blocks
579+ parseStatementCalled = true
580+ while(parseStatementCalled) do
581+ parseStatementCalled = false
582+ pasbody = pasbody:gsub('([^%w_])(for)[\t ]*(%b())%s*(%b{})', parseStatement)
583+ end
584+
585+ --do/while
586+ -- for nested blocks
587+ parseStatementCalled = true
588+ while(parseStatementCalled) do
589+ parseStatementCalled = false
590+ pasbody = pasbody:gsub("([^%w_])do[\t ]*(%b{})[\t ]*while([^;\n]+);",
591+ function(m1, m2, m3)
592+ parseStatementCalled = true
593+ return m1 .. 'repeat ' .. trimChars(m2, '{', '}') .. 'until not' .. m3
594+ end)
595+ end
596+
597+
598+ --switch/case statements
599+
600+ --begin/end enclose blocks around
601+ -- for nested blocks
602+ parseStatementCalled = true
603+ while(parseStatementCalled) do
604+ parseStatementCalled = false
605+ pasbody = pasbody:gsub("([^%w_]case[\t ]+[^:]+)[\t ]*:[%s]*(%b{})",
606+ function(m1, m2, m3)
607+ parseStatementCalled = true
608+ return m1 .. ': begin' .. trimChars(m2, '{', '}') .. 'end;'
609+ end)
610+ pasbody = pasbody:gsub("([^%w_]default[\t ]*):[%s]*(%b{})",
611+ function(m1, m2, m3)
612+ parseStatementCalled = true
613+ return m1 .. ': begin' .. trimChars(m2, '{', '}') .. 'end;'
614+ end)
615+ end
616+
617+ --breaks remove
618+ pasbody = pasbody:gsub("([^%w_]case[\t ]+[^:]+)[\t ]*:[%s]*(.-)break;", '%1: %2')
619+
620+ --case
621+ pasbody = pasbody:gsub("([^%w_])case[\t ]+([^:]+):", '%1%2:')
622+
623+ -- for nested blocks
624+ parseStatementCalled = true
625+ while(parseStatementCalled) do
626+ parseStatementCalled = false
627+ pasbody = pasbody:gsub("([^%w_])switch[\t ]*(%b())[\t ]*(%b{})",
628+ function(m1, m2, m3)
629+ parseStatementCalled = true
630+ return m1 .. 'case ' .. m2 .. ' of ' .. trimChars(m3, '{', '}') .. 'end;'
631+ end)
632+ end
633+
634+ --pasbody = pasbody:gsub("([\t ]*)case[\t ]+('.')[\t ]*:\n", '%1%2:\n')
635+ --pasbody = pasbody:gsub("([\t ]*)case[\t ]+(%d+)[\t ]*:\n", '%1%2:\n')
636+ --pasbody = pasbody:gsub("([\t ]*)(case)[\t ]+('.')[\t ]*:[\t ]*(%b{})", '%1%2 : %3')
637+
638+ --pre/pos increments
639+ -- (n)++ -> PosInc(n)
640+ pasbody = pasbody:gsub("(%([^%s+]+%))%+%+", 'PosInc%1')
641+ -- n*++ -> PosInc(n*)
642+ pasbody = pasbody:gsub("([^%s*+]+)%+%+", 'PosInc(%1)')
643+ -- ++(n) -> PreInc(n)
644+ pasbody = pasbody:gsub("%+%+(%([^%s;+]+%))", 'PreInc%1')
645+ -- ++*n -> PreInc(n)
646+ pasbody = pasbody:gsub("%+%+([^%s*;+]+)", 'PreInc(%1)')
647+
648+ -- (n)--
649+ pasbody = pasbody:gsub("(%([^%s-]+%))%-%-", 'PosDec%1')
650+ -- n*--
651+ pasbody = pasbody:gsub("([^%s*-]+)%-%-", 'PosDec(%1)')
652+ -- --(n)
653+ pasbody = pasbody:gsub("%-%-(%([^%s;-]+%))", 'PreDec%1')
654+ -- --n
655+ pasbody = pasbody:gsub("%-%-([^%s;-]+)", 'PreDec(%1)')
656+
657+ --boolean operators
658+ tblBooleanOperators = {
659+ {'||', 'or'},
660+ {'&&', 'and'},
661+ }
662+ for k,v in pairs(tblBooleanOperators) do
663+ pasbody = pasbody:gsub('([\t ]*)' .. v[1] .. '([\t ]*)', '%1) ' .. v[2] .. ' (%2')
664+ end
665+
666+
667+ --pointers
668+ pasbody = pasbody:gsub("%->", '.')
669+ pasbody = pasbody:gsub("%(%*([%w_]+)", '(%1^')
670+ pasbody = pasbody:gsub("%*%*([%w_]+)", '%1^^')
671+ pasbody = pasbody:gsub("&([%w_]+)", '@%1')
672+ pasbody = pasbody:gsub("%*([%w_]+)[\t ]*:[\t ]*([%w_]+);", '%1 : ^%2;')
673+ pasbody = pasbody:gsub("%^char", 'pchar')
674+ --pasbody = pasbody:gsub("%*([%w_]+)[\t ]*:=[\t ]*([^;\n]+);", '%1^ := %2;')
675+
676+ --bit operators
677+ tblBitOperators = {
678+ {'<<', 'shl'},
679+ {'>>', 'shr'},
680+ {'|', 'or'},
681+ {'&', 'and'},
682+ {'~', 'not'},
683+ }
684+
685+ for k,v in pairs(tblBitOperators) do
686+ pasbody = pasbody:gsub('([\t ]*)' .. v[1] .. '([\t ]*)', '%1 ' .. v[2] .. ' %2')
687+ end
688+
689+ --==eq operator
690+ pasbody = pasbody:gsub("([\t ]*)%=%=([\t ]*)", '%1 = %2')
691+
692+ --!= not eq operator
693+ pasbody = pasbody:gsub("([\t ]*)!%=([\t ]*)", '%1 <> %2')
694+
695+ --! unary
696+ pasbody = pasbody:gsub('([\t ]*)!([\t ]*)', '%1 not %2')
697+
698+ --blocks
699+ --pasbody = pasbody:gsub("(%b{})", function(m1) return 'begin' .. trimChars(m1, '{', '}') .. 'end;' end)
700+ --pasbody = pasbody:gsub("(%b{})", function(m1) return 'begin' .. trimChars(m1, '{', '}') .. 'end;' end)
701+
702+ --strings and escaped characters
703+ tblEscapedCahrs = {
704+ {'\\"', '"'},
705+ {'\\\\', '\\'},
706+ {'\\t', "'#9"},
707+ {'\\n', "'#10'"},
708+ {'\\f', "'#12'"},
709+ }
710+ pasbody = pasbody:gsub('%b""', function(m1)
711+ return "'" .. trimChars(m1:gsub("'", "''") , '"', '"') .. "'"
712+ end)
713+ for k,v in pairs(tblEscapedCahrs) do
714+ pasbody = pasbody:gsub(v[1], v[2])
715+ end
716+
717+ --common functions/procedure
718+ tblOrigFuncNewFunc = {
719+ {'printf', 'format'},
720+ {'malloc', 'GetMem'},
721+ {'free', 'Dispose'},
722+ {'memcpy', 'Move'},
723+ }
724+
725+ for k,v in pairs(tblOrigFuncNewFunc) do
726+ pasbody = pasbody:gsub('([\t ]+)' .. v[1] .. '%(', '%1' .. v[2] .. '(')
727+ end
728+
729+ --typedefs
730+ isTypeEmmited = false
731+ function addType(m1,m2)
732+ local sm
733+ sm = ''
734+ if not isTypeEmmited then
735+ isTypeEmmited = true
736+ sm = '\ntype'
737+ end
738+ return sm .. '\n\t' .. m2 .. ' = ' .. m1 .. ';'
739+ end
740+ --pasbody = pasbody:gsub('\ntypedef%s+struct%s+([^%s]+)%s+([^;]+)', addType)
741+ --pasbody = pasbody:gsub('\ntypedef%s+([^%s]+)%s+([^;]-)', addType)
742+ pasbody = pasbody:gsub('\n[\t ]*typedef[\t ]+struct[\t ]+([%w_]+)[\t ]+([^;]+)', '\n%2 = %1')
743+ pasbody = pasbody:gsub('\n[\t ]*typedef[\t ]+struct%s+(%b{})%s+([%w_]+);',
744+ function(m1, m2) return m2 .. ' = record' .. trimChars(m1, '{', '}') .. 'end;' end)
745+
746+
747+ -- print the results
748+ print(pasbody)
749+```
750+
751+以下是转换后的 C 文件示例:
752+```
753+//uses lua, llimits, lmem, lstate, lzio;
754+ (*
755+ ** $Id: lzio.c,v 1.31.1.1 2007/12/27 13:02:25 roberto Exp $
756+ ** a generic input stream interface
757+ ** See Copyright Notice in lua.h
758+ *)
759+
760+
761+ //#include <string.h>
762+
763+ {$define lzio_c}
764+ {$define LUA_CORE}
765+
766+ //#include 'lua.h'
767+
768+ //#include 'llimits.h'
769+ //#include 'lmem.h'
770+ //#include 'lstate.h'
771+ //#include 'lzio.h'
772+
773+
774+
775+ function luaZ_fill(z : PTZIO):integer;
776+ var
777+ L : Plua_State;
778+ buff : Pchar;
779+ size : size_t;
780+ begin
781+ *L := z.L;
782+ lua_unlock(L);
783+ buff := z.reader(L, z.data, @size);
784+ lua_lock(L);
785+ if (buff = nil ) or ( size = 0) then exit(EOZ);
786+ z.n := size - 1;
787+ z.p := buff;
788+ exit(char2int(PosInc^((z.p))));
789+ end;
790+
791+
792+
793+ function luaZ_lookahead(z : PTZIO):integer;
794+ begin
795+ if (z.n = 0) then begin
796+ if (luaZ_fill(z) = EOZ)
797+ exit(EOZ);
798+ else begin
799+ PosInc(z.n); (* luaZ_fill removed first byte; put back it *)
800+ z-PosDec(>p);
801+ end;
802+ end;
803+ exit(char2int(z^.p));
804+ end;
805+
806+
807+
808+ procedure luaZ_init(L : Plua_State; z : PTZIO; reader : lua_Reader; data : Ppointer);
809+ begin
810+ z.L := L;
811+ z.reader := reader;
812+ z.data := data;
813+ z.n := 0;
814+ z.p := nil;
815+ end;
816+
817+
818+ (* --------------------------------------------------------------- read --- *)
819+
820+ function luaZ_read(z : PTZIO; b : Ppointer; n : size_t):size_t;
821+ var
822+ m : size_t;
823+ begin
824+ while (n) do begin
825+ if (luaZ_lookahead(z) = EOZ)
826+ exit(n); (* exit(number of missing bytes *)
827+ m = (n <= z.n) ? n : z.n); (* min. between n and z.n *)
828+ Move(b, z.p, m);
829+ z.n -= m;
830+ z.p += m;
831+ b := (char *)b + m;
832+ n -= m;
833+ end;
834+ exit(0);
835+ end;
836+
837+ (* ------------------------------------------------------------------------ *)
838+
839+ function luaZ_openspace(L : Plua_State; buff : PTMbuffer; n : size_t):Pchar;
840+ begin
841+ if (n > buff.buffsize) then begin
842+ if (n < LUA_MINBUFFER) then n := LUA_MINBUFFER;
843+ luaZ_resizebuffer(L, buff, n);
844+ end;
845+ exit(buff.buffer);
846+ end;
847+```
848+
849+# 人工将 C 头文件转换为 Pascal
850+
851+这时需要同时运用 C 和 Pascal 知识来制作头文件。请参阅以下示例学习转换过程。
852+
853+## 转换常量
854+
855+C:
856+```
857+#define SOME_CONSTANT 5
858+ #define ANOTHER_CONSTANT 6
859+```
860+
861+Pascal:
862+```
863+const
864+ SOME_CONSTANT = 5;
865+ ANOTHER_CONSTANT = 6;
866+```
867+
868+## 转换已声明的函数
869+
870+绝大部分普通函数可以转换如下:
871+
872+C:
873+```
874+int somekind_of_method(int* param_first, int* param_second)
875+ void another_method(char*)
876+```
877+
878+Pascal:
879+```
880+uses ctypes;
881+
882+ const
883+ MyLib = 'mylib';
884+
885+ function somekind_of_method(param_first, param_second: pcint): cint; external MyLib;
886+ procedure another_method_renamed(param1: PChar); external MyLib name 'another_method';
887+```
888+
889+首先请注意,应采用 ctypes 单元及其声明的类型来转换 C 头文件,不要直接采用 Pascal 基础类型。这样可以确保在所有平台上都能正确转换,因为 C 类型在不同架构和操作系统中可能会有不同的长度。
890+
891+其次请注意,基础类型的 C 指针可以转换为 ctypes 中声明的类型,例如 int* 是 C int 型的指针,所以可转换为 pcint 类型(指向 C 整数的指针)。C int 可以转换为 cint,并且在 Pascal 中可以简化同类型参数的声明:“param_first, param_second: pcint”。
892+
893+最后请注意,如果返回值为 **void** ,应该将函数替换成过程。
894+
895+[创建 C 到 Pascal 的对应关系](<https://github.com/williamhunter/pascal-bindings-for-c>) 是带有源代码的教程,详细介绍了如何对 C 代码库进行人工转换,处理各种数据类型(字符串、数组等)。该教程还演示了如何用 MinGW C 编译器创建静态(*.a)和共享/动态库(*.so 或 *.dll)。再结合使用 h2pas 即非常有效。
896+
897+请参阅参考手册中的[外部函数](<http://www.freepascal.org/docs-html/ref/refse85.html>)。
898+
899+# 在 Lazarus-CCR 或 Free Pascal 发布绑定代码
900+
901+绑定代码可提交至 Lazarus CCR 库(参见 [[Using the Lazarus-ccr SVN repository]]),以供其他人使用和维护。如果这些绑定代码可跨平台应用,还可能会纳入 FreePascal 发行版中。
902+
903+# 参阅
904+
905+ * 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>), 简述:如何在 Free Pascal 中使用 C 和 C++ 代码的简短教程,包括编写封装代码。
906+ * [另一教程](<https://github.com/williamhunter/pascal-bindings-for-c/blob/master/docs/Creating%20Pascal%20bindings%20for%20C%20\(v1.0\).pdf>)演示了如何使用面向对象的 C 语言代码和库。
907+ * [[Common problems when converting C header files|转换 C 语言头文件的常见问题]]
908+ * [[Pascal for C users|C 语言用户的 Pascal 说明]]
909+ * [[Lazarus/FPC Libraries]]
910+ * [[SWIG]]
911+
912+---
913+*From the Free Pascal wiki, page "Creating bindings for C libraries/zh CN", as the Internet Archive kept it on 2025-10-04, last changed on 2024-09-22. 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