Newsgroup: it.comp.lang.c++
Titolo: lpVtbl ? C : C++;

Salve a tutti,
    per caso ho notato il differente approccio alle interfacce Ole tra C e
C++; mentre in C++ si scrive normalmente:
    obj->func(...);
in C questo viene trasformato cosi':
    obj_data->lpVtbl->func( obj_data, ... );
    Presumo che lpVtbl coincida con la vtable di un oggetto C++, ed e'
evidente che il primo parametro e' il this. Ora: se io volessi ripetere
questo meccanismo di accesso ad oggetti C++ da parte di linguaggi non OO
come dovrei ridefinire le classi? In una dll che verra' utilizzata anche da
altri linguaggi, e' meglio seguire questa strada o orientarsi su un "SDK"
tradizionale in C?
Grazie in anticipo,
    Al
--
"Hoping the problem _magically goes away_ by ignoring
it is the "microsoft approach to programming" and
should never be allowed."
-- Linus Torvalds.

tradizionale in C?
Qualche tempo fa per fare quello che dici tu ho utilizzato l'approccio "SDK"
quindi dopo aver costruito le classi che mi servivano, ho scritto anche
un'interfaccia "C" pura per poterle utilizzare ad es. anche dal Visual
Basic.
Questo meccanismo funziona sempre...
Di recente, invece, ho ripreso questo discorso non tanto per utilizzare le
classi da altri linguaggi, ma semplicemente per utilizzare una classe
fornita sotto forma di DLL senza "linkare" il .LIB, ma caricando la libreria
dinamicamente.
In realta' ho fatto solo delle prove per un amico, ma sono giunto ad alcune
conclusioni che penso potrebbero esserti utili.
Trascrivo qui il codice cosi' come lo provai a suo tempo, non fare caso ai
nomi di fantasia utilizzati.
Ho utilizzato il compilatore Microsoft VC++ 6.0
Questa e' la classe che risiede nella DLL.
// Driver di prova
class  LittleDriver
{
public:
_declspec(dllexport) int _cdecl Somma(int x, int y);
int dummy;
char name[16];
int result;
};
In questo modo la funzione "Somma" sara' accessibile dall'esterno con un
nome "decorato" dal compilatore; non so se questo meccanismo e' standard,
comunque con il mio compilatore il nome che risulta e':
?Somma@LittleDriver@@QAAHHH@Z
Questa funzione puo' essere acceduta utilizzando al solito "LoadLibrary" e
"GetProcAddress".
Per la chiamata - comunque utilizzando il C++ - ho fatto in questo modo:
int (*m_pSomma)(LittleDriver* object, int x, int y);
m_pSomma = (int (*)(LittleDriver*, int, int))
  GetProcAddress(hInstance, "?Somma@LittleDriver@@QAAHHH@Z");
