Un modo per lavorare con Visual FoxPro e le strutture API

 

© Articolo: Emanuele Cerlini

Maggio 2004 - Riproduzione vietata - English version

File allegato: rpstruct.zip (ultimo aggiornamento: 18 aprile 2006)

rAPIdStructure è un adattatore per Strutture API e richiede VFP 7 o superiore.

Versione 1.10

- Proprietà aggiunte: cExcludeList, lAutoFar
- Parametro aggiunto: nMemAddr a WriteMemory()
- Rimosso il parametro obsoleto lAllInCharString di SplitStruct(), conseguenza di un modo errato di dichiarare alcune Dll.
- Aggiunto il supporto per più elementi nella stessa riga, per le strutture C.
- Corretto un bug nelle array di BYTE quando lAutoArray = .T., per le strutture C.
- Riconsiderato il conteggio dei bytes per le strutture di bits.

  1. Premessa
  2. Strutture e adattamenti
  3. Suggerimenti
  4. Proprietà
  5. Funzioni
  6. Tipi di dati
  7. Esempi

Premessa

Prima di iniziare, premetto che io non so assolutamente niente di Visual C++ e praticamente nulla di Visual Basic. Quel poco che so di Visual FoxPro mi permette di chiedermi come mai, che siamo ormai alla versione 9.0, ancora non ci sia una funzione interna che gestisca le strutture di dati che lavorano con le funzioni API (Application Programming Interface).
La maggior parte di quello che segue l'ho sviluppato per deduzione logica - quindi ampiamente correggibile ed integrabile - dallo studio di documentazione, degli esempi presenti su http://www.news2news.com e della classe Struct di Christof Lange (e Mark Wilden, del 1999), tutte persone che ringrazio. Aggiungo anche William GC Steinford, il cui lavoro ho visto solo dopo la prima pubblicazione di questo documento, e la recente vfp2c32 di Christian Ehlscheid. Non per superbia o altre pretese, ma solo perché credo che ci sia ora la possibilità di semplificare ulteriormente l'interazione di VFP con queste strutture, mi sono permesso di sviluppare anch'io una classe, interamente in Fox, che vi prego di aiutarmi a migliorarla.
La mia idea principale è quella di prendere pari pari una definizione di struttura scritta in C++ o VB, riportarla in una variabile (o in un campo memo) di Fox, implementare con essa una classe nella quale vengono creati i membri della struttura come proprietà e da qui passare e ricevere i dati dalle Dll. Naturalmente occorre anche il supporto più ampio possibile: sub-strutture, pointers nel buffer di memoria, array di caratteri, di numeri e di strutture... E magari anche un'utilità che riporti facilmente nella programmazione i membri della struttura... Insomma, tutto quello che è utile a togliere quel senso di malessere che può prendere quando si guarda la documentazione di una funzione API e si trova che quel parametro è una struttura.

Forse mi spiego meglio con il codice del più banale degli esempi:

*!* codice valido da VFP 7.00
Local cMyDef As String, ;
	oStruct As Object, ;
	cSysTime As String 
*!* inizializzo
Set Procedure To rpstruct Additive
oStruct=Createobject("rAPIdStructure")
*!* dichiaro la funzione
Declare GetLocalTime In WIN32API ;
	STRING @ lpSystemTime
