Parallelamente al progetto che sto intraprendendo attualmente, ho iniziato a seguire un "percorso alternativo": sto cercando di vedere se e come realizzare un software GIS su piattaforme PocketPC.
Nell'analisi delle possibilità di sviluppo del software, ho verificato che non esiste la possibilità di gestire via palmare i file .dbf tramite codice managed, sia tramite il Compact Framework 1/2, che tramite estensioni OpenNET o componenti esterni.
Siccome xò i file dbf sono alla base della gestione degli attributi nei file shapefile (PDF delle specifiche del formato di file Shapefile), ed è essenziale che qualsiasi cosa si chiami GIS gestisca bene questo formato di file (che sono nei GIS l'equivalente dei documenti word nel programmi di word processing, o dei TIFF/JPG nei software di manipolazione immagini), sono stato costretto a ripiegare su componenti unmanaged.
Essendo però abbastanza a digiuno di C++ (in realtà sono un ignorante completo...) e volendo mantenere una certa indipendenza in questo progetto dal mio gruppo di colleghi (che al contrario ha discrete skills in Visual C++ 6), ho pensato bene (mica tanto bene in realtà...) di provare ad effettuare un porting su dispositivi PocketPC di una libreria, ShapeLib, scritta in C e direttamente utilizzabile in .NET.

Sinceramente all'inizio pensavo che semplicemente ricompilando il codice con il compilatore Embedded Visual C++ avrei potuto utilizzare la libreria anche su dispositivi PocketPC senza troppi patemi d'animo... mai previsione fu + azzardata! :( La realtà si sta rivelando infatti assai più complessa...
Per prima cosa, i dispositivi Pocket PC pare supportino solo i caratteri di tipo UNICODE (sicuramente il Compact Framework 1 non permette di chiamare via P/Invoke codice ANSI, visto che l'enumerazione CharSet non permette che di scegliere di fatto i caratteri UNICODE), mentre la libreria ShapeLib usa caratteri ANSI: questo letteralmente rende il tutto un casino!
Ora, io non ho titolo alcuno per spiegare come si porta del codice pensato per gestire i caratteri ANSI a lavorare correttamente con caratteri UNICODE, quello che posso dire è che, grazie all'aiuto dei miei colleghi (alla faccia dell'indipendenza del lavoro...) e a colpi di conversioni char*/wchar_t, strcmp/wcstcmp e 'C'/L'C', sono riuscito a fare il refactoring del codice C (che poi alla fine è diventato codice C++...) e a compilare correttamente i metodi statici che mi servono (essenzialmente la lettura e scrittura dei DBF, come detto).

Una prima cosa sorprendente che ho riscontrato è questa; prendendo come esempio un semplice metodo:

DBFHandle __declspec(dllexport__stdcall DBFOpenconst wchar_t pszDBFFileconst wchar_t pszAccess );

Ebbene, la corrispondente dichiarazione DllImport:

[DllImport("shapelib.dll", CharSet=CharSet.Unicode)]
public static extern IntPtr DBFOpen (string szDBFFile, string szAccess);

non funziona se si usa come dispositivo di deploy l'emulatore PocketPC 2002 (con processore emulato x86) distribuito con il Visual Studio 2003, mentre funziona correttamente sul mio dispositivo PocketPC con processore ARM.
Il motivo è questo: ovviamente ogni dispositivo richiede che la libreria a cui si fa riferimento sia compilata per il proprio processore: il compilatore eVC++ compila la libreria per dispositivi x86 modificando il nome del metodo da DBFOpen a _DBFOpen@8 (mi è stato detto che è una caratteristica del C++, e quindi la cosa è perfettamente "legale"), tale che la seguente dichiarazione DllImport:

[DllImport("shapelib.dll", EntryPoint="_DBFOpen@8", CharSet=CharSet.Unicode)]
public static extern IntPtr DBFOpen (string szDBFFile, string szAccess);

funziona correttamente.
Lo stesso codice xò, compilato sempre in eVC++ ma per dispositivi ARM non modifica il nome del metodo! Per dispositivi ARM occorre quindi eliminare l'attributo EntryPoint="_DBFOpen@8" dalla dichiarazione DllImport.


Quello che in realtà mi ha sorpreso notevolmente è questo; prendiamo tre metodi C++ correttamente implementati e funzionanti da codice unmanaged:

__declspec(dllexportint __stdcall DBFReadIntegerAttributeDBFHandle hDBFint iShapeint iField,);
__declspec(dllexportdouble __stdcall DBFReadDoubleAttributeDBFHandle hDBFint iShapeint iField,);
__declspec(dllexportconst wchar_t__stdcall DBFReadStringAttributeDBFHandle hDBFint iShapeint iField,);

e le relative dichiarazioni DllImport:

[DllImport("shapelib.dll", CharSet=CharSet.Unicode)]
public static extern int DBFReadIntegerAttribute (IntPtr hDBF, int iShape, int iField);

[DllImport("shapelib.dll", CharSet=CharSet.Unicode)]
public static extern double DBFReadDoubleAttribute (IntPtr hDBF, int iShape, int iField);

[DllImport("shapelib.dll", CharSet=CharSet.Unicode)]
public static extern string DBFReadStringAttribute (IntPtr hDBF, int iShape, int iField);

Il primo metodo funziona correttamente, mentre i metodi successivi (quello col double e la stringa come valore di ritorno) non solo non funzionano, ma a runtime generano una NotSupportedException! Il fatto che codice unmanaged generi a runtime una eccezione di tipo managed a me ha sorpreso assai, visto che ero rimasto al fatto che per "grabbare" eccezioni non gestite fosse necessario usare questa sintassi:

try
{
     
// Chiama codice gestito e non gestito
}
catch(Exception ex)
{
     
// Gestisci errori da codice managed
}
catch 
{
     
// Gestisci errori derivanti da codice unmanaged
}

Immagino quindi che l'eccezione sia generata dall'attributo DllImport (anche se lo stack delle chiamate non lo riporta) o dalla chiamata extern in qualche punto del processo di esecuzione del programma PRIMA dell'ingresso nel codice unmanaged.

Ho risolto la cosa modificando i metodi C++ in questo modo:

void __declspec(dllexport__stdcall DBFReadIntegerAttributeDBFHandle hDBFint iShapeint iFieldint *pReturnValue);
void __declspec(dllexport__stdcall DBFReadDoubleAttributeDBFHandle hDBFint iShapeint iFielddouble *pReturnValue );
void __declspec(dllexport__stdcall DBFReadStringAttributeDBFHandle hDBFint iShapeint iFieldwchar_t *pReturnValue );

cioè passando come parametro ref il valore di ritorno della funzione, e modificando ovviamente in modo opportuno le dichiarazioni DllImport; in questo modo il codice funziona correttamente.

Chiunque possa segnalarmi risorse per approfondire la questione sarà ringraziato ed elogiato privatamente e pubblicamente!

P.S: L'autore non si sente legalmente, materialmente e spiritualmente responsabile delle castronerie eventualmente presenti nel testo precedentemente riportato, dovute a fretta e/o ignoranza...

P.P.S: Nessun costrutto del linguaggio C++ è stato ucciso durante la stesura del post...

powered by IMHO 1.3