I am attempting to write two functions that add and remove a folder from a IShellLibrary. I started with this, but the function produces an exception in System._IntfClear:
First chance exception at $000007FEFE 168BC4. Exception class $C0000005 with Message 'c0000005 ACCESS_VIOLATION'.
The SHAddFolderPathToLibrary is the line that causes the exception.
I guess I need to add the library name to the function?
function AddFolderToLibrary(AFolder: string): HRESULT;
{ Add AFolder to Windows 7 library. }
var
plib: IShellLibrary;
begin
Result := CoCreateInstance(CLSID_ShellLibrary, nil, CLSCTX_INPROC_SERVER,
IID_IShellLibrary, plib);
if SUCCEEDED(Result) then
begin
Result := SHAddFolderPathToLibrary(plib, PWideChar(AFolder));
end;
end;
function RemoveFolderFromLibrary(AFolder: string): HRESULT;
{ Remove AFolder from Windows 7 library. }
var
plib: IShellLibrary;
begin
Result := CoCreateInstance(CLSID_ShellLibrary, nil, CLSCTX_INPROC_SERVER,
IID_IShellLibrary, plib);
if SUCCEEDED(Result) then
begin
Result := SHRemoveFolderPathFromLibrary(plib, PWideChar(AFolder));
end;
end;
The problem here is that the Embarcadero engineer who translated SHAddFolderPathToLibrary does not understand COM reference counting, and how it is handled by different compilers.
Here's how SHAddFolderPathToLibrary is implemented in the C++ header file Shobjidl.h. It's actually an inline wrapper of other core API calls:
__inline HRESULT SHAddFolderPathToLibrary(_In_ IShellLibrary *plib,
_In_ PCWSTR pszFolderPath)
{
IShellItem *psiFolder;
HRESULT hr = SHCreateItemFromParsingName(pszFolderPath, NULL,
IID_PPV_ARGS(&psiFolder));
if (SUCCEEDED(hr))
{
hr = plib->AddFolder(psiFolder);
psiFolder->Release();
}
return hr;
}
And the Delphi translation is very faithful, indeed too faithful:
function SHAddFolderPathToLibrary(const plib: IShellLibrary;
pszFolderPath: LPCWSTR): HResult;
var
psiFolder: IShellItem;
begin
Result := SHCreateItemFromParsingName(pszFolderPath, nil, IID_IShellItem,
psiFolder);
if Succeeded(Result) then
begin
Result := plib.AddFolder(psiFolder);
psiFolder._Release();
end;
end;
The problem is the call to _Release. The Delphi compiler manages reference counting, and so this explicit call to _Release is bogus and should not be there. Since the compiler will arrange for a call to _Release, this extra one simply unbalances the reference counting. The reason why _AddRef and _Release are prefixed with _ is to remind people not to call them and to let the compiler do that.
The call to Release in the C++ version is accurate because C++ compilers don't automatically call Release for you unless you wrap the interface in a COM smart pointer. But the Embarcadero engineer has blindly copied it across and you are left with the consequences. Clearly this code has never even been executed by the Embarcadero engineers.
You'll need to supply your own corrected implementation of this function. And also any other erroneously translated function. Search for _Release in the ShlObj unit, and remove them in your corrected versions. There are other bugs in the translation, so watch out. For example, SHLoadLibraryFromItem (and others) declare local variable plib: ^IShellLibrary which should be plib: IShellLibrary.
I submitted a QC report: QC#117351.
I have invented my own algorithm that I propose here, non-recursive, which takes up very little memory and removes folders of any depth and file (s) with special attributes. Unfortunately the comments are still in Italian.
To explain how it works: you have to initialize the deletion of the file or folder with the procedure InitDelT (Dir: String; Var DelTRec: TDelTRec); and run several times, for example in a sort of loop, the function DelT (Var DelTRec: TDelTRec): Byte;, which returns:
2 -> Deletion completed successfully.
3 -> Deletion failed.
The DelTRec variable: TDelTRec contains:
PathName, BaseDir, Msg: String;
Status: Byte;
{Status: 0 -> Deleting (no items deleted yet).
1 -> Deleting (1 item just deleted).
2 -> Deletion completed successfully.
3 -> Deletion failed}.
Unit DelTU;
Interface
Type TDelTRec=Record
PathName,BaseDir,Msg:String;
Status:Byte;
{Status: 0 -> Eliminazione in corso (nessun elemento ancora eliminato).
1 -> Eliminazione in corso (1 elemento appena eliminato).
2 -> Eliminazione terminata con successo.
3 -> Eliminazione fallita}
End;
Function KeepExtendedDir (Dir:String):String;
{Preleva la Dir non normalizzata
(con BACKSLASH) da Dir.
NOTE: Non effettua alcun accesso ad UNITà A DISCO}
Function KeepNormDir (Dir:String):String;
{Preleva la Dir normalizzata
(senza BACKSLASH) da Dir.
NOTE: Non effettua alcun accesso ad UNITà A DISCO}
Function GetPathNameDir (PathName:String):String;
{Ritorna l' UNITà ed il PERCORSO DI PathName}
Procedure FileSplit (FileName:String;
Var Drive,Dir,Name,Ext:String);
{Scompone un PERCORSO DI FILE FileName
IN UNITà (DRIVE), Dir (Dir), nome (Name)
ed estensione (Ext).
NOTE: Non effettua alcun accesso ad UNITà A DISCO}
Procedure FSplit (FileName:String;
Var Dir,Name,Ext:String);
{Scompone un PERCORSO DI FILE FileName
Path (Dir), nome (Name)
ed estensione (Ext).
NOTE: Non effettua alcun accesso ad UNITà A DISCO}
Function Is_Drive_Or_Root (Dir:String):Boolean;
{Verifica Se la Dir specificata da Dir è
una ROOT Dir o un DRIVE (IN questo caso ritorna TRUE).
Ritorna FALSE Se Dir è una Sub-DIRECTORY}
Function File_Exists_Sub (FileName:String;Attr:Integer;
Var Attr_Read:Integer):Boolean;
{Verifica che un FILE o una Dir FileName esista
ed abbia attributi compresi IN Attr.
Se FileName ha uno o più attributi che differiscono da Attr, ritorna FALSE.
Se FileName non ha attributi, ritorna TRUE.
Ritorna FALSE solo IN caso DI ERRORE,
altrimenti Attr_Read contiene gli attributi DI FileName.
NOTE: Per trovare qualsiasi FILE:
Attr= faAnyFile-
faVolumeId-
faDirectory.
Per trovare qualsiasi FILE E DIRECTORY:
Attr= faAnyFile-
faVolumeId.
Per trovare qualsiasi DIRECTORY:
Found:=File_Exists_Sub(FileName,faAnyFile-faVolumeId,Attr_Read) AND
((Attr_Read AND faDirectory)<>0)}
Function File_Exists (FileName:String):Boolean;
(* Controlla che FileName sia un FILE esistente *)
Function Dir_Exists (FileName:String):Boolean;
(* Controlla che FileName sia una DIRECTORY esistente *)
Function FDel (Source:String):Boolean;
(* Rimuove qualsiasi file, anche con attributi speciali;
non imposta ErrorMsg *)
Function RmDir (Source:String):Boolean;
(* Rimuove qualsiasi directory vuota, anche con attributi speciali;
non imposta ErrorMsg *)
Procedure InitDelT (Dir:String;
Var DelTRec:TDelTRec);
{Inizializzazione funzione "remove not empty folder" alias DelT().
Dir è il percorso assoluto della cartella da rimuovere;
può essere specificato anche senza il backslash finale.
Nel caso Dir non esista, questa funzione disabilita la rimozione;
altrimenti essa potrà avvenire in background, chiamando DelT()}
Function DelT (Var DelTRec:TDelTRec):Byte;
{Funzione "remove not empty folder" alias DelT().
La rimozione potrà avvenire in background, chiamando DelT() dopo
aver inizializzato DelTRec con InitDelT().
Ritorna: 0 -> Eliminazione in corso (nessun elemento ancora eliminato).
1 -> Eliminazione in corso (1 elemento appena eliminato).
2 -> Eliminazione terminata con successo.
3 -> Eliminazione fallita.
ALGORITMO:
---------:
- specificare full-path-name PathName con filtro *.*;
es.: c:\programs.pf\graphic.pf\*.*
- Copiare nella base-path BaseDir il percorso della cartella da rimuovere;
es.: c:\programs.pf
- RemoveDir <- False.
- Preleva FileName1 e Dir da PathName.
- Se FileName1="<Rm_Dir>":
- RemoveDir <- True.
- Preleva FileName1 e Dir da Dir (normalizzata).
- NoSuchFile1 <- False
- Cerca la prima ricorrenza di FileName1 in Dir.:
- Imposta NoSuchFile1 <- True, se non esiste.
- NoSuchFile2 <- True
- SetFileName2 <- False
- Se NoSuchFile1 = False:
- Cerca il file o dir. successivo FileName2 in Dir:
- Imposta NoSuchFile2 <- True, se non esiste.
- Se RemoveDir=True:
- Rimuove la dir. FileName1
- Se Dir=BaseDir, ha finito.
- SetFileName2 <- True
- Se RemoveDir=False:
- Se FileName1 è un file:
- Rimuove il file FileName1.
- SetFileName2 <- True
- Se FileName1 è una dir.:
- Imposta PathName con Dir., FileName1 e *.*
- Se (NoSuchFile2 = False) E SetFileName2:
- Se FileName2 è un file, imposta PathName con Dir. e FileName2
- Se FileName2 è una dir., imposta PathName con Dir., FileName2 e *.*
- Se (NoSuchFile2 = True) E SetFileName2 O
(NoSuchFile1 = True):
- Imposta PathName con Dir. e "<Rm_Dir>"}
{-----------------------------------------------------------------------}
Implementation
Uses SysUtils;
Function KeepExtendedDir(Dir:String):String;
Var Len:Integer;
Begin
Len:=Length(Dir);
If (Len>0) And Not (Dir[Len] In [':','\']) Then
KeepExtendedDir:=Dir+'\'
Else
KeepExtendedDir:=Dir;
End;
Function KeepNormDir(Dir:String):String;
Var Len:Integer;
Begin
Len:=Length(Dir);
If (Len>1) And
(Dir[Len]='\') And
(Dir[Len-1]<>':') Then
KeepNormDir:=Copy(Dir,1,Len-1)
Else
KeepNormDir:=Dir;
End;
Function GetPathNameDir(PathName:String):String;
Var Index:Integer;
Begin
Index:=Length(PathName);
While (Index>0) And Not (PathName[Index] In ['\',':']) Do
Dec(Index);
GetPathNameDir:=Copy(PathName,1,Index);
End;
Procedure FileSplit(FileName:String;
Var Drive,Dir,Name,Ext:String);
Var Ch:Char;
Index,Flag:Integer;
Begin
Drive:='';
Dir:='';
Name:='';
Ext:='';
Flag:=0;
Index:=Length(FileName);
While Index>0 Do
Begin
Ch:=FileName[Index];
Case Ch Of
'\':If Flag<3 Then
Flag:=2;
':':Flag:=3;
'.':If Flag=0 Then
Flag:=1;
End;
Case Flag Of
0:Name:=Ch+Name;
1:If Ext='' Then
Begin
Ext:=Ch+Name;
Name:='';
End
Else
Name:=Ch+Name;
2:Dir:=Ch+Dir;
3:Drive:=Ch+Drive;
End;
Dec(Index);
End;
End;
Procedure FSplit(FileName:String;
Var Dir,Name,Ext:String);
Var Drive:String;
Begin
FileSplit(FileName,Drive,Dir,Name,Ext);
Dir:=Drive+Dir;
End;
Function Is_Drive_Or_Root(Dir:String):Boolean;
Const Special_Chars:Array[Boolean] Of Char=(':','\');
Var Len:Integer;
Begin
Len:=Length(Dir);
Is_Drive_Or_Root:=((Len=1) Or (Len=2) Or (Len=3) And (Dir[2]=':')) And
(Dir[Len]=Special_Chars[Odd(Len)]);
End;
Function File_Exists_Sub(FileName:String;Attr:Integer;
Var Attr_Read:Integer):Boolean;
(* per trovare qualsiasi FILE:
Attr= faAnyFile-
faVolumeId-
faDirectory *)
Var TempOut:Boolean;
SR:TSearchRec;
Begin
Attr_Read:=0;
TempOut:=((Attr And faDirectory)<>0) And
Is_Drive_Or_Root(FileName);
If Not TempOut And
(FindFirst(FileName,Attr,SR)=0) Then
Begin
TempOut:=True;
Attr_Read:=SR.Attr;
FindClose(SR);
End;
File_Exists_Sub:=TempOut;
End;
Function File_Exists(FileName:String):Boolean;
Var Attr_Read:Integer;
Begin
File_Exists:=File_Exists_Sub(FileName,SysUtils.faAnyFile-
SysUtils.faVolumeId-
SysUtils.faDirectory,
Attr_Read);
End;
Function Dir_Exists(FileName:String):Boolean;
Var Attr_Read:Integer;
Begin
Dir_Exists:=File_Exists_Sub(FileName,SysUtils.faAnyFile-
SysUtils.faVolumeId,
Attr_Read) And
((Attr_Read And faDirectory)<>0);
End;
Function FDel(Source:String):Boolean;
Var Attr:Integer;
Begin
FDel:=False;
Source:=KeepNormDir(Source);
Attr:=SysUtils.FileGetAttr(Source);
If (Attr And SysUtils.faDirectory)=0 Then
Begin
If (Attr And (SysUtils.faReadOnly+
SysUtils.faHidden+
SysUtils.faSysFile))<>0 Then
SysUtils.FileSetAttr(Source,
Attr And Not (SysUtils.faReadOnly+
SysUtils.faHidden+
SysUtils.faSysFile));
FDel:=DeleteFile(Source);
End;
End;
Function RmDir(Source:String):Boolean;
Var Attr:Integer;
Begin
RmDir:=False;
Source:=KeepNormDir(Source);
Attr:=SysUtils.FileGetAttr(Source);
If (Attr And SysUtils.faDirectory)<>0 Then
Begin
If (Attr And (SysUtils.faReadOnly+
SysUtils.faHidden+
SysUtils.faSysFile))<>0 Then
SysUtils.FileSetAttr(Source,
Attr And Not (SysUtils.faReadOnly+
SysUtils.faHidden+
SysUtils.faSysFile));
RmDir:=RemoveDir(Source);
End;
End;
Procedure InitDelT(Dir:String;
Var DelTRec:TDelTRec);
Begin
With DelTRec Do
Begin
PathName:=KeepExtendedDir(Dir)+'*.*';
Dir:=KeepNormDir(Dir);
Status:=3 And -Byte(Not Dir_Exists(Dir));
BaseDir:=GetPathNameDir(Dir);
Msg:='';
End;
End;
Function DelT(Var DelTRec:TDelTRec):Byte;
Var RemoveDir,SuchFile1,SuchFile2,SetFileName2,FF:Boolean;
Dir,Name,Ext:String;
SR1,SR2:TSearchRec;
Begin
With DelTRec Do
Begin
If Status<2 Then
Begin
Status:=0;
RemoveDir:=False;
FSplit(PathName,Dir,Name,Ext);
If Name+Ext='<Rm_Dir>' Then
Begin
RemoveDir:=True;
FSplit(KeepNormDir(Dir),Dir,Name,Ext);
End;
FF:=FindFirst(Dir+'*.*',
SysUtils.faAnyFile-
SysUtils.faVolumeId,SR2)=0;
SuchFile1:=FF;
While SuchFile1 And
((SR2.Name='.') Or (SR2.Name='..')) Do
SuchFile1:=FindNext(SR2)=0;
SuchFile2:=False;
SetFileName2:=False;
If SuchFile1 Then
Begin
SR1:=SR2;
SuchFile2:=FindNext(SR2)=0;
If RemoveDir Then
Begin
Msg:=Dir+Name+Ext;
If Not RmDir(Msg) Then
Status:=3
Else
If Dir=BaseDir Then
Status:=2
Else
Status:=1;
SetFileName2:=True;
End
Else
If (SR1.Attr And SysUtils.faDirectory)=0 Then
Begin
Msg:=Dir+SR1.Name;
If FDel(Msg) Then
Status:=1
Else
Status:=3;
SetFileName2:=True;
End
Else
PathName:=Dir+SR1.Name+'\*.*';
End;
If SuchFile2 And SetFileName2 Then
If (SR2.Attr And SysUtils.faDirectory)=0 Then
PathName:=Dir+SR2.Name
Else
PathName:=Dir+SR2.Name+'\*.*';
If Not SuchFile2 And SetFileName2 Or Not SuchFile1 Then
PathName:=Dir+'<Rm_Dir>';
If FF Then
FindClose(SR2);
End;
DelT:=Status;
End;
End;
End.
This is an example (DelTUT.DPR):
program DelTUT;
{$APPTYPE CONSOLE}
uses SysUtils,
DelTU in 'DelTU.pas';
Var DelTRec:TDelTRec;
Dir:String;
begin
{ TODO -oUser -cConsole Main : Insert code here }
WriteLn('Insert the full path-name of the folder to remove it:');
ReadLn(Dir);
WriteLn('Press ENTER to proceed ...');
InitDelT(Dir,DelTRec);
WriteLn('Removing...');
While Not (DelT(DelTRec) In [2,3]) Do
Write(#13,DelTRec.Msg,#32);
WriteLn;
If DelTRec.Status=3 Then
WriteLn('Error!')
Else
WriteLn('Ok.')
end.
Related
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.
I'm new to Eiffel and I'm trying to use the LINKED_LIST class for organizing instances of other class "MONOMIO" I've made. I added a function for ordering this elements and I use the remove and the cursor movement features and when I try to execute the code it raises an exception saying that the objects contained should be readable and writable. I would like to know how to do it, this is my class:
class
MONOMIO
feature --Initialization
make (coef:INTEGER; expX:INTEGER; expY:INTEGER)
do
coeficiente := coef
exponenteX := expX
exponenteY := expY
end
feature
evaluar(valX: INTEGER; valY: INTEGER): REAL_64
do
Result := coeficiente*(valX^exponenteX)*(valY^exponenteY)
end;
coeficiente: INTEGER;
exponenteX: INTEGER;
exponenteY: INTEGER;
feature --setter
set_coeficiente(val: INTEGER)
do
coeficiente := val
end;
end
I think the exception raises because of this feature I've made for a class that has as a feature the LINKED_LIST[MONOMIO] and it's called "contenido":
simplificar
local
tamanio_polinomio: INTEGER -- Número de monomios que tiene el polinomio
contador: INTEGER
monomio_a_comparar: MONOMIO -- Auxiliar
coeficiente_total:INTEGER -- Auxiliar
indice_monomio_en_revision:INTEGER
do
from
contenido.start
indice_monomio_en_revision := 0
tamanio_polinomio := contenido.count
until
indice_monomio_en_revision = tamanio_polinomio
loop
contenido.start
contenido.move (indice_monomio_en_revision)
monomio_a_comparar := contenido.item
from
contador := indice_monomio_en_revision
coeficiente_total := monomio_a_comparar.coeficiente
contenido.forth
until
contador = tamanio_polinomio
loop
if
(monomio_a_comparar.exponentex = contenido.item.exponentex) and
(monomio_a_comparar.exponentey = contenido.item.exponentey)
then
coeficiente_total := coeficiente_total + contenido.item.coeficiente
contenido.remove -- Mueve el cursor a la derecha
tamanio_polinomio := tamanio_polinomio - 1
contador := contador - 1
else
if
not contenido.islast
then
contenido.forth
end
end
contador := contador + 1
end
contenido.start
contenido.move (indice_monomio_en_revision)
contenido.item.set_coeficiente (coeficiente_total)
indice_monomio_en_revision := indice_monomio_en_revision + 1
end
end;
I hope anyone can help me with this problem. Thanks.
Suppose you have a list with 1 element. Then we enter the outer loop and move to the first element. Then we execute contador := indice_monomio_en_revision that is still 0 at this point and do contenido.forth. Now we are beyond the list because there is only one element. However contador = tamanio_polinomio is false (0 = 1), so we enter the inner loop and try to retrieve the second (non-existing) item. BOOM!
Other issues include:
There are multiple calls like contenido.start followed by contenido.move. You could use a single call to go_i_th instead.
Instead of counting number of items in the list I would look at the feature after. It tells when you reach an end of the list. It would simplify the logic of your loop (e.g. the call to islast would be removed) and let you to remove some local variables.
Taking the last point into account I would write the inner loop condition as
contenido.after
At least this would avoid the crash you experience. As to the logic, you may need to check features start, after, forth and remove to see what effect they have. The usual way to write loops in such cases is like
from
l.start
until
l.after
loop
... -- Use l.item
l.forth
end
In case of remove probably you do not need to call forth.
I am trying to scan nearby Bluetooth device for their MAC address using Winsock2 API interface.
Using code below I can found devices. But when I try to get their address using WSAAddressToString get a 10022 (WSAEINVAL) error say "An invalid argument was supplied".
The code is:
uses
winsock2, bt_helper;
procedure test;
var
ulFlags: u_long;
QuerySet: WSAQUERYSET;
QuerySize: u_long;
HLookup: THandle;
Result: Integer;
pCSAddr: pCSADDR_INFO;
pDeviceInfo: PBTH_DEVICE_INFO;
pResults: lpWSAQUERYSET;
Buffer: array [0..999] of Byte;
ProtocolInfo: WSAPROTOCOL_INFO;
ProtocolInfoSize: Integer;
BufferLength, AddressSize: LongWord;
addressAsString: array [0..1999] of Char;
begin
WSAStartup ($0202, Data);
ulFlags:=
LUP_CONTAINERS or //device inquiry
LUP_RETURN_NAME or //Friendly device name (if available) will be returned in lpszServiceInstanceName
LUP_RETURN_ADDR or //BTH_ADDR will be returned in lpcsaBuffer member of WSAQUERYSET
LUP_FLUSHCACHE ; //Flush the device cache for all inquiries, except for the first inquiry
QuerySize:= SizeOf(WSAQuerySet);
ZeroMemory (#QuerySet, SizeOf(QuerySet));
QuerySet.dwNameSpace:= NS_BTH;
QuerySet.dwSize:= QuerySize;
Result:= WSALookupServiceBegin(#QuerySet, ulFlags, HLookup);
if Result = 0 then
begin
while true do
begin
bufferLength:= sizeof(buffer);
pResults:= lpWSAQUERYSET(#buffer);
Result:= WSALookupServiceNext (HLOOKUP, ulFlags, bufferLength, pResults);
if Result = 0 then
begin
// Get the device info, name, address, etc.
Memo1.Lines.Add(Format('The service instance name is %s', [pResults.lpszServiceInstanceName]));
//pCSAddr.LocalAddr.lpSockaddr.sa_family:= AF_INET;
pCSAddr:= PCSADDR_INFO(pResults.lpcsaBuffer);
pDeviceInfo:= PBTH_DEVICE_INFO(pResults.lpBlob);
// Print the local Bluetooth device address ...
AddressSize:= sizeof(addressAsString);
if WSAAddressToString(pCSAddr.LocalAddr.lpSockaddr^, pCSAddr.LocalAddr.iSockaddrLength,
#ProtocolInfo, #AddressAsString, AddressSize) = 0
then
Memo1.Lines.Add(Format ('The localAddress: %s', [AddressAsString]))
else
Memo1.Lines.Add(Format ('WSAAddressToString for localAddress failed with error code %d: %s',
[WSAGetLastError, SysErrorMessage (WSAGetLastError)]));
// Print the remote Bluetooth device address ...
AddressSize:= sizeof(addressAsString);
IF WSAAddressToString(pCSAddr.RemoteAddr.lpSockaddr^, pCSAddr.RemoteAddr.iSockaddrLength,
#ProtocolInfo, #AddressAsString, Addresssize) = 0
then
Memo1.Lines.Add (Format ('The remote device address: %s', [AddressAsString]))
else
Memo1.Lines.Add (Format ('WSAAddressToString for remoteAddress failed with error code %d: %s',
[WSAGetLastError, SysErrorMessage(WSAGetLastError)]));
end
else
begin
Memo1.Lines.Add(SysErrorMessage(WSAGetLastError));
break;
end;
end;
end;
WSALookupServiceEnd(HLookup);
Here is the result inside memo:
The service instance name is BTDevice1
WSAAddressToString for localAddress failed with error code 10022: An invalid argument was supplied
WSAAddressToString for remoteAddress failed with error code 10022: An invalid argument was supplied
---------------------------------
No more results can be returned by WSALookupServiceNext
Use the following unit in order to compile:
unit bt_helper;
interface
uses
winsock2, Winapi.Windows;
const
BTH_MAX_NAME_SIZE = 248;
BTHPROTO_RFCOMM= 3;
BT_PORT_ANY = -1;
type
BTH_ADDR = int64;
SOCKADDR_BTH = packed record
addressFamily :word; // Always AF_BTH
btAddr :BTH_ADDR; // Bluetooth device address
serviceClassId :TGUID; // [OPTIONAL] system will query SDP for port
port :dword; // RFCOMM channel or L2CAP PSM
end;
BTH_COD = ULONG;
_BTH_DEVICE_INFO = record
flags: ULONG; // Combination BDIF_Xxx flags
address: BTH_ADDR; // Address of remote device.
classOfDevice: BTH_COD; // Class Of Device.
name: array [0..BTH_MAX_NAME_SIZE - 1] of CHAR; // name of the device
end;
{$EXTERNALSYM _BTH_DEVICE_INFO}
BTH_DEVICE_INFO = _BTH_DEVICE_INFO;
{$EXTERNALSYM BTH_DEVICE_INFO}
PBTH_DEVICE_INFO = ^BTH_DEVICE_INFO;
{$EXTERNALSYM PBTH_DEVICE_INFO}
TBthDeviceInfo = BTH_DEVICE_INFO;
PBthDeviceInfo = PBTH_DEVICE_INFO;
implementation
end.
If you read the documentation of WSAAddressToString closely you would have noticed this paragraph:
lpProtocolInfo [in, optional] A pointer to the WSAPROTOCOL_INFO
structure for a particular provider. If this is parameter is NULL, the
call is routed to the provider of the first protocol supporting the
address family indicated in the lpsaAddress parameter.
so instead of supplying a fake WSA_PROTOCOL info structure, you should pass in nil. The second problem is that you use SizeOf() to determine the length of the String buffer, this is incorrect and you should use Length():
AddressSize:= Length(addressAsString);
if WSAAddressToString(pCSAddr.LocalAddr.lpSockaddr^, pCSAddr.LocalAddr.iSockaddrLength,
nil, #AddressAsString, AddressSize) = 0 then
begin
SetLength(AddressAsString, AddressSize-1);// resize to returned length minus last null character
...
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}
My stored procedure looks like follows:
sqlQuery := 'DROP INDEX idArchivoIndex';
EXECUTE IMMEDIATE sqlQuery;
EXCEPTION --En caso de que no exista el índice capturamos la excepcion
WHEN index_not_exists THEN NULL; --y la ignoramos
sqlQuery := 'CREATE INDEX idArchivoIndex'||
' ON '||qusuario||' (id_archivo)';
EXECUTE IMMEDIATE sqlQuery;
doresetvalidacion(qusuario, idarchivo);
IF (tipoDependencia = 'PEC') THEN
dovalidapec(qusuario,qaniofiscal,idarchivo,imprimirMensajes);
COMMIT;
ELSIF (tipoDependencia = 'SAGARPA') THEN
dovalidacionpec(qusuario,qaniofiscal,idarchivo,imprimirMensajes);
COMMIT;
END IF;
If the exception is not raised the procedure just drops the index but no index is recreated ! I thought that this part of the code
EXCEPTION
WHEN index_not_exists THEN NULL;
Handled the error and then continue with the code below it. Now that I see the results what's after the EXCEPTION is executed if and only if the exception was raised.
What I want is to simplify my code, I don't want to copy-paste the same block of code before the EXCEPTION clause just to make it work as I expect. Is there a way to achieve it? Maybe with a nested BEGIN ... END block? Or will I have to make a separate procedure to reuse code?
Cheers.
UPDATE
create or replace
PROCEDURE DOVALIDAINFORMACION
(
QARCHIVO IN VARCHAR2
, QUSUARIO IN VARCHAR2
, QANIOFISCAL IN VARCHAR2
) AS
imprimirMensajes CHAR;
tipoDependencia VARCHAR2(25);
idArchivo NUMBER;
sqlQuery VARCHAR2(100);
index_not_exists EXCEPTION;
PRAGMA EXCEPTION_INIT(index_not_exists, -1418);
BEGIN
sqlQuery := 'DROP INDEX idArchivoIndex';
EXECUTE IMMEDIATE sqlQuery;
----------------------
EXCEPTION --En caso de que no exista el índice capturamos la excepcion
WHEN index_not_exists THEN --y la ignoramos
NULL;
END;
----------------------
sqlQuery := 'CREATE INDEX idArchivoIndex'||
' ON '||qusuario||' (id_archivo)';
EXECUTE IMMEDIATE sqlQuery;
doresetvalidacion(qusuario, idarchivo);
IF (tipoDependencia = 'PEC') THEN
dovalidapec(qusuario,qaniofiscal,idarchivo,imprimirMensajes);
COMMIT;
ELSIF (tipoDependencia = 'SAGARPA') THEN
dovalidacionpec(qusuario,qaniofiscal,idarchivo,imprimirMensajes);
COMMIT;
END IF;
END DOVALIDAINFORMACION;
But can't compile the procedure.
Error(32,3): PLS-00103: Se ha encontrado el símbolo "SQLQUERY"
Error(33,48): PLS-00103: Se ha encontrado el símbolo ";" cuando se esperaba uno de los siguientes: ) , * & = - + < / > at in is mod remainder not rem <an exponent (**)> <> or != or ~= >= <= <> and or like LIKE2_ LIKE4_ LIKEC_ between || member SUBMULTISET_
I suspect that are just missing an extra BEGIN in your updated code. An EXCEPTION clause always matches to a BEGIN and an END. In the code that you posted, the EXCEPTION matches the procedure's BEGIN. You need it to match the BEGIN of the nested PL/SQL block.
create or replace
PROCEDURE DOVALIDAINFORMACION
(
QARCHIVO IN VARCHAR2
, QUSUARIO IN VARCHAR2
, QANIOFISCAL IN VARCHAR2
) AS
imprimirMensajes CHAR;
tipoDependencia VARCHAR2(25);
idArchivo NUMBER;
sqlQuery VARCHAR2(100);
index_not_exists EXCEPTION;
PRAGMA EXCEPTION_INIT(index_not_exists, -1418);
BEGIN
BEGIN
sqlQuery := 'DROP INDEX idArchivoIndex';
EXECUTE IMMEDIATE sqlQuery;
EXCEPTION --En caso de que no exista el índice capturamos la excepcion
WHEN index_not_exists THEN --y la ignoramos
NULL;
END;
sqlQuery := 'CREATE INDEX idArchivoIndex'||
' ON '||qusuario||' (id_archivo)';
EXECUTE IMMEDIATE sqlQuery;
doresetvalidacion(qusuario, idarchivo);
IF (tipoDependencia = 'PEC') THEN
dovalidapec(qusuario,qaniofiscal,idarchivo,imprimirMensajes);
COMMIT;
ELSIF (tipoDependencia = 'SAGARPA') THEN
dovalidacionpec(qusuario,qaniofiscal,idarchivo,imprimirMensajes);
COMMIT;
END IF;
END DOVALIDAINFORMACION;
As an aside, it seems odd to drop and then immediately re-create an index in a PL/SQL block. If this is somehow related to your question about recreating an index after a load, I'm afraid that you may have misunderstood my answer. In my earlier answer, I was pointing out that it may be more efficient to drop the index, load your 10 million rows of data, and then re-create the index. Assuming that the loads are happening in the stored procedure calls you are making in this code, you would want the index to be re-created after the loads are complete.