Idiomatic way to use data buffer with non-uniform byte skips in Ada - buffer

I'm trying to read from a byte buffer in Ada, such as a file or via buffer for a network connection. The messages are variable in size with a common header, in C++ it'd look something like this:
enum class MessageType : uint16_t {
Foo = 0, Bar = 1
};
// Force the layout.
#pragma pack(push,1)
// 4 byte message header
struct MessageHeader {
uint16_t checksum;
uint16_t type;
};
// 4 byte header + 4 byte message
struct FooMessage {
MessageHeader header;
uint32_t tomatoes;
};
// 4 byte header + 8 byte message
struct BarMessage {
MessageHeader header;
uint32_t oranges;
uint32_t apples;
};
#pragma pack(pop)
// For simplicity, assume the buffer is complete and only holds full messages.
void read(char* buffer, uint32_t bytesLeft) {
while (bytesLeft > 0) {
MessageHeader* header = reinterpret_cast<MessageHeader*>(buffer);
switch (header->type) {
case FooType: {
FooMessage* foo = reinterpret_case<FooMessage*>(buffer);
// process as const FooMessage&
processFoo(*foo);
}
case BarType: {
BarMessage* bar = reinterpret_cast<BarMessage*>(buffer);
// process as const BarMessage&
processBar(*bar);
}
}
const auto size = (header->type == Foo ? sizeof(FooMessage) : sizeof(BarMessage));
buffer += size;
bytesLeft -= size;
}
}
I'm not sure of the idiomatic way of doing it. Note that in some formats, the message type might not be the leading data member in the header, as well. Should you be writing to and reading off an array of Character or something from Interfaces.C.char_array, or an address of memory from System.Address or something else? Or should this be an address to an array elsewhere here, or just an array with "Convention => C" to prevent the leading size from being included?
This is what I have so far in Ada:
type Message_Type is (Foo, Bar) with Size => 16;
for Message_Type use (Foo => 0, Bar => 1);
-- Assume these work correctly and I don't need to do bit layout directly.
type Message_Header is record
Checksum : Interfaces.Integer_16;
Msg_Type : Message_Type;
end record
with Convention => C, Size => 32;
type Foo_Message is record
Header : Message_Header;
Tomatoes : Interfaces.Integer_32;
end record
with Convention => C, Size => 64;
type Bar_Message is record
Header : Message_Header;
Oranges : Interfaces.Integer_32;
Apples : Interfaces.Integer_32;
end record
with Convention => C, Size => 96;
procedure Read(
-- System.Address seems really weird here
Buffer : in out System.Address;
Bytes_Left : in out Interfaces.Integer_64)
is
use type Interfaces.Integer_64;
use type System.Address;
function To_Address is new Ada.Unchecked_Conversion (Interfaces.Integer_64, System.Address);
function To_Integer is new Ada.Unchecked_Conversion (System.Address, Interfaces.Integer_64);
procedure Process_Bar (B : aliased Bar_Message) is null;
procedure Process_Foo (F : aliased Foo_Message) is null;
begin
while Bytes_Left > 0 loop
declare
-- I'm really lost here.
--
-- Do you use access types to access the buffer or
-- setting the address with "for Foo'Address use Buffer"??
--
Header : Message_Header;
for Header'Address use Buffer;
enter code here
-- I'm assuming this doesn't initialize Foo and Bar here?
Foo_Msg : aliased Foo_Message;
Bar_Msg : aliased Bar_Message;
for Foo_Msg'Address use Buffer;
for Bar_Msg'Address use Buffer;
-- I'm assuming this doesn't initialize Foo and Bar here?
Size : System.Address := To_Address(0);
begin
case Header.Msg_Type is
when Foo => Process_Foo (Foo_Msg);
when Bar => Process_Bar (Bar_Msg);
end case;
Size := To_Address (if Header.Msg_Type = Foo then Foo'Size else Bar'Size);
-- There's probably a better way to do this.
Buffer := To_Address(To_Integer (Buffer) + To_Integer (Size));
Bytes_Left := Bytes_Left - To_Integer (Size);
end;
end loop;
end Read;
What's the idiomatic way to march in a variable way across bytes in buffers and read the data in place?