int z = m_pSomma(this, x, y);    // chiama passando "this" ed i parametri
---
Il problema a questo punto e': dove prendiamo questo "this".
Nel mio caso io avevo fatto una seconda classe di "interfaccia" tra il
chiamante e la DLL, in questo modo.
class LittleDriverDLL: private LittleDriver, public DriverManager
{
// Overrides
public:
bool Load(const char* filename);
public:
int Somma(int x, int y);
private:
int (*m_pSomma)(LittleDriver* object, int x, int y);
};
La nuova classe deriva da quella di partenza, cosi' contiene tutti gli
attributi ed il costruttore e' in grado di allocare la memoria necessaria.
Deriva anche dalla classe "DriverManager" dove ho inserito un po' di codice
per caricare la libreria ed i puntatori a funzione utilizzati (sul modello
di quanto scritto in precedenza).
La funzione "Somma" invece fa da ponte verso quella della libreria.
Un po' di codice...
// Caricamento libreria
bool LittleDriverDLL::Load(const char* filename)
{
bool ok = false;
// Carica DLL e controlla risultato
DriverManager::Load(filename);
if (Loaded())
{
  // Cerca indirizzi delle funzioni
  ok = true;
  m_pSomma = (int (*)(LittleDriver*, int, int))
GetAddress("?Somma@LittleDriver@@QAAHHH@Z");
  if (m_pSomma == NULL)
   ok = false;
  // Scarica libreria in caso di errori
  if (!ok)
   Free();
}
return ok;
}
// ====================================
// === Funzioni "ponte" verso il driver
// ====================================
int LittleDriverDLL::Somma(int x, int y)
{
strcpy(name, "prova");
dummy = 50;
int z = m_pSomma(this, x, y);
return z;
}
Naturalmente ci sono problemi in questa implementazione, ad es. il
costruttore di LittleDriverDLL non e' in grado di chiamare quello di
"LittleDriver" (la classe di partenza) che infatti ne e' sprovvisto.
Inoltre c'e' da fare un bel lavoro per scrivere tutte le funzioni ponte.
---
Se invece non vuoi fare una cosa del genere, forse ti puoi fermare alla
dichiarazione della classe nella DLL in modo che tutto venga esportato come
abbiamo visto prima.
Per costruire l'oggetto invece si potrebbe pensare ad una nuova funzione
tipo "CreateLittleDriver" sempre presente nella DLL che alloca l'oggetto con
"new" e ne restituisce il puntatore (il "this" cercato).
LittleDriver* CreateLittleDriver(...parametri del costruttore...)
{
    LittleDriver* p;
    p = new LittleDriver(...parametri del costruttore...)
    return p;
}
Cosi' facendo si ha qualcosa utilizzabile da piu' linguaggi, almeno credo.
Ciao,
Angelo.

tradizionale in C?
La soluzione piu` generale, su piattaforme Win32, e` probabilmente
l'uso del COM, che rende la vita estremamente facile ai chiamanti che
usino praticamente qualsiasi linguaggio (Basic, Delphi, Python, Perl,
Java, C, C++, linguaggi di scripting misti assortiti) dove il compilatore 
(o, a seconda dei casi, l'interprete) offra il dovuto supporto.
Se non puoi, o non vuoi, implementare il Common Object Model, o
se vuoi usare piattaforme diverse da Win32, stai probabilmente piu`
tranquillo ad esporre un'API non-ad-oggetti se vuoi supportare le
chiamate da parte di linguaggi non-ad-oggetti.  Alternativamente,
alcune implementazioni (gratuite e disponibili su varie piattaforme)
di CORBA, come ILU, offrono una soluzione abbastanza analoga,
concettualmente, a quello che fa COM su Win32 (purtroppo, nessuno
ha la stessa enorme efficienza di COM nel caso ottimale di connessione
entro lo stesso thread -- gli overhead di chiamata per tutti i Corba e
simili che ho esaminati sono sempre sostanziali, mentre COM li
riesce a ridurre a quei "20-30 nanosecondi su Pentium 133" di cui
dicevo sopra come overhead di stdcall rispetto a thiscall).
Se scegli la strada di "esporre API non ad oggetti", accertati pero`
di usare convenzioni di chiamata adeguate a seconda della tua
piattaforma; ad esempio, su Win32, sarebbe suicida esportare
funzioni cdecl, se l'intenzione e` permettere chiamate da altri
linguaggi (quindi: niente funzioni con numero e tipo variabili di
argomenti, e neppure, naturalmente, nessun overload, ecc ecc).
Su Win32, comunque tu decida di scrivere le tue DLL, ti puo`
convenire comunque accompagnarle di una "type library" -- essa
rendera` comunque la vita MOLTO piu` facile a chi vuole chiamarla
da tanti altri linguaggi (il testo "Hardcore Visual Basic" fa questo
lavoro per una buona fetta delle DLL delle API di Win32 -- cosi`,
un programmatore Basic che voglia chiamare le API si risparmia
l'enorme faticaccia di dichiararsi tutte le funzioni di interesse in
termini VB...!!!).  Per scrivere una Type Library, si parte, di solito,
dall'Interface Definition Language (IDL).
Alex