*!* riporto la struttura SYSTEMTIME così com'è dall'Help MSDN
TEXT to cMyDef NOSHOW
typedef struct _SYSTEMTIME {
	WORD wYear;
	WORD wMonth;
	WORD wDayOfWeek;
	WORD wDay;
	WORD wHour;
	WORD wMinute;
	WORD wSecond;
	WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME
ENDTEXT
With oStruct
*!* carico la struttura definita in C++
	.LoadCDef(m.cMyDef)
*!* creo la stringa
	cSysTime=.MakeStruct()
*!* chiamo la Dll
	GetLocalTime(@cSysTime)
*!* distribuisco il risultato nei membri
	.SplitStruct(m.cSysTime)
*!* il gioco è fatto
	?"Anno", .wYear
	?"Mese", .wMonth
	?"Giorno della settimana", .wDayOfWeek
	?"Giorno", .wDay
	?"Ora", .wHour
	?"Minuti", .wMinute
	?"Secondi", .wSecond
	?"Millisecondi", .wMilliseconds
*!* per riportare rapidamente i nomi dei membri, mentre provavo il codice, ho utilizzato .ClipMembers(), che me li copia negli appunti.
Endwith
Clear Dlls "GetLocalTime"
oStruct=.Null.
*!* fine esempio

Naturalmente non sempre è così facile. Tante sono le varianti di quello che è un vero e proprio blocco di codice nei linguaggi di origine e che qui sto tentando di adattare.
Rimandando a più dettagliati documenti presenti sul Web, per quanto ho capito, riporto alcuni minimi

Cenni sulle strutture nel linguaggio C (e VB), sulla conversione dei dati in VFP e riguardo gli adattamenti necessari per gli automatismi della classe rAPIdStructure.

Quando vogliamo richiamare da VFP una funzione scritta in linguaggio C, come ad esempio:

BOOL GetVolumeInformation(
	LPCTSTR lpRootPathName, // root directory
	LPTSTR lpVolumeNameBuffer, // volume name buffer
	DWORD nVolumeNameSize, // length of name buffer
	LPDWORD lpVolumeSerialNumber, // volume serial number
	LPDWORD lpMaximumComponentLength, // maximum file name length
	LPDWORD lpFileSystemFlags, // file system options
	LPTSTR lpFileSystemNameBuffer, // file system name buffer
	DWORD nFileSystemNameSize // length of file system name buffer
);

convertiamo i dati in modo che siano compatibili con Visual FoxPro e generalmente abbiamo a che fare con stringhe di testo o numeri:

Declare SHORT GetVolumeInformation In KERNEL32 As GetVolumeInformation ;
	STRING cRootPathName, ;
	STRING @ cVolumeNameBuffer, ;
	INTEGER nVolumeNameSize, ;
	INTEGER @ nVolumeSerialNumber, ;
	INTEGER @ nMaxComponentLength, ;
	INTEGER @ nFlags, ;
	STRING @ cFileSystemNameBuffer, ;
	INTEGER nFileSystemNameSize

ma capita a volte che ci si imbatta in un tipo di dati non direttamente convertibile:

VOID GetLocalTime(
	LPSYSTEMTIME lpSystemTime // system time
);

dove il parametro è un tipo di dato definito dall'utente cioè una struttura di dati passati però in un unico parametro. In VFP dobbiamo trattarlo come una stringa di caratteri:

Declare GetLocalTime In WIN32API ;
	STRING @ lpSystemTime

ma in questa stringa occorre lavorare sul codice numerico in bit in gruppi di uno, due, quattro o otto caratteri, quando non si tratta di una struttura interna alla prima. In definitiva, i punti fissi a cui ci si può agganciare sono due:

Adesso si tratta di identificare il tipo di dato di ogni singolo membro e la loro lunghezza (oltre quella dell'intera struttura) in bytes.
Consideriamo dunque alcuni tipi di dati di C++:

BOOL
normalmente si usa per i valori logici di Vero o Falso, ma si può valutare anche come numero dove 0 è falso e qualsiasi altro risultato è vero. Intero a 32 bit.
BYTE
intero a 8 bit senza segno +/- il cui valore varia da 0 a 255.
CHAR
sempre a 8 bit ma segnato, quindi il valore parte da -128 a +127.
TCHAR
8 bit, ma viene utilizzato spesso in matrici per ottenere stringhe di testo che hanno la particolarità di essere ANSI o Unicode a seconda di come viene chiamata la funzione API. Se Unicode vuole sostituito con WCHAR.
SHORT
intero a 16 bit segnato.
WORD
come SHORT ma non segnato.
WCHAR
16 bit utilizzati spesso in matrici per stringhe di testo sempre Unicode.
FLOAT
numero con virgola e precisione fino a 6 decimali. 32 bit.
LONG
intero a 32 bit segnato.
UINT
come LONG ma non segnato.
DOUBLE
numero con virgola e precisione fino a 15 decimale. 64 bit.
LONGLONG
intero a 64 bit segnato.
ULONGLONG
intero a 64 bit non segnato.

Prendiamo ora una struttura qualsiasi e ne misuriamo i bytes:

typedef struct _STARTUPINFO { 
	DWORD cb; 1|4 4 bytes
	LPTSTR lpReserved; 5|8 4
	LPTSTR lpDesktop; 9|12 4
	LPTSTR lpTitle; 13|16 4
	DWORD dwX; 17|20 4
	DWORD dwY; 21|24 4
	DWORD dwXSize; 25|28 4
	DWORD dwYSize; 29|32 4
	DWORD dwXCountChars; 33|36 4
	DWORD dwYCountChars; 37|40 4
	DWORD dwFillAttribute; 41|44 4
	DWORD dwFlags; 45|48 4
	WORD wShowWindow; 49|50 2
	WORD cbReserved2; 51|52 2
	LPBYTE lpReserved2; 53|56 4
	HANDLE hStdInput; 57|60 4
	HANDLE hStdOutput; 61|64 4
	HANDLE hStdError; 65|68 4
} STARTUPINFO, *LPSTARTUPINFO; totale: 68 bytes

ed ecco che troviamo già tipi di dati prima non citati. Questo perché, a differenza di VFP o VB, in quel linguaggio è possibile definire anche questi. Ad esempio, nel file di intestazione WINDEF.H di Visual C++ troviamo queste righe:

typedef unsigned long ULONG;
typedef unsigned long DWORD;

che definiscono due tipi di dati spesso utilizzati che hanno lo stesso tipo di valore e cioè un numero intero a 32 bit (LONG, 4 bytes) ma senza segno +/-, cioè possono essere da 0 a 4.294.967.295: cambia solo il nome!

Nonostante le infinite possibilità, ci sono alcune regole, scritte o no, che possono aiutarci:

Rimangono altre varianti, ma cito solo quella che viene supportata dalla classe (se lAutoFar è .T.) e cioè il caso di avere CHAR FAR * (supporto speciale per strutture Windows Sockets) come tipo di dato, lungo 4 bytes che punta ad un indirizzo di memoria (e qui mi comporto come per LPTSTR) oppure CHAR FAR * FAR * che punta sempre al buffer di memoria ma questo riporta una matrice con numero di elementi indefiniti che contengono altri indirizzi di memoria per stringhe di lunghezza variabile. In quest'ultimo caso, su cui non incide lAutoMemory, riporto la matrice degli indirizzi ridimensionandola a seconda del risultato. All'utente il compito di ottenere la stringa da ogni indirizzo.

Per risolvere in qualche modo il problema della varietà dei tipi di dati ho optato per prendere un elenco ufficiale da MSDN -> Platform SDK: Win32 API -> Data Types, con qualche modifica necessaria. La funzione DataTypes() restituisce una stringa di testo con la lista dei tipi di dati riconosciuti in base alla loro lunghezza in bit (8, 16, 32 e 64), per il linguaggio C.
Per quanto riguarda i dati di Visual Basic, vengono accettati i seguenti:
BOOLEAN, BYTE, DOUBLE, INTEGER, LONG, STRING, SINGLE

Qualsiasi dato non riconosciuto tra questi, nei 2 linguaggi, lo intendo come una sub-struttura e provoca quindi la creazione di un oggetto rAPIdStructure interno da caricare con la propria definizione di struttura, se non è tra quelle comprese nella stringa restituita da DataTypes() e queste sono abilitate. Se lUnknownAsStruct è .F., invece, ogni tipo di dato non riconosciuto la classe lo marca come numerico a 32 bit. Occorre vedere la guida di ogni struttura per conoscere che dati abbiamo. È anche possibile utilizzare ClipMembers() con i parametri 1, 2 o 3 per sapere come e quali tipi dati sono stati riconosciuti.

In questo caso:

typedef struct _SHFILEOPSTRUCT{ 
HWND hwnd;
UINT wFunc;
LPCTSTR pFrom;
LPCTSTR pTo;
FILEOP_FLAGS fFlags;
BOOL fAnyOperationsAborted;
LPVOID hNameMappings;
LPCSTR lpszProgressTitle;
} SHFILEOPSTRUCT, *LPSHFILEOPSTRUCT;

troviamo la riga

FILEOP_FLAGS fFlags; 

dove FILEOP_FLAGS non è una sub-struttura, ma solo un dato non riconosciuto, lungo 2 bytes (vedi Shellapi.h: typedef WORD FILEOP_FLAGS). Per cui, prima di caricare il tutto, cambieremo quella riga con questa:

WORD fFlags;

e non avremo problemi.

Nel caso seguente abbiamo 3 sub-strutture FILETIME, che, avendo lAllowIntStruct=.T., vengono caricate in automatico, altrimenti sono da caricare con struttura1.membro.LoadCDef(m.cVariabileDef), cioè:

oStruct.ftCreationTime.LoadCDef(m.cFileTime)

dove m.cFileTime è una variabile contenente la definizione della struttura FILETIME. Sarà poi possibile accedere ai membri della sub struttura in questo modo:

?oStruct.ftCreationTime.dwLowDateTime
typedef struct _WIN32_FIND_DATA {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
TCHAR cFileName[ MAX_PATH ];
TCHAR cAlternateFileName[ 14 ];
} WIN32_FIND_DATA, *PWIN32_FIND_DATA;

ma in quest'ultimo esempio abbiamo anche 2 matrici TCHAR:

TCHAR cFileName[ MAX_PATH ]; 
TCHAR cAlternateFileName[ 14 ]; 

riconoscibili dalle parentesi quadre [ ]. Il numero all'interno di queste specifica gli elementi della matrice. Se questi ultimi sono rappresentati da una costante come MAX_PATH, significa che in qualche file di intestazione troveremo:

#define MAX_PATH 260

e allora, prima di caricare la struttura, sostituiremo quella costante manualmente:

TCHAR cFileName[ 260 ]; 

Lo stesso discorso vale anche per espressioni come (0 to 31): questa deve essere sostituita con un unico valore intero (32). In Visual Basic, le matrici sono tra parentesi tonde ( ) oppure indicate con * numero_elementi.
Qualsiasi tipo di dato può essere rappresentato in matrici, comprese le sub-strutture.
Per il linguaggio C, la classe, nel caso lAutoArray sia .T., tratta come stringhe di testo ANSI le matrici di BYTE, BCHAR, CHAR, TBYTE, TCHAR, UCHAR; testo Unicode matrici di WCHAR. Per Visual Basic, testo ANSI le matrici di BYTE o STRING; di INTEGER per Unicode. La classe non accetta matrici a lunghezza 0.
Se questi dati non sono in matrice viene riportato il loro valore numerico, (a parte STRING di VB). Esiste anche la possibilità che una funzione API chiami direttamente una matrice di dati o di strutture come parametro. In questo caso la funzione InitAPIArray() tratta queste come se fossero una struttura con una matrice di un solo tipo di dato (o sub-struttura) e produce una stringa passabile e ricevibile dalle Dll.

Tornando sul discorso dei puntatori in memoria, come già in precedenza accennato, c'è anche la possibilità di avere sub-strutture residenti nella memoria:

typedef struct _JOB_INFO_2 { 
DWORD JobId;
LPTSTR pPrinterName;
LPTSTR pMachineName;
LPTSTR pUserName;
LPTSTR pDocument;
LPTSTR pNotifyName;
LPTSTR pDatatype;
LPTSTR pPrintProcessor;
LPTSTR pParameters;
LPTSTR pDriverName;
LPDEVMODE pDevMode;
LPTSTR pStatus;
PSECURITY_DESCRIPTOR pSecurityDescriptor;
DWORD Status;
DWORD Priority;
DWORD Position;
DWORD StartTime;
DWORD UntilTime;
DWORD TotalPages;
DWORD Size;
SYSTEMTIME Submitted;
DWORD Time;
DWORD PagesPrinted;
} JOB_INFO_2, *PJOB_INFO_2;

qui abbiamo due elementi che puntano ad una DEVMODE e ad una SECURITY_DESCRIPTOR (non riportate adesso) nella memoria. Nel preparare la struttura principale sostituiamo i richiami alle due strutture

LPDEVMODE pDevMode;
PSECURITY_DESCRIPTOR pSecurityDescriptor;      

con il tipo dato HGLOBAL

HGLOBAL pDevMode;
HGLOBAL pSecurityDescriptor;      

in modo da poter memorizzare il valore numerico che rappresenta l'indirizzo di memoria. Se poi, ad esempio, intendiamo utilizzare anche la DEVMODE, creiamo un oggetto rAPIdStructure separato dove carichiamo e impostiamo quest'ultima struttura, ne scriviamo la stringa ottenuta da MakeStruct() nel buffer con WriteMemory() riportandone il risultato nel membro .pDevMode. Ora la struttura principale JOB_INFO_2 la utilizzerà direttamente. Per liberare la memoria allocata sarà necessario intervenire con una chiamata alla funzione FreeMemory() in quanto questo non avviene in automatico. Occorre porre attenzione a quelle scritte in Visual Basic perchè alcune, come quella dell'esempio precedente, sembrano gestire direttamente gli handle di memoria come se fossero strutture incorporate, mentre altre, come CHOOSEFONT, riportano il tipo di dato che rappresenta l'handle e con cui bisogna sostituire il nome della struttura: LONG.

Definizioni nella precedente struttura in Visual Basic:

pDevMode As DEVMODE 
pSecurityDescriptor As SECURITY_DESCRIPTOR 

corrette prima del caricamento nella classe:

pDevMode As Long
pSecurityDescriptor As Long

In alternativa a questo possiamo impostare lUnknownAsStruct a .F., ma se troviamo un mix di sub-strutture normalmente incorporate e di altre puntanti alla memoria, la sostituzione manuale del tipo di dato prima del caricamento rimane l'unica strada. Non ho trovato un modo migliore per automatizzare questa particolarità delle strutture, considerando le varianti che si possono presentare.

Spiego anche quello che ho capito del funzionamento della parola union in una struttura:

typedef struct _PROCESS_HEAP_ENTRY {
PVOID lpData;
DWORD cbData;
BYTE cbOverhead;
BYTE iRegionIndex;
WORD wFlags;
union {
struct {
HANDLE hMem;
DWORD dwReserved[ 3 ];
} Block;
struct {
DWORD dwCommittedSize;
DWORD dwUnCommittedSize;
LPVOID lpFirstBlock;
LPVOID lpLastBlock;
} Region;
};
} PROCESS_HEAP_ENTRY, *LPPROCESS_HEAP_ENTRY;

Se guardo la guida di questo esempio leggo che la struttura interna Block sarà presa in considerazione solo se il membro wFlags ha valore 4 o 16, mentre il valore 1 determinerà il completamento di quella denominata Region. Convertendo la struttura per VFP, dobbiamo tener presente che solo una di queste due strutture viene implementata, mentre l'altra è come se non esistesse, quindi intervenendo manualmente occorre o riempire la proprietà cExcludeList con i membri da scartare:

oStruct.cExcludeList="hMem;dwReserved"

oppure rimuovere quella che non interessa, altrimenti i membri di entrambe vengono caricati dalla classe e ciò può provocare lo sfasamento dei dati (non in questo esempio, ma potrebbero esserci anche altri membri dopo la parentesi che chiude la sessione union):

typedef struct _PROCESS_HEAP_ENTRY {
	PVOID lpData;
DWORD cbData;
BYTE cbOverhead;
BYTE iRegionIndex;
WORD wFlags;
union {
struct {
DWORD dwCommittedSize;
DWORD dwUnCommittedSize;
LPVOID lpFirstBlock;
LPVOID lpLastBlock;
} Region;
};
} PROCESS_HEAP_ENTRY, *LPPROCESS_HEAP_ENTRY;

Occorre infine notare che se il nome di un membro della struttura corrisponde a una proprietà già esistente nella classe, al momento del caricamento, viene aggiunto un carattere _ (underscore) all'inizio della nuova proprietà. È il caso della struttura RECT (o RECTL) che ha left e top come membri:

typedef struct _RECT {
	LONG left;
	LONG top;
	LONG right;
	LONG bottom;
} RECT

per non confonderle con le proprietà native, le nuove verranno riportate così nella classe:

With oStruct
	._left
	._top
	.right
	.bottom
EndWith

Spero che tutto questo possa tornare utile a qualcuno. Ricordo che è un adattamento e, pur coprendo buona parte delle strutture più comuni, non si possono pretendere miracoli.

Suggerimenti

- SET COMPATIBLE deve essere OFF.
SET FIXED OFF è consigliato.

- Controllare sempre il punto e virgola (;) finale per le gli elementi delle strutture in C++.

- Per provare rapidamente il caricamento delle strutture usare il file rptest_it.prg.

Documentazione della classe rAPIdStructure in rpstruct.prg

Per l'inizializzazione:

Set Procedure To rpstruct ADDITIVE
oStruct=CreateObject("rAPIdStructure")

PROPRIETÀ PUBBLICHE (tra parentesi il valore predefinito)

Le proprietà lAllowIntStruct, lAutoArray, lAutoFar, lBoolNumeric, lSplit64bit e lUnknownAsStruct possono essere impostate solo prima del caricamento di una definizione di struttura o prima della creazione di una array API.
Le classi delle sub-strutture ereditano le proprietà impostate nella classe del livello immediatamente superiore.
Le proprietà cDoubleNullList, cExcludeList, nPadding e vStopAt non sono ereditarie e si applicano solo alla propria struttura o al primo livello in un'array di strutture.
È possibile caricare una sola struttura per volta.

cDoubleNullList (="")
Contiene l'elenco, separato da un punto e virgola (;), dei membri che SplitStruct() deve leggere dalla memoria come stringhe terminanti con un doppio carattere Null.

cExcludeList (="")
Indica l'elenco, separato da un punto e virgola (;), dei membri da non elaborare. Apposito per le strutture che utilizzano parti "Union".

cStructure (="")
Contiene il nome della struttura caricata.

lAllowIntStruct (=.T.)
Abilita il caricamento automatico delle strutture interne (vedi DataTypes("IS") )

lAutoArray (=.T.)
Gestisce in modo automatico le matrici di dati di 8 o 16 bit come stringhe di testo, oppure, se .F. le tratta come le altre matrici riportando il valore numerico di ogni elemento.

lAutoFar (=.T.)
Se è Falso (.F.), disabilita l'automatismo per i tipi di dati CHAR FAR * e CHAR FAR * FAR *, riconoscendoli solo come puntatori.

lAutoMemory (=.T.)
Per i puntatori in memoria permette la scrittura e la lettura automatica del buffer con stringhe di testo, altrimenti accetta e restituisce solo valori numerici.

lAutoUnicodeConv (=.T.)
Se Vero (.T.), converte automaticamente le stringhe ANSI in Unicode e viceversa sia in Input che Output.

lBoolNumeric (=.T.)
Se Vero (.T.) indica se rappresentare i valori booleani come numerici (0 = Falso, Vero negli altri casi); se Falso (.F.) i valori booleani sono di tipo logico: Vero o Falso (.T. o .F.).

lSplit64bit (=.T.)
Proprietà per il solo linguaggio C++. Per i valori interi di 64 bit viene creata una sub-struttura che li divide in due parti a 32 bit. Gli elementi avranno i nomi di LowPart e HighPart. La formula per calcolare l'intero sarebbe (HighPart * (0xFFFFFFFF+1)) + LowPart.

lUnknownAsStruct (=.T.)
Gestisce in modo automatico le sub-strutture creando per ogni tipo di dato non riconosciuto un oggetto rAPIdStructure. Se questo valore è .F., riconosce il dato come HGLOBAL (numerico, non segnato, 4 bytes) per le definizioni in linguaggio C oppure LONG (numerico, segnato, 4 bytes) per quelle in Visual Basic.

nError (=0)
Contiene il codice interno dell'ultimo errore generato.
Questi sono i codici di errore restituiti:

0
nessun errore
-11
passati parametri non corretti
-301
definizione di struttura non caricata
-302
definizione di struttura già caricata
-303
definizione di struttura non valida
-304
nome struttura non rilevato
-305
matrice nella struttura non valida
-306
nessun membro della struttura caricato
-307
dato non corrispondente in input di un membro
-308
errore in una struttura di livello secondario
-309

errore nelle DLL per la lettura/scrittura della memoria

-310
nessun puntatore memorizzato
-311
nome membro inesistente
-312
indice membro errato
-313
il membro non è un puntatore
-314
la stringa non ha puntatore in memoria

nPadding (=0)
Aggiusta artificiosamente la dimensione della struttura aggiungendo il numero specificato di caratteri Null. Se questo valore è -1, la classe aggiunge automaticamente i caratteri necessari arrotondando per eccesso la dimensione al più vicino multiplo di 4. Questa impostazione è la più comune per le strutture che ne necessitano (vedere anche il file correct.rtf).

vStopAt (=0)
Indica il nome (carattere) o il numero (numerico e più veloce) dell'ultimo elemento da elaborare. Occorre considerare i membri che contengono array o sub-strutture come un unico elemento. Questo supporto è per le strutture multiversione, per esempio quelle che variano in dimensione da un sistema operativo all'altro. Il numero di elemento comprende anche gli eventuali membri elencati in cExcludeList.


FUNZIONI PUBBLICHE (tra parentesi i parametri accettati)

Per tutte le funzioni vale il discorso che se l'operazione riesce la proprietà nError avrà valore 0, altrimenti riporterà il codice dell'errore.
Le funzioni CalculateBytes(), MakeStruct(), SplitStruct() e, con parametro 0 o 2, ClipMembers() hanno esito negativo se tutte le eventuali sub-strutture non sono state caricate.

CalculateBytes
Calcola e restituisce il numero di bytes componenti la struttura.
Se non riesce ritorna 0.

ClipMembers (nMode)
Utilità per i programmatori. Copia negli appunti tutti i membri della struttura, preceduti da un punto, pronti per essere inseriti in un comando WITH ... ENDWITH.
Per il parametro nMode (numerico e opzionale): se il valore è 1, forza la copia dei membri anche se le sub-strutture non sono state caricate; se il valore è 2, aggiunge ad ogni membro un commento con la descrizione di come è stato riconosciuto ma non forza la copia; con valore 3, forza la copia e aggiunge il commento con la descrizione. Senza o con qualsiasi altro valore copia i nomi dei membri, senza commenti e solo se tutte le sub-strutture sono caricate correttamente.
Se non riesce ritorna .F.

DataTypes (cType)
Restituisce l'elenco dei tipi di dati riconosciuti automaticamente dalla classe e delle strutture interne. Per il linguaggio C, l'elenco è diviso in base alla lunghezza in bit del dato rappresentato, passando come parametri 8, 16, 32 o 64; per Visual Basic si ottiene una lista unica dei tipi di dati riconosciuti, col parametro "VB". In tutti gli altri casi viene restituito l'elenco delle strutture interne riconosciute in automatico se lAllowIntStruct è .T.
Il parametro cType (carattere o numerico e opzionale) deve contenere l'indicazione per l'elenco che si vuole ottenere. Per l'elenco completo dei dati supportati vedere al termine di questa documentazione.

FlagTest (nIntValue, nFlag)
Verifica se uno o più flags (bits impostati a 1) sono presenti in un determinato campo o valore numerico e, se riesce, ritorna Vero (.T.). La funzione è simile a BITTEST() ma non richiede la posizione del bit da testare e lavora anche con flags composti da più bits.
Il parametro nIntValue (numerico) indica il valore che può contenere i flags.
Il parametro nFlag (numerico) specifica il numero composto di uno o più flags di cui verificare la presenza in nIntValue (nFlag = 0 ritorna sempre .T.).
Se non riesce ritorna .F.

GetPtr (cMemberName, nIndex)
Restituisce il puntatore di una stringa di testo dalla memoria. La funzione opera solo con i membri della classe in cui è contenuta. A seconda dell'ultima delle due funzioni che è stata chiamata, il puntatore restituito è quello della stringa scritta da MakeStruct() o quello riportato da SplitStruct().
Il parametro cMemberName (carattere) deve contenere il nome del membro della struttura da cui ritornare il puntatore.
Il parametro nIndex (numerico e opzionale) specifica il numero di indice dell'elemento in una improbabile array di puntatori.
Se non riesce ritorna 0.

InitAPIArray (cDataType, nElement, cElementName)
Crea un'array per funzioni API, formata da un solo tipo di dato o struttura, trattandola come un'unica struttura. Se il tipo di dato è una struttura (non interna), occorre poi caricare in ogni membro della matrice la definizione della struttura. Questa funzione rispetta tutte le regole di LoadCDef() o LoadVBDef(). Solo i tipi di dati del linguaggio C possono essere caricati come singoli.
Il parametro cDataType (carattere) deve contenere il nome del tipo di dato o della struttura da caricare.
Il parametro nElement (numerico e opzionale) deve contenere il numero di elementi della matrice. Se è vuoto si crea una matrice di un solo elemento.
Il parametro cElementName (carattere e opzionale) deve contenere il nome della proprietà, aggiunta alla classe, che contiene la matrice. Se vuoto viene preso il nome del tipo di dato.
Se non riesce ritorna .F.

LoadCDef (cDefStruct, lAnsi2Wide)
Carica una definizione di struttura in linguaggio C++.
Il parametro cDefStruct (carattere) deve contenere la definizione.
Il parametro lAnsi2Wide (logico e opzionale), se Vero (.T.) carica una struttura dichiarata ANSI come se fosse Unicode (TCHAR->WCHAR, LPTSTR->LPWSTR,...).
Se non riesce ritorna .F.

LoadVBDef (cDefStruct)
Carica una definizione di struttura in linguaggio Visual Basic.
Il parametro cDefStruct (carattere) deve contenere la definizione.
Se non riesce ritorna .F.

MakeStruct (lAllZero, nLength)
Restituisce la stringa di caratteri composta dalla struttura e pronta per essere passata alla funzione API.
Se lAutoMemory è .T. ed è stato immesso del testo in un membro che punta alla memoria, questa funzione alloca la memoria. ResetData la libera automaticamente.
Il parametro lAllZero (logico e opzionale) se impostato a .T. restituisce una stringa di caratteri (Chr(0)) senza valutare il contenuto dei vari membri, guadagnando in velocità. Questa stringa è adatta per le funzioni API che non richiedono valori in input.
Il parametro nLength (numerico e opzionale) indica la lunghezza della stringa restituita aggiungendo caratteri (Chr(0)) al termine se necessario. Viene ignorato se il suo valore è inferiore a quello restituito da CalculateBytes().
Se non riesce ritorna una stringa vuota.

ResetClass
Azzera la classe portandola alla condizione iniziale, prima di caricare una definizione di struttura (ma solo da VFP 8 rimuove le proprietà aggiunte alla classe).
Questa funzione non modifica i settaggi di cDoubleNullList, cExcludeList, lAllowIntStruct, lAutoArray, lAutoMemory, lBoolNumeric, lSplit64bit, lUnknownAsStruct, nPadding e vStopAt.
Se non riesce ritorna .F.

ResetData
Azzera i dati dei singoli membri portandoli al valore vuoto. Pulisce anche la memoria allocata dalla classe (non dalle funzioni API), se possibile. Si consiglia comunque questa funzione ogni volta che si accede o in lettura o scrittura al buffer di memoria. Questa funzione viene chiamata in modo automatico al rilascio della classe.
Se non riesce ritorna .F.

SplitStruct (cCharString)
Ridistribuisce la stringa di caratteri componenti la struttura ricevuta dalla funzione API sui membri della classe. Converte automaticamente i dati nel tipo necessario alla lettura degli stessi.
Il parametro cCharString (carattere) deve contenere la stringa della struttura.
(Il parametro obsoleto lAllInCharString (logico) è stato rimosso).
Se non riesce ritorna .F.

Gestione della memoria

AllocateMemory (nBytes)
Alloca un certo numero di bytes nella memoria e ne restituisce l'indirizzo. WriteMemory() chiama automaticamente questa funzione.
Il parametro nBytes (numerico) indica quanti bytes saranno allocati.
Se non riesce ritorna 0.

FreeMemory (nMemHandle)
Libera un indirizzo di memoria allocata nel buffer.
Il parametro nMemHandle (numerico) indica quale indirizzo liberare.
Se non riesce ritorna .F.

GetMemorySize (nMemHandle)
Ottiene, quando possibile, in bytes da un indirizzo la lunghezza della memoria allocata nel buffer.
Il parametro nMemHandle (numerico) indica quale indirizzo leggere.
Se non riesce ritorna 0

ReadMemory (nMemAddr, nSizeRead, lUnicode, lDoubleNull)
Restituisce una stringa di caratteri da un indirizzo del buffer di memoria.
Il parametro nMemAddr (numerico) indica l'indirizzo di memoria da cui partire a leggere. Se mancano gli altri parametri si ferma al primo carattere nullo.
Il parametro nSizeRead (numerico e opzionale) indica per quanti bytes leggere la memoria. Se ha valore maggiore di 0, esclude i parametri lUnicode e lDoubleNull.
Il parametro lUnicode (logico e opzionale) se Vero (.T.) converte la stringa da Unicode ad ANSI.
Il parametro lDoubleNull (logico e opzionale), se Vero (.T.) specifica che la stringa da leggere termina con un doppio carattere Null invece di uno solo.
Se non riesce ritorna una stringa vuota.

WriteMemory (cMemString, nMemAddr)
Scrive una stringa nel buffer di memoria restituendone l'indirizzo.
Il parametro cMemString (carattere) non può essere lungo 0 e contiene la stringa da scrivere. Fare attenzione alla documentazione della struttura per sapere come terminare la stringa con l'aggiunta di uno o più caratteri nulli Chr(0).
Il parametro nMemAddr (numerico e opzionale) indica l'indirizzo di memoria su cui si vuole scrivere direttamente. L'utilizzo di questo parametro è potenzialmente pericoloso per l'applicazione. Vedere l'Help di Fox al comando SYS(2600).
Se non riesce ritorna 0.

 

Conversione numerica
Queste funzioni risentono dei limiti di VFP per i numeri minimi e massimi. Non vengono verificati i valori fuori range.

Char2Float (cCharFloat)
Converte una stringa di caratteri standard IEEE in numero decimale.
Il parametro cCharFloat (carattere) deve contenere 4 o 8 caratteri e i decimali restituiti sono rispettivamente 6 o 15.
Se non riesce ritorna 0.

Char2Num (cCharInt, lSigned)
Converte una stringa di caratteri in numero intero.
Il parametro cCharInt (carattere) contiene la stringa da convertire.
Il parametro lSigned (logico e opzionale) indica se il numero restituito può essere negativo.
Se non riesce ritorna 0.

Float2Char (nFloat, lDouble)
Converte un numero decimale in una stringa di caratteri standard IEEE.
Il parametro nFloat (numerico) indica quale numero convertire.
Il parametro lDouble (logico e opzionale) indica se il risultato è Float o Double (4 o 8 caratteri).
Se non riesce ritorna una stringa vuota.

Num2Char (nIntValue, nChar)
Converte un numero intero in una stringa di caratteri.
Il parametro nIntValue (numerico) indica quale numero convertire.
Il parametro nChar (numerico e opzionale) indica di quanti caratteri deve essere il risultato. Se non indicato, vengono restituiti 4 caratteri.
Se non riesce ritorna una stringa vuota.

 

Tipi di dati supportati dalla classe (restituiti da DataTypes())

C a 8 bit (uguale a DataTypes("8") )

BYTE
BCHAR
CHAR
TBYTE
TCHAR
UCHAR

C a 16 bit (uguale a DataTypes("16") )

ATOM
LANGID
SHORT
USHORT
WCHAR
WORD

C a 32 bit (uguale a DataTypes("32") )

BOOL
BOOLEAN
COLORREF
DWORD
DWORD_PTR
DWORD32
FLOAT
HACCEL
HANDLE
HBITMAP
HBRUSH
HCOLORSPACE
HCONV
HCONVLIST
HCURSOR
HDC
HDDEDATA
HDESK
HDROP
HDWP
HENHMETAFILE
HFILE
HFONT
HGDIOBJ
HGLOBAL
HHOOK
HICON
HIMAGELIST
HIMC
HINSTANCE
HKEY
HKL
HLOCAL
HMENU
HMETAFILE
HMODULE
HMONITOR
HPALETTE
HPEN
HRGN
HRSRC
HSZ
HWINSTA
HWND
INT
INT_PTR
INT32
IPADDR
IPMASK
LCID
LCSCSTYPE
LCSGAMUTMATCH
LCTYPE
LONG
LONG_PTR
LONG32
LPARAM
LPBOOL
LPBYTE
LPCOLORREF
LPCSTR
LPCTSTR
LPCVOID
LPCWSTR
LPDWORD
LPHANDLE
LPINT
LPLONG
LPSTR
LPTSTR
LPVOID
LPWORD
LPWSTR
LRESULT
PBOOL
PBOOLEAN
PBYTE
PCHAR
PCSTR
PCTSTR
PCWCH
PCWSTR
PDWORD
PFLOAT
PHANDLE
PHKEY
PINT
PLCID
PLONG
PSHORT
PSTR
PTBYTE
PTCHAR
PTSTR
PUCHAR
PUINT
PULONG
PUSHORT
PVOID
PWCHAR
PWORD
PWSTR
REGSAM
SC_HANDLE
SC_LOCK
SERVICE_STATUS_HANDLE
SIZE_T
SSIZE_T
UINT
UINT_PTR
UINT32
ULONG
ULONG_PTR
ULONG32
USHORT
VOID
WPARAM

C a 64 bit (uguale a DataTypes("64") )

DWORD64
DWORDLONG
INT64
LONG64
LONGLONG
QWORD
UINT64
ULONG64
ULONGLONG

Visual Basic (elenco unico, uguale a DataTypes("VB") )

BOOLEAN
BYTE
DOUBLE
INTEGER
LONG
STRING
SINGLE

Strutture interne (uguale a DataTypes("IS") oppure DataTypes(). Si possono creare direttamente nella classe principale passando il solo nome invece dell'intera definizione, ignorando l'impostazione di lAllowIntStruct)

FILETIME
POINT (o POINTL)
RECT (o RECTL)
SYSTEMTIME

 

Esempi inclusi

Strutture semplici: createprocessVB.prg,...
Sub-strutture: getwindowplacementVB.prg, ...
Puntatori in memoria: getstartupinfoVB.prg, ...
Costanti ed espressioni non supportate: findfirstfileC.prg, sendmessageVB.scx, ...
Tipo dato non riconosciuto quando non è una sub-struttura: shfileoperationC.prg, ...
Tipi di dati a 64 bit: globalmemorystatusexC.prg, ...
Array come testo: gettimezoneinformationC.prg, ...
Union: shellexecuteexC.prg
Strutture in memoria e complesse: gethostbynameC.prg, getprinterVB.prg, ...
API array di tipi di dati singoli e di strutture: polypolygonC.scx, enumprinterdriversC.prg, ...
Array di array: devicecapabilitiesC.prg
Membri in bits: shgetsettingsC.prg, ...
GetPtr: getopenfilenameC.prg, ...
Doppio Null: getprinterdriverC.prg
Strutture multiversione: rasgetconnectionstatisticsC.scx, ...

Altri esempi: faxsenddocumentC.prg, mapisendmailC.prg, setprinterVB.prg, ...

 

"Parte per chi legge i gialli a partire dall'ultima pagina..."


Tutti i marchi sono dei rispettivi proprietari.

«La classe è stata testata su Windows XP Pro e Visual FoxPro 8 SP1 (resa compatibile con VFP7, ricompilandola). Non tutti i tipi di dati riconosciuti dalla stessa sono stati testati.

La classe è priva di ogni garanzia per danni diretti od indiretti a chi la utilizza e/o a terzi, è fornita così com'è ed ogni modifica è di esclusiva responsabilità di chi la opera.
La classe è libera e gratuita, può essere distribuita anche col codice e decompilata.

Siate clementi con le critiche: ho abbandonato la scuola a 16 anni e da meno di una ventina lavoro in mezzo ai bovini (è la verità, non una scarsa considerazione dei miei colleghi...) e alla natura della campagna emiliana.

Grazie per aver letto fin qua e a chi vorrà fare uso del mio lavoro. Potete inviare bugs e commenti all'indirizzo qui sotto.»

© Articolo: Emanuele Cerlini, Sabbione - Reggio Emilia
Allevatore, programmatore per hobby

Maggio 2004 - Riproduzione vietata

File allegato: rpstruct.zip

 

© FoxPro e Visual FoxPro sono un marchi registrati da Microsoft Corporation