维基副本镜像

这是 pascal.chat 维基的镜像。编辑、历史记录和登录都在那里。

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语言头文件就能保持不变。

工作步骤

安装工具

正常安装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目录。

pascal
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

运行向导程序

点击底部的“运行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 属性中加入:

pascal
MPI_INCLUDED
     MPIO_INCLUDE
     NEEDS_MPI_FINT
     MPI_BUILD_PROFILING
     MPICH_SUPPRESS_PROTOTYPES
     MPI_Wtime
     HAVE_MPICH2G2
     FOO
     HAVE_PRAGMA_HP_SEC_DEF

在 Defines 属性中加入:

pascal
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
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
Name RemoveFuncP_MPIO_Test_Wait
SearchFor ^int P?MPIO_(Test
ReplaceWith
Options [trtMatchCase,trtRegExpr]

改进已有转换工具

发现缺陷后想要修复,或者扩展上述工具。很好!

大部分转换工具的代码都在 h2paswizard 包的 h2pasconvert 单元中。 转换工具程序至少需要类名、说明和Execute 方法。 若要脱离 IDE 调试代码并节省大量的编译时间,请参阅项目 components/simpleideintf/examples/testh2pastool.lpi。 请编译后在控制台/终端中启动,并将 .h 文件名作为第一个命令行参数。例如:

pascal
./testh2pastool files/h2pastest.pas

编写自定义转换工具

自行编写转换工具并在 IDE 中注册,也都很容易。 启动一个软件包并加入新的单元文件(比如 unit1),定义一个类。已有工具的代码可以作为示例,请参阅前文。 新的转换工具编写完成并用上述 simpleideintf 项目测试通过后,即可在 IDE 中注册了: 在单元文件(unit1)中添加 register 过程,类似以下代码(伪代码):

pascal
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:

pascal
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:

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 脚本语言的强大功能印象深刻,很容易去完成一些有趣的事情。

pascal
--
    -- 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 文件示例:

pascal
//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:

pascal
#define SOME_CONSTANT 5
    #define ANOTHER_CONSTANT 6

Pascal:

pascal
const
      SOME_CONSTANT = 5;
      ANOTHER_CONSTANT = 6;

转换已声明的函数

绝大部分普通函数可以转换如下:

C:

pascal
int somekind_of_method(int* param_first, int* param_second)
    void another_method(char*)

Pascal:

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 发行版中。

参阅


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.

版本2,最后由WikiArchive于修改。