Subject Re: Problems writing/running FreePascal UDF on Firebird Linux/Windows -- SOLVED
Author reinier_olislagers
Fixed the last problem, too. Didn't realize that Firebird passes input
variables BY REFERENCE, so I had to dereference them. Stupid.

Now, the dll seems to work. I'll post the binaries for Windows and
Linux somewhere soon; for now, please find the FreePascal source code
below.
Suggestions for improvement are welcome.

// FreePascal library (.dll/so) to convert between string
representation of IPv4 address and integer values.
// Meant for use in Firebird 2+
//
// On Linux, make sure that that libib_util.so is in your library
search path. Check with ldd libipstring2long.so
// On Windows, make sure that ib_util.dll is in the source directory
or compiler search path.
//
// Compiled using
// Windows XP, Firebird embedded 2.0.1.12855, Free Pascal Compiler
version 2.2.1 [2007/10/08] for i386
// Ubuntu 7.10, 2.6.22-generic, Firebird , Free Pascal Compiler
version 2.2.0 [2007/08/31] for i386
// Compiler options:
// fpc.exe -O3 -WR -fPIC ipstring2long.pas
// to optimize for speed, and use relocatable image (on Windows) so
various udf dlls can live togther,
// use PIC code on Linux (otherwise .so won't compile correctly)
//
// Declaration in Firebird:
// declare external function IPString2Long cstring(255) returns
integer by value entry_point 'IPString2Long' module_name 'ipstring2long';
// We need to let Firebird clear up the PChar result variable so use
FREE_IT:
// declare external function IPLong2String integer returns
cstring(255) free_it entry_point 'IPLong2String' module_name
'ipstring2long';
// sample functions:
// Select ipstring2long('192.168.0.1') from rdb$database;
// select iplong2string(1062692586) from rdb$database;
//
// Alternative code can be found on
http://www.delphi3000.com/articles/article_3845.asp?SK=
// In C, you can use inet_ntoa and inet_aton; use inet.h declaration
on Linux; on Windows, use winsock:
// Winsock2.h
//
// (c) Reinier Olislagers, 2007.
// BSD licence: all use permitted but no warranties whatsoever given.
// Background: FPC versus Firebird data types:
//Type Range Size in
bytes/signed Firebird equiv.
//Byte 0 .. 255 1
none?
//Shortint -128 .. 127 1S
?
//Smallint -32768 .. 32767 2S
smallint
//Word 0 .. 65535 2
none?
//Integer either smallint, longint or int64 size 2,4 or 8S
<see above/below>
//Cardinal either word, longword or qword size 2,4 or 8
none?
//Longint -2147483648 .. 2147483647 4S
integer
//Longword 0..4294967295 4
none?
//Int64 -9223372036854775808 .. 9223372036854775807 8S
bigint
//QWord 0 .. 18446744073709551615 8
none?
//PChar 0 terminated C-style string 1+length
//Ansistring
// Note: firebird doesn't support unsigned integers; it uses two's
complement to store the ints.
// Numeric: Firebird arbitrary size integer: NUMERIC(Precision, Scale)
=> scale 0 equals integers.
// highest precision apparently 18.

{$IFDEF FPC}
// Compiler constants for the FreePascal compiler.
{$MODE OBJFPC} // Needed for the Result := construct
{$SMARTLINK ON} // Try to slim down resulting binary. Seems to work
only on Windows at the moment.
// Try packrecords c to pack bytes just the way c does it.
// Doesn't seem to have any effect, but let's leave it in.
{$PACKRECORDS C}
{$IFDEF WINDOWS}
// DLL version number:
{$VERSION 1.0}
{$ENDIF}
{$ENDIF}

library ipstringconvert;

{$IFDEF FPC}
uses classes, strings, sysutils;
{$ENDIF}

// Memory allocation function that Firebird uses. We need to use the same,
// so Firebird's Free_IT can free string return parameters for us.
function ib_util_malloc(Bytes: integer): pointer; cdecl; external
'ib_util';
// Use longint instead of integer?

function IPString2Long(IPAddress: PChar): LongInt; cdecl; export;
// longint=4 bytes, signed. We don't use unsigned, because
// Firebird can't handle that at the moment.
var
parts: TStringList;
begin
parts := TStringList.Create;
ExtractStrings(['.'], [], IPAddress, parts);
// maybe rewrite using shl??
if parts.Count = 4 then
begin
Result :=
StrToInt(parts[0]) shl 24 +
StrToInt(parts[1]) shl 16 +
StrToInt(parts[2]) shl 8 +
StrToInt(parts[3])
;
{$IFDEF DEBUGOUTPUT}
write ('IPString2Long: our output will be: ' );
writeln (Result);
{$ENDIF}
end
else
begin
// Invalid result. Todo: maybe pass NULL?
{$IFDEF DEBUGOUTPUT}
writeln ('IPString2Long: invalid address or something - returning 0.');
{$ENDIF}
Result := 0 ;
end;
end;

function IPLong2String(IPLong: PLongInt): PChar; cdecl; export;
// LongInt: 4 bytes, signed.
// Get 4 bytes; convert to dotted IP address notation.
// Firebird passes the inputs by reference, so PLongInt is a pointer
// to a longint.
var
intermediate: String;
begin
// Use ^ to dereference the pointer and get to the actual value.
intermediate := IntToStr((IPLong^ shr 24)and $FF) + '.' +
IntToStr((IPLong^ shr 16) and $FF) + '.' +
IntToStr((IPLong^ shr 8) and $FF) + '.' +
IntToStr(IPLong^ and $FF);
{$IFDEF DEBUGOUTPUT}
writeln ('IPLong2String: intermediate: ' + intermediate );
{$ENDIF}
// This works but doesn't use firebird's memory allocation so
// we'll get a memory leak.
//Result:=StrAlloc(Length(intermediate)+1); //Allow for null
character termination
if length(intermediate)>0 then
begin
Result:=ib_util_malloc(Length(intermediate)+1);
// Use firebird's memory allocation, so it can clean up afterwards
// if function is declared in firebird using free_it
StrPCopy (Result, intermediate);
end
else
begin
// Invalid result - TODO: maybe return NULL???!?!
Result:=ib_util_malloc(1);
StrPCopy(Result,'');
end;
end;

exports
IPString2Long name 'IPString2Long',
IPLong2String name 'IPLong2String';

begin
// This dynamic link library needs no initialisation
end.