Generazione di componenti COM per l'interoperabilità
Aggiornamento: novembre 2007
Se si intende scrivere applicazioni COM, è possibile progettare il codice affinché interagisca in modo efficace con il codice gestito. Un'accurata pianificazione consente inoltre di semplificare la migrazione di codice non gestito in codice gestito.
Nelle istruzioni riportate di seguito sono riassunte le procedure più adatte per la scrittura di tipi COM in grado di interagire con il codice gestito.
Fornire librerie dei tipi
Nella maggior parte dei casi in Common Language Runtime sono richiesti metadati per tutti i tipi, compresi i tipi COM. Grazie all'utilità di importazione della libreria dei tipi (Tlbimp.exe), inclusa in Windows Software Development Kit (SDK), è possibile convertire librerie dei tipi COM in metadati di .NET Framework. Dopo aver convertito la libreria dei tipi in metadati, il tipo COM può essere chiamato dai client gestiti in modo uniforme. Per praticità, fornire sempre informazioni sui tipi in una libreria dei tipi.
È possibile comprimere una libreria dei tipi come file separato o incorporarla come risorsa in un file DLL, EXE o OCX. È inoltre possibile generare metadati direttamente, in modo da poterli firmare con la coppia di chiavi dell'editore. I metadati firmati con una chiave sono caratterizzati da un'origine definita e consentono pertanto di impedire l'associazione quando il chiamante dispone della chiave errata, garantendo in tal modo una maggior sicurezza.
Registrare le librerie dei tipi
Per eseguire correttamente il marshalling delle chiamate, in fase di runtime può essere necessario individuare la libreria dei tipi che descrive un tipo particolare. È necessario registrare una libreria dei tipi prima che venga individuata in fase di runtime, ad eccezione dei casi di associazione tardiva.
Per eseguire la registrazione di una libreria dei tipi, chiamare la funzione LoadTypeLibEx dell'API Microsoft Win32 con il flag regkind impostato su REGKIND_REGISTER. Grazie a Regsvr32.exe viene registrata automaticamente una libreria dei tipi incorporata in un file DLL.
Utilizzare matrici protette invece di matrici a lunghezza variabile
Le matrici protette COM sono autodescrittive. Mediante l'analisi della matrice protetta, con il gestore di marshalling di runtime è possibile determinare il numero di dimensioni, la dimensione, i limiti e, di solito, il tipo del contenuto della matrice in fase di esecuzione. Le matrici a lunghezza variabile, o di tipo C, non sono invece autodescrittive. Nella firma del metodo non gestito riportata di seguito ad esempio non sono fornite informazioni sul parametro matrice, a parte il tipo di elemento.
HRESULT DoSomething(int cb, [in] byte buf[]);
Non è infatti possibile distinguere la matrice da qualsiasi altro parametro passato per riferimento. Ne risulta che il parametro matrice del metodo DoSomething non è convertito da Tlbimp.exe. La matrice appare invece come riferimento a un tipo Byte, come illustrato nel codice che segue.
Public Sub DoSomething(cb As Integer, ByRef buf As Byte)
public void DoSomething(int cb, ref Byte buf);
Per migliorare l'interoperabilità, è possibile digitare l'argomento come elemento SAFEARRAY nella firma del metodo non gestito. Di seguito è riportato un esempio:
HRESULT DoSomething(SAFEARRAY(byte)buf);
L'elemento SAFEARRAY viene convertito da Tlbimp.exe nel tipo gestito della matrice riportato di seguito:
Public Sub DoSomething(buf As Byte())
public void DoSomething(Byte[] buf);
Utilizzare tipi di dati compatibili con l'automazione
Nel servizio di marshalling di runtime sono automaticamente supportati tutti i tipi di dati compatibili con l'automazione. È possibile che i tipi non compatibili non siano supportati.
Fornire la versione e le impostazioni internazionali nelle librerie dei tipi
Quando si importa una libreria dei tipi, le informazioni sulla versione e sulle impostazioni internazionali della libreria vengono anche comunicate all'assembly. I client gestiti possono quindi essere associati a una determinata versione o a impostazioni internazionali particolari dell'assembly o alla versione più recente dell'assembly. Grazie all'inserimento delle informazioni sulla versione nella libreria dei tipi, i client possono scegliere con precisione la versione dell'assembly da utilizzare.
Utilizzare tipi copiabili
I tipi di dati sono copiabili o non copiabili. I primi hanno una rappresentazione comune oltre i limiti di interoperabilità. I tipi integer e a virgola mobile sono copiabili, così come le matrici e le strutture di tipi copiabili. Le stringhe, le date e gli oggetti sono esempi di tipi non copiabili convertiti nel processo di marshalling.
Nel servizio di marshalling di interoperabilità sono supportati sia i tipi copiabili che quelli non copiabili; i tipi che richiedono la conversione nel corso del processo non garantiscono tuttavia prestazioni pari ai tipi copiabili. Quando si utilizzano tipi non copiabili, prestare attenzione al sovraccarico aggiunto associato all'operazione di marshalling.
Le stringhe sono particolarmente problematiche. Le stringhe gestite sono memorizzate come caratteri Unicode e di conseguenza, il marshalling viene eseguito in modo molto più efficiente nel codice non gestito per il quale si prevedono argomenti di caratteri Unicode. Dove possibile, è consigliabile evitare stringhe composte da caratteri ANSI.
Implementare IProvideClassInfo
Quando si esegue il marshalling di interfacce non gestite nel codice gestito, in runtime viene creato un wrapper di un tipo specifico. La firma del metodo specifica in genere il tipo dell'interfaccia, ma il tipo dell'oggetto che implementa l'interfaccia può essere sconosciuto. In questo caso, in runtime viene eseguito il wrapping dell'interfaccia con un wrapper di oggetto COM generico meno funzionale dei wrapper di tipi specifici.
Si consideri ad esempio la firma del metodo COM riportata di seguito:
interface INeedSomethng {
HRESULT DoSomething(IBiz *pibiz);
}
Quando viene importato, il metodo viene convertito come segue:
Interface INeedSomething
Sub DoSomething(pibiz As IBiz)
End Interface
interface INeedSomething {
void DoSomething(IBiz pibiz);
}
Se si passa un oggetto gestito che implementa l'interfaccia INeedSomething sull'interfaccia IBiz, il gestore di marshalling di interoperabilità tenta di eseguire il wrapping dell'interfaccia con il wrapper di oggetto di un tipo specifico nell'introduzione iniziale di IBiz al codice gestito. Per individuare il tipo corretto di wrapper, è necessario che il gestore di marshalling conosca il tipo dell'oggetto che implementa l'interfaccia. Un modo a disposizione del gestore di marshalling per determinare il tipo di oggetto consiste nell'esecuzione di una query per l'interfaccia IProvideClassInfo. Se IProvideClassInfo viene implementata dall'oggetto, il gestore di marshalling determina il tipo dell'oggetto ed esegue il wrapping dell'interfaccia in un wrapper tipizzato.
Utilizzare chiamate modulari
Il marshalling dei dati tra codice gestito e non gestito ha un costo, che può essere mitigato dalla riduzione delle transizioni. Le interfacce che riducono al minimo il numero di transizioni garantiscono in genere prestazioni migliori rispetto a quelle che ricorrono a un numero maggiore di transizioni, eseguendo attività ridotte per ciascuna di esse.
Utilizzare HRESULT di errore in senso conservativo
Quando un oggetto COM viene chiamato da un client gestito, in runtime viene eseguito il mapping degli HRESULT di errore dell'oggetto COM sulle eccezioni generate dal gestore di marshalling dopo la chiamata. Il modello delle eccezioni gestite è stato ottimizzato per i casi senza eccezioni. Il sovraccarico associato all'intercettazione delle eccezioni è pressoché inesistente quando non si verificano eccezioni. Viceversa, quando l'eccezione esiste, la relativa intercettazione può avere un costo in prestazioni elevato.
Utilizzare le eccezioni con moderazione ed evitare di restituire HRESULT di errore per scopi informativi, ma utilizzarli solo per situazioni di eccezione. Un uso eccessivo degli HRESULT di errore può influire negativamente sulle prestazioni.
Liberare esplicitamente le risorse esterne
Le risorse esterne vengono utilizzate da parte di alcuni oggetti nel corso della loro durata: un set di record potrebbe ad esempio essere aggiornato durante la connessione a un database. Un oggetto trattiene in genere una risorsa esterna per tutta la durata dell'esistenza dell'oggetto, ma la risorsa può essere restituita immediatamente da un'operazione di rilascio esplicita. È ad esempio possibile utilizzare il metodo Close su un oggetto file anziché chiudere il file nel distruttore della classe o con IUnknown.Release. Tramite l'inserimento di un equivalente del metodo Close nel codice, è possibile liberare la risorsa di file esterna nonostante l'oggetto file continui a esistere.
Evitare la ridefinizione di tipi non gestiti
Il modo corretto di implementare un'interfaccia COM esistente nel codice gestito consiste nell'iniziare a importarne la definizione con Tlbimp.exe o un'API equivalente. I metadati risultanti forniscono una definizione compatibile dell'interfaccia COM (stessi IID, stessi DispId e così via).
Evitare di ridefinire manualmente le interfacce COM nel codice gestito. Questa attività richiede tempo e raramente comporta la creazione di un'interfaccia gestita compatibile con quella COM esistente. Utilizzare invece Tlbimp.exe per mantenere la compatibilità della definizione.
Evitare l'utilizzo degli HRESULT di esito positivo
Intercettare le eccezioni è il modo più naturale a disposizione delle applicazioni gestite per gestire le situazioni di errore. Per rendere trasparente l'impiego dei tipi COM, in runtime viene automaticamente generata un'eccezione ogni volta che un metodo COM restituisce un HRESULT di errore.
Se l'oggetto COM restituisce un HRESULT di esito positivo, in runtime viene restituito qualsiasi valore contenuto nel parametro retval. Per impostazione predefinita, l'HRESULT viene ignorato, rendendo così difficile per il client gestito l'esame del valore di un HRESULT di esito positivo. Benché sia possibile conservare un HRESULT con l'attributo PreserveSigAttribute, il processo risulta complesso. È necessario aggiungere manualmente l'attributo a un assembly generato con Tlbimp.exe o un'API equivalente.
È preferibile evitare gli HRESULT di esito positivo il più possibile. È invece possibile restituire le informazioni sullo stato di una chiamata mediante un parametro out.
Evitare di utilizzare le funzioni modulo
Nelle librerie dei tipi possono essere presenti funzioni definite su un modulo, le quali sono in genere utilizzate per fornire informazioni sui tipi per i punti di ingresso DLL. Tali funzioni non sono importate da Tlbimp.exe.
Evitare di utilizzare i membri di System.Object nelle interfacce predefinite
Grazie ai wrapper forniti dal runtime, client gestiti e coclassi COM interagiscono. Quando si esegue l'importazione di un tipo COM, nel processo di conversione vengono aggiunti tutti i metodi dell'interfaccia predefinita della coclasse alla classe wrapper, derivata dalla classe System.Object. Assicurarsi di denominare i membri dell'interfaccia predefinita in modo che non si verifichino conflitti di denominazione con i membri di System.Object. In caso di conflitto, il metodo importato eseguirà l'override del metodo della classe base.
Questa operazione può rivelarsi valida se il metodo dell'interfaccia predefinita e il metodo System.Object forniscono la stessa funzionalità Può tuttavia essere problematica se i metodi dell'interfaccia predefinita vengono utilizzati in modo non intenzionale. Per impedire conflitti di denominazione, evitare di utilizzare i nomi riportati di seguito nelle interfacce predefinite: Object, Equals, Finalize, GetHashCode, GetType, MemberwiseClone e ToString.
Vedere anche
Riferimenti
Utilità di importazione della libreria dei tipi (Tlbimp.exe)