Creating bindings for C libraries/zh CN
English · Español · Français · Русский · 中文(中国) · 日本語
简介
本页介绍了如何为“C语言库”创建“Pascal绑定”项目。为了在Pascal中使用C语言库,需要为每个C函数、类型和变量创建Pascal转译代码。这可以用h2pas之类的自动化工具来实现,或者对头文件进行人工转换。
H2PAS
概述
H2Pas工具可以对很多常见C语言元素进行自动转译。Lazarus提供了图形用户界面(GUI),可用h2pas和其他工具将创建过程自动化。这会创建一个用于更新绑定项目的规则集。这样后续版本的C库转换起来将会更加容易。h2pas向导程序的一个好特性就是使用临时文件,这样C语言头文件就能保持不变。
工作步骤
- 获取需要转译的C语言头文件。
- 创建工作目录并命名绑定项目。
- 用向导程序新建h2pas项目。
- 将.h文件加入项目。
- 配置h2pas参数。
- 运行向导程序。
- 用文本添加工具修正错误,再次运行向导。
- 如果h2pas运行没有错误了,尝试编译并加入其它工具以美化输出结果。
- 编写测试程序对绑定项目进行测试。
- 在lazarus-ccr或者FreePascal发布绑定项目。
安装工具
正常安装fpc都会自带h2pas工具。
在Lazarus IDE中安装h2paswizard软件包。打开“软件包->安装/卸载软件包”,从右侧列表选中H2PasWizard 并点击“安装选中的”,然后点击“保存并重新构建IDE”。 重启IDE,就会出现新的菜单项:工具 -> h2pas。
获取C语言头文件
C语言.h头文件描述了C程序库的接口,通常不会和C程序库一起提供。必须得有源代码或者程序库的开发包才行。例如gtk库的头文件就在gtk+-devel包中。
示例:MPICH2
从http://www-unix.mcs.anl.gov/mpi/mpich2/下载mpich2-1.0.3.tar.gz并解压。.h文件都在mpich2-1.0.3/src/include下。 关于FPC的MPICH,更多信息请参阅here。
创建工作目录并命名绑定项目
请用有意义的名字创建一个目录。目录名不能包含特殊字符,如空格、变音字符、句点和逗号。 将.h文件拷入目录。
示例:MPICH2
Pascal文件将存放于h2p目录。 .h文件将存放于h2p/c_sources目录。
mkdir -p h2p/c_sources
cp mpich2-1.0.3/src/include/*.h h2p/c_sources/用h2pas向导程序新建h2pas项目
从“工具 -> h2pas”打开向导程序。这会打开一个窗口。可以和其他IDE窗口之间来回切换。最近的2pas项目会自动加载。如要新建一个项目,请点击“设置 -> 新建/清除设置”。然后点击底部的“保存设置”按钮,选取文件名。
注意:如果是第一次使用h2pas向导程序,需用“设置”页确认h2pas.exe转换程序的位置。转换程序h2pas.exe位于FreePascal编译器fpc.exe所在的目录中,如果连同Lazarus下载了FPC,则会位于...\Lazarus\fpc_version_ \bin目录。请保存设置,下次就不必再设置了。
示例:MPICH2
请点击“设置 -> 新建/清除设置”。然后点击底部的“保存设置”按钮,保存为h2p/mpi2.h2p。
在h2pas项目中添加.h文件
在“C 语言头文件”页可以添加/删除.h文件。可以启用/禁用某些.h文件,以便只转换部分文件。
示例:MPICH2
点击“C 语言头文件 -> 添加 .h 文件 ...”,并选取“mpi.h”和“mpio.h”文件。他们会自动启用。选中mpi.h并点击“合并除此以外的所有内容”,这样向导程序就会将所有头文件并入一个单元文件(mpi.pas)
设置h2pas参数
在“H2Pas 选项”页,可以设置h2pas程序的参数。
示例:MPICH2
- 启用-e、-D、-p、-w,并禁用其他所有选项。
- -l 库名称路径设为“mpich”。
- 输出扩展名为“.pas”。
- 输出目录默认是h2pas/,所以保持为空即可。
运行向导程序
点击底部的“运行H2Pas”按钮。 这会将<example>.h文件拷贝成临时文件<example>.tmp.h,并运行“H2Pas 之前”页列出的工具。然后会运行h2pas将<example>.tmp.h转换为<example>.inc或<example>.pas,或者别的什么在“设置”页设置的输出扩展名。然后对输出文件运行“H2Pas 之后”页列出的工具。
如果h2pas发现语法错误,将会用IDE打开example.tmp.h文件,并跳转到出错的行。通常h2pas只会报“syntax error”,非常宽泛。请参阅转换C语言头文件时的常见问题。
示例:MPICH2
h2pas向导程序已包含的工具足以转换该头文件的所有内容,因此这里不会报错。但单元文件还没创建好,请继续。
请切换到“H2Pas 之后”页,加入“Reduce compiler directives in pascal file”工具。
在 Undefines 属性中加入:
MPI_INCLUDED
MPIO_INCLUDE
NEEDS_MPI_FINT
MPI_BUILD_PROFILING
MPICH_SUPPRESS_PROTOTYPES
MPI_Wtime
HAVE_MPICH2G2
FOO
HAVE_PRAGMA_HP_SEC_DEF在 Defines 属性中加入:
MPICH2
MPICH_NEW_MPIIO自行选用转换程序中的工具
使用“搜索和替换”工具
很多类似修改变量名之类的活儿都能用搜索和替换 工具完成。这通过“H2Pas 之前”或“H2Pas 之后”页中的'添加新工具'按钮就能加入。 然后设置SearchFor 、ReplaceWith 、 Options 和Caption 属性即可。
trtRegExpr选项
如果Options 中的trtRegExpr设为True ,则表示要使用正则表达式。这样就能利用元字符 和限定符 对源代码进行复杂的调整和美化。IDE regular expressions中给出了元字符 和限定符 的清单及示例。
示例:将标识符Tguint更名为guint
| 属性 | 值 |
|---|---|
| Caption | Rename Tguint to guint |
| SearchFor | Tguint |
| ReplaceWith | guint |
| Options | [trtMatchCase,trtWholeWord] |
示例:为多个标识符更名
Tguint更名为guint,Tgulong更名为gulong,Tgint更名为gint:
| 属性 | 值 |
|---|---|
| Caption | Rename Tguint to guint |
| SearchFor | T(guint|gint|gulong) |
| ReplaceWith | $1 |
| Options | [trtMatchCase,trtWholeWord,trtRegExpr] |
示例:MPICH2
删除MPI_DUP_FN宏
MPI_DUP_FN 在mpi头文件中定义了两次。前一次是个空的宏定义,后一次是个实际函数。因此,第一次的定义应该删除。
在“H2Pas 之前”加入搜索和替换 工具(加到最后),并设置如下:
| 属性 | 值 |
|---|---|
| Caption | Remove dummy define MPI_DUP_FN |
| Name | RemoveDummyDefineMPI_DUP_FN |
| SearchFor | ^#define\s+MPI_DUP_FN\s+MPIR_Dup_fn |
| ReplaceWith | |
| Options | [trtMatchCase,trtRegExpr] |
删除针对MPI_DUP_FN的类型转换
在“H2Pas 之前”加入搜索和替换 工具(加到最后),并设置如下:
| 属性 | 值 |
|---|---|
| Caption | Remove MPI_DUP_FN Makros |
| Name | Remove_MPI_DUP_Func |
| SearchFor | ^#define .MPI_DUP_FN\).$ |
| ReplaceWith | |
| Options | [trtMatchCase,trtRegExpr] |
删除 MPIO_Test、PMPIO_Test、MPIO_Wait 和 PMPIO_Wait 函数
MPIO_Test、PMPIO_Test、MPIO_Wait和PMPIO_Wait都定义了两次,同时定义了宏。因此函数定义应该删除:
在“H2Pas 之前”加入搜索和替换 工具(加到最后),并设置如下:
| 属性 | 值 |
|---|---|
| Caption | Remove Func P?MPIO_(Test|Wait) |
| Name | RemoveFuncP_MPIO_Test_Wait |
| SearchFor | ^int P?MPIO_(Test|Wait).*$ |
| ReplaceWith | |
| Options | [trtMatchCase,trtRegExpr] |
改进已有转换工具
发现缺陷后想要修复,或者扩展上述工具。很好!
大部分转换工具的代码都在 h2paswizard 包的 h2pasconvert 单元中。 转换工具程序至少需要类名、说明和Execute 方法。 若要脱离 IDE 调试代码并节省大量的编译时间,请参阅项目 components/simpleideintf/examples/testh2pastool.lpi。 请编译后在控制台/终端中启动,并将 .h 文件名作为第一个命令行参数。例如:
./testh2pastool files/h2pastest.pas编写自定义转换工具
自行编写转换工具并在 IDE 中注册,也都很容易。 启动一个软件包并加入新的单元文件(比如 unit1),定义一个类。已有工具的代码可以作为示例,请参阅前文。 新的转换工具编写完成并用上述 simpleideintf 项目测试通过后,即可在 IDE 中注册了: 在单元文件(unit1)中添加 register 过程,类似以下代码(伪代码):
uses
Classes, ..., IDETextConverter;
type
TYourToolClass = class(TCustomTextConverterTool)
public
class function ClassDescription: string; override;
function Execute(aText: TIDETextConverter): TModalResult; override;
end;
procedure Register;
implementation
procedure Register;
begin
TextConverterToolClasses.RegisterClass(TYourToolClass);
end;别忘了在软件包编辑器中为对应单元勾选 register ,否则 IDE 不会去调用 Register 过程。 然后在 IDE 中安装此软件包。
未来任务/未尽事宜
- 将静态链接函数转换为动态链接函数变量的工具
- 修正参数名缺失函数的工具,可通过检索 .c 文件完成修复。还可加入注释,注明找到对应函数的 c 文件名。
- 查到缺失的标识符,并让用户确认是否要注释掉或用基础类型取而代之。
- 建立一份已完成部分转译的宏清单。
C:
typedef struct page
{
u32int present : 1; // Page present in memory
u32int rw : 1; // Read-only if clear, readwrite if set
u32int user : 1; // Supervisor level only if clear
u32int accessed : 1; // Has the page been accessed since last refresh?
u32int dirty : 1; // Has the page been written to since last refresh?
u32int unused : 7; // Amalgamation of unused and reserved bits
u32int frame : 20; // Frame address (shifted right 12 bits)
} page_t;Pascal:
type
Unsigned_7 = 0 .. (1 shl 7) - 1;
Unsigned_20 = 0 .. (1 shl 20) - 1;
type
page_t = bitpacked record
present : boolean;
rw : boolean;
user : boolean;
accessed : boolean;
dirty : boolean;
unused : Unsigned_7;
frame : Unsigned_20;
end;用 Lua 脚本将 C 转换为 Pascal
我对 Lua 脚本语言的强大功能印象深刻,很容易去完成一些有趣的事情。
--
-- Please send any improvement to mingodadATgmailDOTcom.
--
inFileName = arg[1] or 'g:\\tmp\\plua\\clua\\lvm.c'
fh = assert(io.open(inFileName))
cbody = fh:read('*a')
fh:close()
pasbody = cbody
function split(str, pat)
local t = {} -- NOTE: use {n = 0} in Lua-5.0
local fpat = '(.-)' .. pat
local last_end = 1
local s, e, cap = str:find(fpat, 1)
while s do
if s ~= 1 or cap ~= '' then
table.insert(t, cap)
end
last_end = e+1
s, e, cap = str:find(fpat, last_end)
end
if last_end <= #str then
cap = str:sub(last_end)
table.insert(t, cap)
end
return t
end
function trim(s)
-- from PiL2 20.4
return (s:gsub('^%s*(.-)%s*$', '%1'))
end
function trimChars(str, c1, c2)
return str:gsub('^%' .. c1 .. '*(.-)%' .. c2 .. '*$', '%1')
end
function getPascalType(cType)
if cType == 'void' then
return 'pointer'
elseif cType == 'int' then
return 'integer'
elseif cType == 'short' then
return 'Shortint'
elseif cType == 'define' then
return 'is_define'
else
return cType
end
end
--check var name and type
--if var starts with a capital letter
-- add 'T' informt of it
--if var is preceded by * then add a 'P' for type
function checkVarType(aType, aVar)
aType = getPascalType(aType)
aType = aType:gsub('^(%u)', 'T%1')
aVar = aVar:gsub('^(%*)', function(m)
aType = 'P' .. aType
return ''
end)
aVar = aVar:gsub('(%[[^]]+%])', function(m)
aType = 'array' .. m .. ' of ' .. aType
return ''
end)
return aType, aVar
end
function varDeclaration(aType, aVar)
aType, aVar = checkVarType(aType, aVar)
return aVar .. ' : ' .. aType
end
function checkDeclarationOrGoto(m1, m2, m3)
if m2 ~= 'goto' then
return '\n' .. m1 .. varDeclaration(m2, m3) .. ';'
else
return '\n' .. m1 .. m2 .. ' ' .. m3 .. ';'
end
end
function addProcFunc(mtype, mname, mparams, mbody)
local str, isFunc, rType, k, v
if mtype == 'void' then
str = '\nprocedure '
isFunc = false
else
str = '\nfunction '
isFunc = true
end
mtype, mname = checkVarType(mtype, mname)
local vparams = split(trimChars(mparams, '(',')'), ',')
str = str .. mname .. '('
local tparams = {}
for k, v in pairs(vparams) do
local vparam = split(trim(v), ' ')
if #vparam > 2 then
table.insert(tparams, vparam[1] .. ' ' .. varDeclaration(vparam[2], vparam[3]))
else
--print(vparam[1], vparam[2])
table.insert(tparams, varDeclaration(vparam[1], vparam[2] or 'is_define'))
end
end
str = str .. table.concat(tparams, '; ') .. ')'
if isFunc then
if mtype == 'int' then
rType = 'integer'
else
rType = mtype
end
str = str .. ':' .. rType
end
if mbody then
local tblLabels = {}
for v in mbody:gmatch('\n[\t ]*([%w_]+)[\t ]*:[^=]') do
if v ~= 'default' then
table.insert(tblLabels, v)
end
end
if #tblLabels > 0 then
str = str .. ';\nlabel\n '.. table.concat(tblLabels, ', ')
end
local declarations = {}
mbody = mbody:gsub('(\n[\t ]*)const[\t ]+([%w_]+)[\t ]+(%*?[%w_]+)[\t ]*:%=[\t ]*([^;\n]+;)','%1 %2 %3 := %4')
mbody = mbody:gsub('(\n[\t ]*)([%w_]+)[\t ]+(%*?[%w_]+)[\t ]*:%=[\t ]*([^;\n]+;)', function(m1, m2, m3, m4)
table.insert(declarations, varDeclaration(m2, m3) .. ';' )
return m1 .. m3 .. ' := ' .. m4
end)
mbody = mbody:gsub('\n[\t ]*const[\t ]*([%w_]+)[\t ]+(%*?[%w_]+)[\t ]*;', function(m1,m2)
table.insert(declarations, varDeclaration(m1, m2) .. ';')
return ''
end)
mbody = mbody:gsub('\n([\t ]*)([%w_]+)[\t ]+(%*?[%w_ ,]+);', function(m1,m2,m3)
if m2 ~= 'goto' then
table.insert(declarations, varDeclaration(m2, m3) .. ';')
return ''
else
return '\n' .. m1 .. m2 .. ' ' .. m3 .. ';'
end
end)
if #declarations > 0 then
local unique_decl = {}
table.sort(declarations)
local lastV = ''
local k2, v2
for k2,v2 in pairs(declarations) do
if v2 ~= lastV then
lastV = v2
table.insert(unique_decl, v2)
end
end
str = str .. ';\nvar\n '.. table.concat(unique_decl, '\n ') .. '\n'
else
str = str .. ';\n'
end
str = str .. 'begin' .. trimChars(mbody, '{','}') .. 'end'
end
return str .. ';'
end
--*************************************
--
-- Order of substitutions are important
--
--*************************************
--=assignements statements
pasbody = pasbody:gsub("([\t ]*)([%w_]+)[\t ]*=[\t ]*([^=;\n]+);", '%1%2 := %3;')
--return
pasbody = pasbody:gsub('([\t ]*)return[\t ]+([^;]+);', '%1exit(%2);')
--NULL
pasbody = pasbody:gsub('([^%w]+)NULL([^%w]+)', '%1nil%2')
--void functions to procedure declaratios
pasbody = pasbody:gsub('([%w_*]+)[\t ]+([%w_*]+)[\t ]*(%b())[%s]*(%b{})', addProcFunc)
--pasbody = pasbody:gsub('\n([%w_]+)%s+([^%s]+)%s*(%b());', addProcFunc)
--const/static
tblLeaveCommented = {
'const', 'static'
}
for k,v in pairs(tblLeaveCommented) do
pasbody = pasbody:gsub('([\t ]*)(' .. v .. ')([\t ]*)', '%1{%2}%3')
end
--defines to const
isConstEmmited = false
function addConst(m1,m2)
local sm
sm = ''
if not isConstEmmited then
isConstEmmited = true
sm = '\nconst'
end
return sm .. '\n\t' .. m1 .. ' = ' .. m2 .. ';'
end
isConstEmmited = false
pasbody = pasbody:gsub('\n#define%s+([^%s]+)%s+(%b())', '\nconst %1 = %2;')
isConstEmmited = false
pasbody = pasbody:gsub('\n#define%s+([^%s]+)[ \t]+([^%s]+)', '\nconst %1 = %2;')
--includes
tblUses = {}
pasbody = pasbody:gsub('\n#[ \t]*(include)%s+([^\n]+)', function(m1,m2)
for w in m2:gmatch('"([^.]+)%..+"') do
table.insert(tblUses, w)
end
return '\n//#' .. m1 .. ' ' .. m2
end)
pasbody = '//uses ' .. table.concat(tblUses, ', ') .. ';\n' .. pasbody
--defines to compiler directives
pasbody = pasbody:gsub('\n#([^\n]+)', '\n{$%1}')
--comments
pasbody = pasbody:gsub('\n#', '\n//#')
pasbody = pasbody:gsub('/%*', '(*')
pasbody = pasbody:gsub('%*/', '*)')
--declarations
pasbody = pasbody:gsub('\n([\t ]*)([%w_]+)[\t ]+([%w_ ,]+);', checkDeclarationOrGoto)
--structs
function parseStruct(m1, m2)
return '\n' .. m1 .. ' = record\n' .. trimChars(m2, '{', '}') .. '\nend'
end
pasbody = pasbody:gsub('\n[\t ]*struct[\t ]+([%w_]+)[\t ]*(%b{})', parseStruct)
--if statements
parseStatementCalled = false
function parseStatement(mSpace, mStatement, mCond, mBody)
parseStatementCalled = true
local vStatement
if mStatement == 'if' then
vStatement = mStatement .. ' ' .. mCond .. ' then'
elseif mStatement == 'else' then
return mSpace .. mStatement .. ' begin' .. trimChars(mCond, '{', '}') .. ' end;'
elseif (mStatement == 'while') then
vStatement = mStatement .. ' ' .. mCond .. ' do'
elseif (mStatement == 'for') then
vStatement = mStatement .. ' ' .. mCond .. ' do'
--local forArgs = split(trimChars(mCond, '(', ')'), ';')
--return mSpace .. (forArgs[1] or '') .. ';' .. mSpace ..'while (' ..
-- (forArgs[2] or 'true') .. ') do' .. mSpace .. 'begin' .. mSpace .. trimChars(mBody, '{', '}') .. '\n' .. mSpace ..
-- (forArgs[3] or '') .. ';\n' .. mSpace .. 'end;'
else
vStatement = mStatement .. ' ' .. mCond .. '\n'
end
return mSpace .. vStatement .. ' begin' .. trimChars(mBody, '{', '}') .. 'end;'
end
-- for nested blocks
parseStatementCalled = true
while(parseStatementCalled) do
parseStatementCalled = false
pasbody = pasbody:gsub('([^%w_])(if)[\t ]*(%b())[\t ]*(%b{})', parseStatement)
end
-- else
pasbody = pasbody:gsub('([\t ]+)(else)[\t ]*(%b{})', parseStatement)
pasbody = pasbody:gsub("([^%w_])if[\t ]*(%b())[\t ]*([^;\n]+);", '%1if %2 then %3;')
--?: short if
pasbody = pasbody:gsub("([^?])?([^:;\n]):[\t ]*([^%s;]+);", ' iff(%1, %2, %3) ')
--while loop
-- for nested blocks
parseStatementCalled = true
while(parseStatementCalled) do
parseStatementCalled = false
pasbody = pasbody:gsub('([^%w_])(while)[\t ]*(%b())[\t ]*(%b{})', parseStatement)
end
--for loop
-- for nested blocks
parseStatementCalled = true
while(parseStatementCalled) do
parseStatementCalled = false
pasbody = pasbody:gsub('([^%w_])(for)[\t ]*(%b())%s*(%b{})', parseStatement)
end
--do/while
-- for nested blocks
parseStatementCalled = true
while(parseStatementCalled) do
parseStatementCalled = false
pasbody = pasbody:gsub("([^%w_])do[\t ]*(%b{})[\t ]*while([^;\n]+);",
function(m1, m2, m3)
parseStatementCalled = true
return m1 .. 'repeat ' .. trimChars(m2, '{', '}') .. 'until not' .. m3
end)
end
--switch/case statements
--begin/end enclose blocks around
-- for nested blocks
parseStatementCalled = true
while(parseStatementCalled) do
parseStatementCalled = false
pasbody = pasbody:gsub("([^%w_]case[\t ]+[^:]+)[\t ]*:[%s]*(%b{})",
function(m1, m2, m3)
parseStatementCalled = true
return m1 .. ': begin' .. trimChars(m2, '{', '}') .. 'end;'
end)
pasbody = pasbody:gsub("([^%w_]default[\t ]*):[%s]*(%b{})",
function(m1, m2, m3)
parseStatementCalled = true
return m1 .. ': begin' .. trimChars(m2, '{', '}') .. 'end;'
end)
end
--breaks remove
pasbody = pasbody:gsub("([^%w_]case[\t ]+[^:]+)[\t ]*:[%s]*(.-)break;", '%1: %2')
--case
pasbody = pasbody:gsub("([^%w_])case[\t ]+([^:]+):", '%1%2:')
-- for nested blocks
parseStatementCalled = true
while(parseStatementCalled) do
parseStatementCalled = false
pasbody = pasbody:gsub("([^%w_])switch[\t ]*(%b())[\t ]*(%b{})",
function(m1, m2, m3)
parseStatementCalled = true
return m1 .. 'case ' .. m2 .. ' of ' .. trimChars(m3, '{', '}') .. 'end;'
end)
end
--pasbody = pasbody:gsub("([\t ]*)case[\t ]+('.')[\t ]*:\n", '%1%2:\n')
--pasbody = pasbody:gsub("([\t ]*)case[\t ]+(%d+)[\t ]*:\n", '%1%2:\n')
--pasbody = pasbody:gsub("([\t ]*)(case)[\t ]+('.')[\t ]*:[\t ]*(%b{})", '%1%2 : %3')
--pre/pos increments
-- (n)++ -> PosInc(n)
pasbody = pasbody:gsub("(%([^%s+]+%))%+%+", 'PosInc%1')
-- n*++ -> PosInc(n*)
pasbody = pasbody:gsub("([^%s*+]+)%+%+", 'PosInc(%1)')
-- ++(n) -> PreInc(n)
pasbody = pasbody:gsub("%+%+(%([^%s;+]+%))", 'PreInc%1')
-- ++*n -> PreInc(n)
pasbody = pasbody:gsub("%+%+([^%s*;+]+)", 'PreInc(%1)')
-- (n)--
pasbody = pasbody:gsub("(%([^%s-]+%))%-%-", 'PosDec%1')
-- n*--
pasbody = pasbody:gsub("([^%s*-]+)%-%-", 'PosDec(%1)')
-- --(n)
pasbody = pasbody:gsub("%-%-(%([^%s;-]+%))", 'PreDec%1')
-- --n
pasbody = pasbody:gsub("%-%-([^%s;-]+)", 'PreDec(%1)')
--boolean operators
tblBooleanOperators = {
{'||', 'or'},
{'&&', 'and'},
}
for k,v in pairs(tblBooleanOperators) do
pasbody = pasbody:gsub('([\t ]*)' .. v[1] .. '([\t ]*)', '%1) ' .. v[2] .. ' (%2')
end
--pointers
pasbody = pasbody:gsub("%->", '.')
pasbody = pasbody:gsub("%(%*([%w_]+)", '(%1^')
pasbody = pasbody:gsub("%*%*([%w_]+)", '%1^^')
pasbody = pasbody:gsub("&([%w_]+)", '@%1')
pasbody = pasbody:gsub("%*([%w_]+)[\t ]*:[\t ]*([%w_]+);", '%1 : ^%2;')
pasbody = pasbody:gsub("%^char", 'pchar')
--pasbody = pasbody:gsub("%*([%w_]+)[\t ]*:=[\t ]*([^;\n]+);", '%1^ := %2;')
--bit operators
tblBitOperators = {
{'<<', 'shl'},
{'>>', 'shr'},
{'|', 'or'},
{'&', 'and'},
{'~', 'not'},
}
for k,v in pairs(tblBitOperators) do
pasbody = pasbody:gsub('([\t ]*)' .. v[1] .. '([\t ]*)', '%1 ' .. v[2] .. ' %2')
end
--==eq operator
pasbody = pasbody:gsub("([\t ]*)%=%=([\t ]*)", '%1 = %2')
--!= not eq operator
pasbody = pasbody:gsub("([\t ]*)!%=([\t ]*)", '%1 <> %2')
--! unary
pasbody = pasbody:gsub('([\t ]*)!([\t ]*)', '%1 not %2')
--blocks
--pasbody = pasbody:gsub("(%b{})", function(m1) return 'begin' .. trimChars(m1, '{', '}') .. 'end;' end)
--pasbody = pasbody:gsub("(%b{})", function(m1) return 'begin' .. trimChars(m1, '{', '}') .. 'end;' end)
--strings and escaped characters
tblEscapedCahrs = {
{'\\"', '"'},
{'\\\\', '\\'},
{'\\t', "'#9"},
{'\\n', "'#10'"},
{'\\f', "'#12'"},
}
pasbody = pasbody:gsub('%b""', function(m1)
return "'" .. trimChars(m1:gsub("'", "''") , '"', '"') .. "'"
end)
for k,v in pairs(tblEscapedCahrs) do
pasbody = pasbody:gsub(v[1], v[2])
end
--common functions/procedure
tblOrigFuncNewFunc = {
{'printf', 'format'},
{'malloc', 'GetMem'},
{'free', 'Dispose'},
{'memcpy', 'Move'},
}
for k,v in pairs(tblOrigFuncNewFunc) do
pasbody = pasbody:gsub('([\t ]+)' .. v[1] .. '%(', '%1' .. v[2] .. '(')
end
--typedefs
isTypeEmmited = false
function addType(m1,m2)
local sm
sm = ''
if not isTypeEmmited then
isTypeEmmited = true
sm = '\ntype'
end
return sm .. '\n\t' .. m2 .. ' = ' .. m1 .. ';'
end
--pasbody = pasbody:gsub('\ntypedef%s+struct%s+([^%s]+)%s+([^;]+)', addType)
--pasbody = pasbody:gsub('\ntypedef%s+([^%s]+)%s+([^;]-)', addType)
pasbody = pasbody:gsub('\n[\t ]*typedef[\t ]+struct[\t ]+([%w_]+)[\t ]+([^;]+)', '\n%2 = %1')
pasbody = pasbody:gsub('\n[\t ]*typedef[\t ]+struct%s+(%b{})%s+([%w_]+);',
function(m1, m2) return m2 .. ' = record' .. trimChars(m1, '{', '}') .. 'end;' end)
-- print the results
print(pasbody)以下是转换后的 C 文件示例:
//uses lua, llimits, lmem, lstate, lzio;
(*
** $Id: lzio.c,v 1.31.1.1 2007/12/27 13:02:25 roberto Exp $
** a generic input stream interface
** See Copyright Notice in lua.h
*)
//#include <string.h>
{$define lzio_c}
{$define LUA_CORE}
//#include 'lua.h'
//#include 'llimits.h'
//#include 'lmem.h'
//#include 'lstate.h'
//#include 'lzio.h'
function luaZ_fill(z : PTZIO):integer;
var
L : Plua_State;
buff : Pchar;
size : size_t;
begin
*L := z.L;
lua_unlock(L);
buff := z.reader(L, z.data, @size);
lua_lock(L);
if (buff = nil ) or ( size = 0) then exit(EOZ);
z.n := size - 1;
z.p := buff;
exit(char2int(PosInc^((z.p))));
end;
function luaZ_lookahead(z : PTZIO):integer;
begin
if (z.n = 0) then begin
if (luaZ_fill(z) = EOZ)
exit(EOZ);
else begin
PosInc(z.n); (* luaZ_fill removed first byte; put back it *)
z-PosDec(>p);
end;
end;
exit(char2int(z^.p));
end;
procedure luaZ_init(L : Plua_State; z : PTZIO; reader : lua_Reader; data : Ppointer);
begin
z.L := L;
z.reader := reader;
z.data := data;
z.n := 0;
z.p := nil;
end;
(* --------------------------------------------------------------- read --- *)
function luaZ_read(z : PTZIO; b : Ppointer; n : size_t):size_t;
var
m : size_t;
begin
while (n) do begin
if (luaZ_lookahead(z) = EOZ)
exit(n); (* exit(number of missing bytes *)
m = (n <= z.n) ? n : z.n); (* min. between n and z.n *)
Move(b, z.p, m);
z.n -= m;
z.p += m;
b := (char *)b + m;
n -= m;
end;
exit(0);
end;
(* ------------------------------------------------------------------------ *)
function luaZ_openspace(L : Plua_State; buff : PTMbuffer; n : size_t):Pchar;
begin
if (n > buff.buffsize) then begin
if (n < LUA_MINBUFFER) then n := LUA_MINBUFFER;
luaZ_resizebuffer(L, buff, n);
end;
exit(buff.buffer);
end;人工将 C 头文件转换为 Pascal
这时需要同时运用 C 和 Pascal 知识来制作头文件。请参阅以下示例学习转换过程。
转换常量
C:
#define SOME_CONSTANT 5
#define ANOTHER_CONSTANT 6Pascal:
const
SOME_CONSTANT = 5;
ANOTHER_CONSTANT = 6;转换已声明的函数
绝大部分普通函数可以转换如下:
C:
int somekind_of_method(int* param_first, int* param_second)
void another_method(char*)Pascal:
uses ctypes;
const
MyLib = 'mylib';
function somekind_of_method(param_first, param_second: pcint): cint; external MyLib;
procedure another_method_renamed(param1: PChar); external MyLib name 'another_method';首先请注意,应采用 ctypes 单元及其声明的类型来转换 C 头文件,不要直接采用 Pascal 基础类型。这样可以确保在所有平台上都能正确转换,因为 C 类型在不同架构和操作系统中可能会有不同的长度。
其次请注意,基础类型的 C 指针可以转换为 ctypes 中声明的类型,例如 int* 是 C int 型的指针,所以可转换为 pcint 类型(指向 C 整数的指针)。C int 可以转换为 cint,并且在 Pascal 中可以简化同类型参数的声明:“param_first, param_second: pcint”。
最后请注意,如果返回值为 void ,应该将函数替换成过程。
创建 C 到 Pascal 的对应关系 是带有源代码的教程,详细介绍了如何对 C 代码库进行人工转换,处理各种数据类型(字符串、数组等)。该教程还演示了如何用 MinGW C 编译器创建静态(.a)和共享/动态库(.so 或 *.dll)。再结合使用 h2pas 即非常有效。
请参阅参考手册中的外部函数。
在 Lazarus-CCR 或 Free Pascal 发布绑定代码
绑定代码可提交至 Lazarus CCR 库(参见 Using the Lazarus-ccr SVN repository),以供其他人使用和维护。如果这些绑定代码可跨平台应用,还可能会纳入 FreePascal 发行版中。
参阅
- G. Marcou, E. Engler, A. Varnek, How to use C code in Free Pascal projects , Université de Strasbourg, 2009, Link 1, Link 2, 简述:如何在 Free Pascal 中使用 C 和 C++ 代码的简短教程,包括编写封装代码。
- 另一教程演示了如何使用面向对象的 C 语言代码和库。
- 转换 C 语言头文件的常见问题
- C 语言用户的 Pascal 说明
- Lazarus/FPC Libraries
- SWIG
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.
Talk about this page
Nobody has opened a bench for this page yet. Sign in to open one.