I would keep it simple: just define a buffer array at the given address:
Buf : System.Storage_Elements.Storage_Array (0 .. Bytes_Left - 1)
with Address => Buffer;
and then parse the buffer, message-by-message. The example below provides a sketch of how I would solve this (disclaimer: did not test it).
message_reader.ads
with System;
with System.Storage_Elements;
with Interfaces;
package Message_Reader is
package SSE renames System.Storage_Elements;
-- NOTE: Not using an enum type eases the implementation of the parser (I think).
-- In particular for detecting unknown message types.
type Message_Type is new Interfaces.Unsigned_16;
Message_Type_Foo : constant Message_Type := 0;
Message_Type_Bar : constant Message_Type := 1;
-- Assume these work correctly and I don't need to do bit layout directly.
type Message_Header is record
Checksum : Interfaces.Integer_16;
Msg_Type : Message_Type;
end record
with Convention => C, Size => 32;
type Foo_Message is record
Header : Message_Header;
Tomatoes : Interfaces.Integer_32;
end record
with Convention => C, Size => 64;
type Bar_Message is record
Header : Message_Header;
Oranges : Interfaces.Integer_32;
Apples : Interfaces.Integer_32;
end record
with Convention => C, Size => 96;
Unknown_Message_Type : exception;
procedure Read
(Buffer : in System.Address;
Bytes_Left : in out SSE.Storage_Count);
private
use type SSE.Storage_Count;
pragma Compile_Time_Error
(System.Storage_Unit /= 8, "implementation expects a storage unit size of 8");
Foo_Msg_Size_Bytes : constant SSE.Storage_Count :=
Foo_Message'Size / System.Storage_Unit;
Bar_Msg_Size_Bytes : constant SSE.Storage_Count :=
Bar_Message'Size / System.Storage_Unit;
procedure Process_Bar (B : Bar_Message) is null;
procedure Process_Foo (F : Foo_Message) is null;
end Message_Reader;
message_reader.adb
with Ada.Unchecked_Conversion;
package body Message_Reader is
generic
type Chunk_Type is private;
procedure Read_Chunk
(Buffer : in SSE.Storage_Array;
Offset : in SSE.Storage_Offset;
Chunk : out Chunk_Type;
Success : out Boolean);
----------
-- Read --
----------
procedure Read
(Buffer : in System.Address;
Bytes_Left : in out SSE.Storage_Count)
is
Buf : SSE.Storage_Array (0 .. Bytes_Left - 1)
with Address => Buffer;
procedure Read_Header is new Read_Chunk (Message_Header);
procedure Read_Foo_Msg is new Read_Chunk (Foo_Message);
procedure Read_Bar_Msg is new Read_Chunk (Bar_Message);
Header : Message_Header;
Success : Boolean;
begin
loop
Read_Header (Buf, Buf'Last - Bytes_Left - 1, Header, Success);
if not Success then
exit; -- Not enough data left in buffer.
end if;
case Header.Msg_Type is
when Message_Type_Foo =>
declare
Foo : Foo_Message;
begin
Read_Foo_Msg (Buf, Buf'Last - Bytes_Left - 1, Foo, Success);
if not Success then
exit; -- Not enough data left in buffer.
end if;
Bytes_Left := Bytes_Left - Foo_Msg_Size_Bytes;
Process_Foo (Foo);
end;
when Message_Type_Bar =>
declare
Bar : Bar_Message;
begin
Read_Bar_Msg (Buf, Buf'Last - Bytes_Left - 1, Bar, Success);
if not Success then
exit; -- Not enough data left in buffer.
end if;
Bytes_Left := Bytes_Left - Bar_Msg_Size_Bytes;
Process_Bar (Bar);
end;
when others =>
raise Unknown_Message_Type;
end case;
end loop;
end Read;
----------------
-- Read_Chunk --
----------------
procedure Read_Chunk
(Buffer : in SSE.Storage_Array;
Offset : in SSE.Storage_Offset;
Chunk : out Chunk_Type;
Success : out Boolean)
is
Chunk_Type_Bytes : constant SSE.Storage_Count :=
Chunk_Type'Size / System.Storage_Unit;
subtype Chunk_Raw is SSE.Storage_Array (0 .. Chunk_Type_Bytes - 1);
function To_Chunk is new Ada.Unchecked_Conversion
(Source => Chunk_Raw, Target => Chunk_Type);
Slice_First : constant SSE.Storage_Offset := Offset;
Slice_Last : constant SSE.Storage_Offset := Offset + Chunk_Type_Bytes - 1;
begin
if Slice_Last <= Buffer'Last then
Chunk := To_Chunk (Buffer (Slice_First .. Slice_Last));
Success := True;
else
Success := False;
end if;
end Read_Chunk;
end Message_Reader;

Use can use a record with a Unchecked_Union aspect.
type Message (Msg_Type : Message_Type) is record
Header : Message_Header;
case Msg_Type is
when Foo =>
Tomatoes : Interfaces.Integer_16;
when Bar =>
Oranges : Interfaces.Integer_32;
Apples : Interfaces.Integer_32;
end case;
end record
with Unchecked_Union;
Please note the discriminant is not accessible when using Unchecked_Union.
Note : Tomatoes has not the same size in the C code and the Ada code you provided.

Related

Out parameter undefined

I'm currently stuck in creating two tasks inside of a procedure adding numbers of an array passed to the respective procedure.
My generic package looks like this:
generic
type Item_Type is private;
with function "+"(Left: Item_Type; Right: Item_Type) return Item_Type;
package Parallel_Algorithms is
type Array_Type is array(Natural range <>) of Item_Type;
type Array_Access_Type is access all Array_Type;
procedure Parallel_Sum(Input: Array_Access_Type; Result: out Item_Type);
end Parallel_Algorithms;
I implemented the Parallel_Sum Method the following way, being aware that the implementation is not perfect, nor thread safe.
procedure Parallel_Sum(Input: Array_Access_Type; Result: out Item_Type) is
Loop_Var: Integer:= 0;
task type T;
Task1, Task2 : T;
task body T is
begin
while Loop_Var < Input'Length loop
Result := Result + Input(Loop_Var);
Loop_Var := Loop_Var + 1;
end loop;
end T;
begin
-- Result := Temp;
end Parallel_Sum;
If I now run my main program the output of Result always ends up being something like 1918988326. Considering the elements inside of my array (1,2,3,4) that result is obviously wrong.
I read in another post that non altering an out type may result in undefined behaviour of the respective variable.
What would be the proper way to get the 'real' Result?
Upon looking at the problem more closely I see there are several issues to overcome. The tasks must accumulate their own totals, then those totals must be combined. Adding totals to an unprotected Result variable will produce a race condition which will result in undefined results.
Following is my approach to the problem.
------------------------------------------------------------------
-- Parallel Addition of Array Elements --
------------------------------------------------------------------
generic
type Element_Type is range <>;
package Parallel_Addition is
type Array_Type is array(Natural range <>) of Element_Type;
type Array_Access is access all Array_Type;
task type Adder is
Entry Set_Slice(Low, High : in Natural;
Item : in not null Array_Access);
end Adder;
protected Result is
procedure Accumulate(Item : in Element_Type);
function Report return Element_Type;
private
Sum : Integer := 0;
end Result;
end Parallel_Addition;
package body Parallel_Addition is
-----------
-- Adder --
-----------
task body Adder is
My_Array : Array_Access;
Id_Low, Id_High : Natural;
Sum : Integer := 0;
begin
accept Set_Slice(Low, High : in Natural;
Item : in not null Array_Access) do
Id_Low := Low;
Id_High := High;
My_Array := Item;
end Set_Slice;
for I in Id_Low..Id_High loop
Sum := Sum + Integer(My_Array(I));
end loop;
Result.Accumulate(Element_Type(Sum));
end Adder;
------------
-- Result --
------------
protected body Result is
----------------
-- Accumulate --
----------------
procedure Accumulate (Item : in Element_Type) is
begin
Sum := Sum + Integer(Item);
end Accumulate;
------------
-- Report --
------------
function Report return Element_Type is
begin
return Element_Type(Sum);
end Report;
end Result;
end Parallel_Addition;
------------------------------------------------------------------
-- Parallel_Addition Test --
------------------------------------------------------------------
with Ada.Text_IO; use Ada.Text_IO;
with Parallel_Addition;
procedure PA_Test is
package adders is new Parallel_Addition(Natural);
use adders;
Data : aliased Array_Type := (1,2,3,4,5,6,7,8,9,10);
T1, T2 : Adder;
begin
T1.Set_Slice(Low => 0, High => 4, Item => Data'Access);
T2.Set_Slice(Low => 5, High => 9, Item => Data'Access);
loop
if T1'Terminated and then T2'Terminated then
exit;
end if;
end loop;
put_Line("The sum is " & Integer'Image(Result.Report));
end PA_Test;

Copying an address from a pointer to a different memory address

I have a C DLL with a number of functions I'm calling from Delphi. One of the functions (say Func1) returns a pointer to a struct - this all works fine. The structs created by calling Func1 are stored in a global pool within the DLL. Using a second function (Func2) I get a pointer to a block of memory containing an array of pointers, and I can access the array elements using an offset.
I need to be able copy the address in the returned pointer for a struct (from Func1) to any of the memory locations in the array (from Func2). The idea is that I can build arrays of pointers to pre-defined structs and access the elements directly from Delphi using pointer offsets.
I tried using:
CopyMemory(Pointer(NativeUInt(DataPointer) + offset), PStruct, DataSize);
where DataPointer is the start of my array and PStruct is returned from Func1, but that doesn't copy the address I need.
In .NET it works using Marshal.WriteIntPtr and looking at the underlying code for this using Reflector I think I need something trickier than CopyMemory. Anyone got any ideas for doing this in Delphi?
Edit: This is part of a wrapper around vector structures returned from the R language DLL. I have a base vector class from which I derive specific vector types. I've got the wrapper for the numeric vector working, so my base class looks fine and this is where I get DataPointer:
function TRVector<T>.GetDataPointer: PSEXPREC;
var
offset: integer;
h: PSEXPREC;
begin
// TVECTOR_SEXPREC is the vector header, with the actual data behind it.
offset := SizeOf(TVECTOR_SEXPREC);
h := Handle;
result := PSEXPREC(NativeUInt(h) + offset);
end;
Setting a value in a numeric vector is easy (ignoring error handling):
procedure TNumericVector.SetValue(ix: integer; value: double);
var
PData: PDouble;
offset: integer;
begin
offset := GetOffset(ix); // -- Offset from DataPointer
PData := PDouble(NativeUInt(DataPointer) + offset);
PData^ := value;
end;
For a string vector I need to (i) create a base vector of pointers with a pre-specified length as for the numeric vector (ii) convert each string in my input array to an R internal character string (CHARSXP) using the R mkChar function (iii) assign the address of the character string struct to the appropriate element in the base vector. The string array gets passed into the constructor of my vector class (TCharacterVector) and I then call SetValue (see below) for each string in the array.
I should have thought of PPointer as suggested by Remy but neither that or the array approach seem to work either. Below is the code using the array approach from Remy and with some pointer vars for checking addresses. I'm just using old-fashioned pointer arithmetic and have shown addresses displayed for a run when debugging:
procedure TCharacterVector.SetValue(ix: integer; value: string);
var
PData: PSEXPREC;
offset: integer;
offset2: integer;
PTest: PSEXPREC;
PPtr: Pointer;
PPtr2: Pointer;
begin
offset := GetOffset(ix);
PPtr := PPointer(NativeUInt(DataPointer) + offset); // $89483D8
PData := mkChar(value); // $8850258
// -- Use the following code to check that mkChar is working.
offset2 := SizeOf(TVECTOR_SEXPREC);
PTest := PSEXPREC(NativeUInt(PData) + offset);
FTestString := FTestString + AnsiString(PAnsiChar(PTest));
//PPointerList(DataPointer)^[ix] := PData;
//PPtr2 := PPointer(NativeUInt(DataPointer) + offset); // Wrong!
PPointerArray(DataPointer)^[ix] := PData;
PPtr2 := PPointerArray(DataPointer)^[ix]; // $8850258 - correct
end;
I'd have thought the address in PData ($8850258) would now be in PPtr2 but I've been staring at this so long I'm sure I'm missing something obvious.
Edit2: The code for SetValue used in R.NET is as follows (ignoring test for null string):
private void SetValue(int index, string value)
{
int offset = GetOffset(index);
IntPtr stringPointer = mkChar(value);
Marshal.WriteIntPtr(DataPointer, offset, stringPointer);
}
From reflector, Marshal.WriteIntPtr uses the following C:
public static unsafe void WriteInt32(IntPtr ptr, int ofs, int val)
{
try
{
byte* numPtr = (byte*) (((void*) ptr) + ofs);
if ((((int) numPtr) & 3) == 0)
{
*((int*) numPtr) = val;
}
else
{
byte* numPtr2 = (byte*) &val;
numPtr[0] = numPtr2[0];
numPtr[1] = numPtr2[1];
numPtr[2] = numPtr2[2];
numPtr[3] = numPtr2[3];
}
}
catch (NullReferenceException)
{
throw new AccessViolationException();
}
}
You say you want to copy the struct pointer itself into the array, but the code you have shown is trying to copy the struct data that the pointer is pointing at. If you really want to copy just the pointer itself, don't use CopyMemory() at all. Just assign the pointer as-is:
const
MaxPointerList = 255; // whatever max array count that Func2() allocates
type
TPointerList = array[0..MaxPointerList-1] of Pointer;
PPointerList = ^TPointerList;
PPointerList(DataPointer)^[index] := PStruct;
Your use of NativeUInt reveals that you are using a version of Delphi that likely supports the {$POINTERMATH} directive, so you can take advantage of that instead, eg:
{$POINTERMATH ON}
PPointer(DataPointer)[index] := PStruct;
Or, use the pre-existing PPointerArray type in the System unit:
{$POINTERMATH ON}
PPointerArray(DataPointer)[index] := PStruct;

Delphi and using Teamspeak SDK read returned multi dim arrays

I'm trying to read returned arrays from the TeamSpeak3 SDK, some of the methods returns arrays that are null terminated and multi dimensional with a mix of data types.
What "delhpi" structure should I pass as parameter and how can I read the returned values back in the a matching structure? a la.
type
TDeviceInfo = record
DeviceId : string; // maybe an integer
DeviceName : string;
end;
TDeviceInfoArr = array of TDeviceInfo
// or maybe
TDeviceInfoArr = array of array[0..1] of string;
var
DeviceArr : array of TDeviceInfoArr;
This is what the SDK Documentation says.
To get a list of all available playback and capture devices for the specified mode, call
unsigned int ts3client_getPlaybackDeviceList(modeID, result);
const char* modeID;
char**** result;
unsigned int ts3client_getCaptureDeviceList(modeID, result);
const char* modeID;
char**** result;
Parameters
• modeID
Defines the playback/capture mode to use. For different modes there might be different device lists. Valid modes are returned by
ts3client_getDefaultPlayBackMode / s3client_getDefaultCaptureMode and ts3client_getPlaybackModeList / ts3client_getCaptureModeList.
• result
Address of a variable that receives a NULL-terminated array { { char* deviceName, char* deviceID }, { char* deviceName, char* deviceID }, ... , NULL }.
Unless the function returns an error, the elements of the array and the array itself need to be freed using ts3client_freeMemory.
Returns ERROR_ok on success, otherwise an error code as defined in public_errors.h. In case of an error, the result array is uninitialized and must not be released.
Example to query all available playback devices:
char * defaultMode;
if (ts3client_getDefaultPlayBackMode( & defaultMode) == ERROR_ok) {
char * * * array;
if (ts3client_getPlaybackDeviceList(defaultMode, & array) == ERROR_ok) {
for (int i = 0; array[i] != NULL; ++i) {
printf("Playback device name: %s\n", array[i][0]); /* First element: Device name */
printf("Playback device ID: %s\n", array[i][1]); /* Second element: Device ID */
/* Free element */
ts3client_freeMemory(array[i][0]);
ts3client_freeMemory(array[i][1]);
ts3client_freeMemory(array[i]);
}
ts3client_freeMemory(array); /* Free complete array */
} else {
printf("Error getting playback device list\n");
}
} else {
printf("Error getting default playback mode\n");
}
First of all, I'm going to ignore error handling because I think we handled that in your last question. And I'm going to assume that ts3client_getDefaultPlayBackMode presents no problems.
So that leaves ts3client_getPlaybackDeviceList. Import it like this:
function ts3client_getPlaybackDeviceList(modeID: PAnsiChar;
out result: PPPAnsiChar): Cardinal; cdecl; external '...';
You will likely need to define PPPAnsiChar.
type
PPPAnsiChar = ^PPAnsiChar;
PPAnsiChar = ^PAnsiChar;
You might find that the RTL already defines PPAnsiChar.
So, next to calling the function. First of all declare a variable to hold the array, and so others to help iterate:
var
arr, myarr: PPPAnsiChar;
p: PPAnsiChar;
Then call the function:
ts3client_getPlaybackDeviceList(modeID, arr);
myarr := arr;
while myarr^ <> nil do
begin
p := myarr^;
Writeln('Playback device name: ', p^);
ts3client_freeMemory(p^);
inc(p);
Writeln('Playback device ID: ', p^);
ts3client_freeMemory(p^);
ts3client_freeMemory(myarr^);
inc(myarr);
end;
ts3client_freeMemory(arr);
This code is really quite vile I'm sure that you will agree. If you have a modern version of Delphi then you can enable pointer math to make it read better.
{$POINTERMATH ON}
ts3client_getPlaybackDeviceList(modeID, arr);
i := 0;
while arr[i] <> nil do
begin
Writeln('Playback device name: ', arr[i][0]);
Writeln('Playback device ID: ', arr[i][1]);
ts3client_freeMemory(arr[i][0]);
ts3client_freeMemory(arr[i][1]);
ts3client_freeMemory(arr[i]);
inc(i);
end;
ts3client_freeMemory(arr);
Although this code is better, it will never win a beauty contest.
Remember that I've neglected all error checking. You'll need to add that.
Based on David's suggesstions I found the following code working, thanks David!
{$POINTERMATH ON}
procedure TfrmMain.RequestPlaybackDevices;
var
arr, myarr: PPPAnsiChar;
p: PPAnsiChar;
defaultmode : PAnsiChar;
i : Integer;
begin
try
ts3check(ts3client_getDefaultPlayBackMode(#defaultmode));
ts3check(ts3client_getPlaybackDeviceList(defaultMode, #arr));
try
i := 0;
while arr[i] <> nil do
begin
LogMsg(format('Playback device name: %s',[UTF8ToUnicodeString(arr[i][0])]));
LogMsg(format('Playback device ID: %s',[UTF8ToUnicodeString(arr[i][1])]));
ts3client_freeMemory(arr[i][0]);
ts3client_freeMemory(arr[i][1]);
ts3client_freeMemory(arr[i]);
inc(i);
end;
finally
ts3client_freeMemory(arr);
end;
except
on e: exception do LogMsg(Format('Error RequestPlaybackDevices: %s', [e.Message]));
end;
end;
{$POINTERMATH OFF}

String comparison in Delphi

I have two strings, which I need to compare for equality.
String 1 is created in this way:
var
inBuf: array[0..IN_BUF_SIZE] of WideChar;
stringBuilder : TStringBuilder;
mystring1:string;
...
begin
stringBuilder := TStringBuilder.Create;
for i := startOfInterestingPart to endOfInterestingPart do
begin
stringBuilder.Append(inBuf[i]);
end;
mystring1 := stringBuilder.ToString();
stringBuilder.Free;
String 2 is a constant string 'ABC'.
When string 1 is displayed in a debug console, it is equal to 'ABC'. But the comparisons
AnsiCompareText(mystring1, 'ABC')
mystring1 = 'ABC'
CompareStr(mystring1, 'ABC')
all report inequality.
I suppose that I need to convert string 2 ('ABC') to the same type as the string 1.
How can I do that?
Update 26.09.2012:
aMessage is displayed in the log output as {FDI-MSG-START-Init-FDI-MSG-END}
Here's the code for printing the length of strings:
StringToWideChar('{FDI-MSG-START-Init-FDI-MSG-END}', convString, iNewSize);
...
OutputDebugString(PChar('Len (aMessage): ' + IntToStr(Length(aMessage))));
OutputDebugString(PChar('Len (original constant): ' + IntToStr(Length('{FDI-MSG-START-Init-FDI-MSG-END}'))));
OutputDebugString(PChar('Len (convString): ' + IntToStr(Length(convString))));
And here's the log output:
[3580] Len (aMessage): 40
[3580] Len (original constant): 32
[3580] Len (convString): 0
It looks like you're keeping garbage data in your wide string after the meaningful part, in your update, Length(aMessage) returns 40, while your source string's length is 32.
In Delphi a wide string is COM BSTR compatible, meaning it can hold null characters, a null does not terminate it, it keeps its length at a negative offset of the character data. A possible null character in it helps it to be converted to other string types, but it doesn't alter its own termination.
Consider the below,
const
Source = '{FDI-MSG-START-Init-FDI-MSG-END}';
var
ws: WideString;
size: Integer;
begin
size := 40;
SetLength(ws, size);
StringToWideChar(Source, PWideChar(ws), size);
// the below assertion fails when uncommented
// Assert(CompareStr(Source, ws) = 0);
ws := PWideChar(ws); // or SetLength(ws, Length(Source));
// this assertion does not fail
Assert(CompareStr(Source, ws) = 0);
end;

replacement for luaL_getMetaTable

I want to enable Lua-Scripting (Lua 5.1) in my Delphi application. For this purpose I use the header Files of Thomas Lavergne.
Now I try to register a userdata type following this example: http://www.lua.org/pil/28.2.html
At the "new array function" it uses the command *luaL_getmetatable*.
static int newarray (lua_State *L) {
int n = luaL_checkint(L, 1);
size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double);
NumArray *a = (NumArray *)lua_newuserdata(L, nbytes);
luaL_getmetatable(L, "LuaBook.array");
lua_setmetatable(L, -2);
a->size = n;
return 1; /* new userdatum is already on the stack */
}
Unfortunately the *luaL_getmetatable* Function is marked al old at my header File and commented out. I tried to activate it again but as expected I will get an error because the dll entrancepoint couldn't be found.
This is the Delphi-translation of that example (using another non array datatype)
Type
tMyType = tWhatever;
pMyType = ^tMyType;
{...}
Function newusertype(aState : pLua_State) : LongInt; cdecl;
Var
NewData : pMyType;
Begin
Result := 0;
NewData := lua_newuserdata(aState, SizeOf(tMyType ));
NewData^ := GetInitValue;
luaL_getMetaTable(aState, 'myexcample.mytype'); // Error/unknown function
lua_setmetatable(aState, -2);
Result := 1;
End;
Now I'm looking for an replacement of luaL_getMetaTable. I haven't found any information about one. In fact I haven't found any information that luaL_getMetaTable is outdated but it seems to be :(.
use lua_newmetatable(aState, 'myexample.mytype'). The thing is (if you only want to continue if the metatable already exists) you'll need to evaluate whether it returns a 0! If it returns 0, then it's wanting to create the metatable... in which case you can lua_pop(aState, 1).
Just remember that lua_newmetatable is a function returning an Integer (which in reality should be a Boolean).
Otherwise you can wait a few weeks for me to release Lua4Delphi version 2, which makes all of this super easy (and the Professional version actually automates the registration of Delphi Types and Instances with Lua)

Resources