Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
O agrupamento de interoperabilidade opera com base em regras que ditam como os dados associados aos parâmetros do método se comportam à medida que passam entre a memória gerenciada e a não gerenciada. Essas regras internas controlam atividades de empacotamento como transformações de tipo de dados, se um destinatário pode alterar os dados passados para ele e retornar essas alterações ao chamador e em quais circunstâncias o empacotador fornece otimizações de desempenho.
Esta seção identifica as características comportamentais padrão do serviço de interoperabilidade. Ele apresenta informações detalhadas sobre matrizes de empacotamento, tipos booleanos, tipos de char, delegados, classes, objetos, cadeias de caracteres e estruturas.
Observação
A conversão de tipos genéricos não é suportada. Para obter mais informações, consulte Interoperando usando tipos genéricos.
Gerenciamento de memória com o marshaller de interoperabilidade
O marshaller de interoperabilidade sempre tenta liberar memória alocada por código não gerenciado. Esse comportamento está em conformidade com as regras de gerenciamento de memória COM, mas difere das regras que regem C++ nativo.
A confusão pode surgir se você antecipar o comportamento nativo do C++ (sem liberação de memória) ao usar a invocação de plataforma, o que libera automaticamente memória para ponteiros. Por exemplo, chamar o seguinte método não gerenciado de uma DLL C++ não libera automaticamente nenhuma memória.
Assinatura não gerenciada
BSTR MethodOne (BSTR b) {
return b;
}
No entanto, se definir o método como um protótipo de invocação de plataforma, substituir cada tipo BSTR por um tipo String e chamar MethodOne, o tempo de execução da linguagem comum tenta libertar b duas vezes. Pode alterar o comportamento de marshalling utilizando tipos IntPtr em vez de tipos String.
O runtime utiliza sempre o CoTaskMemFree método no Windows e free o método noutras plataformas para libertar memória. Se a memória com que está a trabalhar não foi alocada com o método CoTaskMemAlloc no Windows ou o método malloc noutras plataformas, deve usar um IntPtr e libertar a memória manualmente usando o método apropriado. De forma semelhante, pode evitar a libertação automática de memória em situações em que a memória nunca deveria ser libertada, como ao usar a GetCommandLine função de Kernel32.dll, que devolve um ponteiro para a memória do kernel. Para obter detalhes sobre como liberar memória manualmente, consulte o Exemplo de buffers.
Marshalling padrão para classes
As classes só podem ser organizadas por interoperabilidade COM e são sempre organizadas como interfaces. Em alguns casos, a interface usada para organizar a classe é conhecida como interface de classe. Para obter informações sobre como substituir a interface de classe por uma interface de sua escolha, consulte Apresentando a interface de classe.
Passando aulas para COM
Quando uma classe gerida é passada para COM, o marshal de interoperabilidade automaticamente encapsula a classe com um proxy COM e transfere a interface da classe produzida pelo proxy para a chamada do método COM. Em seguida, o proxy delega todas as chamadas na interface de classe de volta ao objeto gerenciado. O proxy também expõe outras interfaces que não são explicitamente implementadas pela classe. O proxy implementa automaticamente interfaces como IUnknown e IDispatch em nome da classe.
Passando classes para o código .NET
As coclasses não são normalmente usadas como argumentos de método em COM. Em vez disso, uma interface padrão geralmente é passada no lugar da coclass.
Quando uma interface é passada para o código gerido, o encapsulador de interoperabilidade é responsável por envolver a interface com o encapsulamento adequado e transmitir o encapsulamento para o método gerido. Determinar qual invólucro usar pode ser difícil. Cada instância de um objeto COM tem um único wrapper exclusivo, não importa quantas interfaces o objeto implemente. Por exemplo, um único objeto COM que implementa cinco interfaces distintas tem apenas um wrapper. O mesmo wrapper expõe todas as cinco interfaces. Se duas instâncias do objeto COM forem criadas, duas instâncias do wrapper serão criadas.
Para que o invólucro mantenha o mesmo tipo durante toda a sua vida útil, o marshaller de interoperabilidade deve identificar o invólucro correto na primeira vez que uma interface exposta pelo objeto é passada através do marshaller. O marshaller identifica o objeto observando uma das interfaces que o objeto implementa.
Por exemplo, o marshaller determina que o wrapper de classe deve ser usado para encapsular a interface que foi passada para o código gerenciado. Quando a interface é passada pela primeira vez através do marshaller, o marshaller verifica se a interface é proveniente de um objeto conhecido. Esta verificação ocorre em duas situações:
Uma interface está sendo implementada por outro objeto gerenciado que foi passado para COM em outro lugar. O marshaller pode identificar prontamente interfaces expostas por objetos gerenciados e é capaz de combinar a interface com o objeto gerenciado que fornece a implementação. O objeto gerenciado é então passado para o método e nenhum wrapper é necessário.
Um objeto que já foi encapsulado está implementando a interface. Para determinar se este é o caso, o marshaller consulta o objeto para a sua
IUnknowninterface e compara a interface devolvida com as interfaces de outros objetos que já estão envolvidos. Se a interface for a mesma de outro wrapper, os objetos terão a mesma identidade e o wrapper existente será passado para o método.
Se uma interface não for de um objeto conhecido, o marshaller fará o seguinte:
O marshaller consulta o objeto para a interface IProvideClassInfo2 . Se fornecido, o marshaller usa o CLSID retornado de IProvideClassInfo2.GetGUID para identificar a coclasse que fornece a interface. Com o CLSID, o marshaller pode localizar o invólucro do registro se o conjunto tiver sido registrado anteriormente.
O marshaller consulta a interface para a
IProvideClassInfointerface. Se fornecido, o marshaller usa o valor retornado de IProvideClassInfo.GetClassinfo para determinar o CLSID da classe que expõe a interface. O marshaller pode usar o CLSID para localizar os metadados do wrapper.Se o marshaller ainda não conseguir identificar a classe, ele envolve a interface com uma classe de wrapper genérica chamada System.__ComObject.
Agrupamento por defeito para delegados
Um delegado gerido é marshallado como uma interface COM ou como um apontador de função, dependendo do mecanismo de chamada.
Para a invocação de plataforma, um delegado é transformado como um ponteiro de função não gerida por padrão.
Para a interoperabilidade COM, um delegado é marshallado como uma interface COM do tipo
_Delegatepor defeito. A_Delegateinterface está definida na biblioteca de tipos Mscorlib.tlb e contém o Delegate.DynamicInvoke método que permite chamar o método referenciado pelo delegado.
A tabela a seguir mostra as opções de empacotamento para o tipo de dados de delegado gerenciado. O atributo MarshalAsAttribute fornece vários valores de enumeração UnmanagedType para gerir delegados.
| Tipo de enumeração | Descrição do formato não gerenciado |
|---|---|
| UnmanagedType.FunctionPtr | Um ponteiro de função não gerenciado. |
| UnmanagedType.Interface | Uma interface do tipo _Delegate, conforme definido em Mscorlib.tlb. |
Considere o código de exemplo a seguir, no qual os métodos de DelegateTestInterface são exportados para uma biblioteca de tipos COM. Note que apenas os delegados marcados com a ref palavra-chave (ou ByRef) são passados como parâmetros In/Out.
using System;
using System.Runtime.InteropServices;
public interface DelegateTest {
void m1(Delegate d);
void m2([MarshalAs(UnmanagedType.Interface)] Delegate d);
void m3([MarshalAs(UnmanagedType.Interface)] ref Delegate d);
void m4([MarshalAs(UnmanagedType.FunctionPtr)] Delegate d);
void m5([MarshalAs(UnmanagedType.FunctionPtr)] ref Delegate d);
}
Representação da biblioteca de tipos
importlib("mscorlib.tlb");
interface DelegateTest : IDispatch {
[id(…)] HRESULT m1([in] _Delegate* d);
[id(…)] HRESULT m2([in] _Delegate* d);
[id(…)] HRESULT m3([in, out] _Delegate** d);
[id()] HRESULT m4([in] int d);
[id()] HRESULT m5([in, out] int *d);
};
Um ponteiro de função pode ser desreferenciado, assim como qualquer outro ponteiro de função não gerenciado pode ser desreferenciado.
Neste exemplo, quando os dois delegados são agrupados como UnmanagedType.FunctionPtr, o resultado é um int e um ponteiro para um int. Como os tipos delegados estão sendo reorganizados, int aqui representa um ponteiro para void (void*), que é o endereço do delegado na memória. Em outras palavras, esse resultado é específico para sistemas Windows de 32 bits, já que int aqui representa o tamanho do ponteiro de função.
Observação
Uma referência ao ponteiro de função para um delegado gerenciado mantido por código não gerenciado não impede que o Common Language Runtime execute a coleta de lixo no objeto gerenciado.
Por exemplo, o código a seguir está incorreto porque a referência ao objeto cb, passada para o método SetChangeHandler, não mantém o cb vivo além da vida do método Test. Assim que o objeto cb é coletado como lixo, o ponteiro de função passado para SetChangeHandler não é mais válido.
public class ExternalAPI {
[DllImport("External.dll")]
public static extern void SetChangeHandler(
[MarshalAs(UnmanagedType.FunctionPtr)]ChangeDelegate d);
}
public delegate bool ChangeDelegate([MarshalAs(UnmanagedType.LPWStr) string S);
public class CallBackClass {
public bool OnChange(string S){ return true;}
}
internal class DelegateTest {
public static void Test() {
CallBackClass cb = new CallBackClass();
// Caution: The following reference on the cb object does not keep the
// object from being garbage collected after the Main method
// executes.
ExternalAPI.SetChangeHandler(new ChangeDelegate(cb.OnChange));
}
}
Para compensar a coleta de lixo inesperada, o chamador deve garantir que o cb objeto seja mantido ativo enquanto o ponteiro da função não gerenciada estiver em uso. Opcionalmente, você pode fazer com que o código não gerenciado notifique o código gerenciado quando o ponteiro da função não for mais necessário, como mostra o exemplo a seguir.
internal class DelegateTest {
CallBackClass cb;
// Called before ever using the callback function.
public static void SetChangeHandler() {
cb = new CallBackClass();
ExternalAPI.SetChangeHandler(new ChangeDelegate(cb.OnChange));
}
// Called after using the callback function for the last time.
public static void RemoveChangeHandler() {
// The cb object can be collected now. The unmanaged code is
// finished with the callback function.
cb = null;
}
}
Marshalling padrão para tipos de valor
A maioria dos tipos de valores, como inteiros e números de ponto flutuante, são blittable e não requerem marshalling. Outros tipos não blittable têm representações diferentes na memória gerenciada e não gerenciada e exigem empacotamento. Outros tipos ainda exigem formatação explícita através do limite de interoperação.
Esta seção fornece informações sobre os seguintes tipos de valores formatados:
Além de descrever tipos formatados, este tópico identifica os tipos de valor do sistema que têm comportamento de empacotamento incomum.
Um tipo formatado é um tipo complexo que contém informações que controlam explicitamente o layout de seus membros na memória. As informações de layout do membro são fornecidas usando o StructLayoutAttribute atributo. O layout pode ser um dos seguintes LayoutKind valores de enumeração:
LayoutKind.Auto
Indica que o Common Language Runtime tem liberdade para reordenar os membros do tipo visando eficiência. No entanto, quando um tipo de valor é passado para código não gerenciado, o layout dos membros é previsível. Uma tentativa de organizar tal estrutura causa automaticamente uma exceção.
LayoutKind.Sequencial
Indica que os membros do tipo devem ser dispostos na memória não gerenciada na mesma ordem em que aparecem na definição de tipo gerenciado.
LayoutKind.Explícito
Indica que os membros estão dispostos de acordo com o FieldOffsetAttribute fornecido com cada campo.
Tipos de valor usados na invocação de plataforma
No exemplo a seguir, os tipos Point e tipos Rect fornecem informações de layout de membro usando o StructLayoutAttribute.
Imports System.Runtime.InteropServices
<StructLayout(LayoutKind.Sequential)> Public Structure Point
Public x As Integer
Public y As Integer
End Structure
<StructLayout(LayoutKind.Explicit)> Public Structure Rect
<FieldOffset(0)> Public left As Integer
<FieldOffset(4)> Public top As Integer
<FieldOffset(8)> Public right As Integer
<FieldOffset(12)> Public bottom As Integer
End Structure
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct Point {
public int x;
public int y;
}
[StructLayout(LayoutKind.Explicit)]
public struct Rect {
[FieldOffset(0)] public int left;
[FieldOffset(4)] public int top;
[FieldOffset(8)] public int right;
[FieldOffset(12)] public int bottom;
}
Quando empacotados para código não gerenciado, esses tipos formatados são empacotados como estruturas de estilo C. Isso fornece uma maneira fácil de chamar uma API não gerenciada que tem argumentos de estrutura. Por exemplo, as POINT estruturas e RECT podem ser passadas para a função da API PtInRect do Microsoft Windows da seguinte forma:
BOOL PtInRect(const RECT *lprc, POINT pt);
Você pode passar estruturas usando a seguinte definição de invocação de plataforma:
Friend Class NativeMethods
Friend Declare Auto Function PtInRect Lib "User32.dll" (
ByRef r As Rect, p As Point) As Boolean
End Class
internal static class NativeMethods
{
[DllImport("User32.dll")]
internal static extern bool PtInRect(ref Rect r, Point p);
}
O Rect tipo de valor deve ser passado por referência porque a API não gerenciada espera que um ponteiro para um RECT seja passado para a função. O Point tipo de valor é passado por valor porque a API não gerenciada espera que o POINT seja passado na pilha. Esta diferença subtil é muito importante. As referências são passadas para código não gerenciado como ponteiros. Os valores são passados para código não gerenciado na pilha.
Observação
Quando um tipo formatado é organizado como uma estrutura, apenas os campos dentro do tipo são acessíveis. Se o tipo tiver métodos, propriedades ou eventos, eles serão inacessíveis a partir de código não gerenciado.
As classes também podem ser agrupadas em código não gerenciado como estruturas de estilo C, desde que tenham layout de membro fixo. As informações de layout de membro para uma classe também são fornecidas com o StructLayoutAttribute atributo. A principal diferença entre tipos de valor com layout fixo e classes com layout fixo é a maneira como eles são empacotados para código não gerenciado. Os tipos de valor são passados por valor (na pilha) e, consequentemente, quaisquer alterações feitas nos membros do tipo pelo destinatário não são vistas pelo chamador. Os tipos de referência são passados por referência (uma referência ao tipo é passada na pilha); Consequentemente, todas as alterações feitas em membros do tipo blittable de um tipo pelo destinatário são vistas pelo chamador.
Observação
Se um tipo de referência tiver membros de tipos não blittable, a conversão será necessária duas vezes: a primeira vez quando um argumento é passado para o lado não gerenciado e a segunda vez no retorno da chamada. Devido a essa sobrecarga adicionada, os parâmetros de entrada/saída devem ser explicitamente aplicados a um argumento se o chamador quiser ver as alterações feitas pelo destinatário.
No exemplo seguinte, a SystemTime classe tem layout sequencial de membros e pode ser passada para a função da API GetSystemTime do Windows.
<StructLayout(LayoutKind.Sequential)> Public Class SystemTime
Public wYear As System.UInt16
Public wMonth As System.UInt16
Public wDayOfWeek As System.UInt16
Public wDay As System.UInt16
Public wHour As System.UInt16
Public wMinute As System.UInt16
Public wSecond As System.UInt16
Public wMilliseconds As System.UInt16
End Class
[StructLayout(LayoutKind.Sequential)]
public class SystemTime {
public ushort wYear;
public ushort wMonth;
public ushort wDayOfWeek;
public ushort wDay;
public ushort wHour;
public ushort wMinute;
public ushort wSecond;
public ushort wMilliseconds;
}
A GetSystemTime função é definida da seguinte forma:
void GetSystemTime(SYSTEMTIME* SystemTime);
A definição equivalente de invocação de plataforma para GetSystemTime é a seguinte:
Friend Class NativeMethods
Friend Declare Auto Sub GetSystemTime Lib "Kernel32.dll" (
ByVal sysTime As SystemTime)
End Class
internal static class NativeMethods
{
[DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
internal static extern void GetSystemTime(SystemTime st);
}
Observe que o argumento não é digitado SystemTime como um argumento de referência porque SystemTime é uma classe, não um tipo de valor. Ao contrário dos tipos de valor, as classes são sempre passadas por referência.
O exemplo de código a seguir mostra uma classe diferente Point que tem um método chamado SetXY. Como o tipo tem layout sequencial, ele pode ser passado para código não gerenciado e empacotado como uma estrutura. No entanto, o SetXY membro não é chamável a partir de código não gerenciado, mesmo que o objeto é passado por referência.
<StructLayout(LayoutKind.Sequential)> Public Class Point
Private x, y As Integer
Public Sub SetXY(x As Integer, y As Integer)
Me.x = x
Me.y = y
End Sub
End Class
[StructLayout(LayoutKind.Sequential)]
public class Point {
int x, y;
public void SetXY(int x, int y){
this.x = x;
this.y = y;
}
}
Tipos de valor usados na interoperabilidade COM
Os tipos formatados também podem ser passados para chamadas de método de interoperabilidade COM. Na verdade, quando exportados para uma biblioteca de tipos, os tipos de valor são automaticamente convertidos em estruturas. Como mostra o exemplo a seguir, o Point tipo de valor torna-se uma definição de tipo (typedef) com o nome Point. Todas as referências ao Point tipo de valor em outro lugar na biblioteca de tipos são substituídas pelo Point typedef.
Representação da biblioteca de tipos
typedef struct tagPoint {
int x;
int y;
} Point;
interface _Graphics {
…
HRESULT SetPoint ([in] Point p)
HRESULT SetPointRef ([in,out] Point *p)
HRESULT GetPoint ([out,retval] Point *p)
}
As mesmas regras usadas para organizar valores e referências a chamadas de invocação de plataforma são usadas ao organizar através de interfaces COM. Por exemplo, quando uma instância do Point tipo de valor é passada do .NET Framework para COM, o Point é passado por valor. Se o tipo de valor Point for passado por referência, um ponteiro para um Point será passado na pilha. O marshaller de interoperabilidade não suporta níveis mais elevados de indireção (Ponto **) em nenhuma das direções.
Observação
As estruturas que têm o valor de enumeração LayoutKind definido como Explicit não podem ser usadas para interoperar com COM porque a biblioteca de tipos exportada não consegue expressar um layout explícito.
Tipos de valor do sistema
O System namespace tem vários tipos de valor que representam a forma encapsulada dos tipos primitivos em tempo de execução. Por exemplo, a estrutura de tipo System.Int32 de valor representa o formato encapsulado de ELEMENT_TYPE_I4. Em vez de agrupar esses tipos como estruturas, como outros tipos formatados são, você os organiza da mesma maneira que os tipos primitivos que eles encaixotam.
System.Int32 é, portanto, empacotado como ELEMENT_TYPE_I4 em vez de como uma estrutura contendo um único membro do tipo long. A tabela seguinte contém uma lista dos tipos de valor no System namespace que são representações em caixa dos tipos primitivos.
| Tipo de valor do sistema | Tipo de elemento |
|---|---|
| System.Boolean | TIPO_DE_ELEMENTO_BOOLEANO |
| System.SByte | ELEMENT_TYPE_I1 |
| System.Byte | ELEMENT_TYPE_UI1 |
| System.Char | ELEMENT_TYPE_CHAR |
| System.Int16 | ELEMENT_TYPE_I2 |
| System.UInt16 | ELEMENT_TYPE_U2 |
| System.Int32 | ELEMENT_TYPE_I4 |
| System.UInt32 | ELEMENT_TYPE_U4 |
| System.Int64 | ELEMENT_TYPE_I8 |
| System.UInt64 | ELEMENT_TYPE_U8 |
| System.Single | ELEMENT_TYPE_R4 |
| System.Double | ELEMENT_TYPE_R8 |
| System.String | ELEMENT_TYPE_STRING |
| System.IntPtr | ELEMENT_TYPE_I |
| System.UIntPtr | ELEMENT_TYPE_U |
Alguns outros tipos de valores no System namespace são tratados de forma diferente. Como o código não gerenciado já tem formatos bem estabelecidos para esses tipos, o marshaller tem regras especiais para organizá-los. A tabela seguinte lista os tipos de valores especiais no namespace System, bem como o tipo não gerido para o qual são convertidos.
| Tipo de valor do sistema | Tipo de Linguagem de Definição de Interface (IDL) |
|---|---|
| System.DateTime | DATA |
| System.Decimal | Decimal |
| System.Guid | GUID |
| System.Drawing.Color | OLE_COLOR |
O código seguinte mostra a definição dos tipos não geridos DATE,GUID, DECIMAL e OLE_COLOR na biblioteca de tipos Stdole2.
Representação da biblioteca de tipos
typedef double DATE;
typedef DWORD OLE_COLOR;
typedef struct tagDEC {
USHORT wReserved;
BYTE scale;
BYTE sign;
ULONG Hi32;
ULONGLONG Lo64;
} DECIMAL;
typedef struct tagGUID {
DWORD Data1;
WORD Data2;
WORD Data3;
BYTE Data4[ 8 ];
} GUID;
O código a seguir mostra as definições correspondentes na interface gerenciada IValueTypes .
Public Interface IValueTypes
Sub M1(d As System.DateTime)
Sub M2(d As System.Guid)
Sub M3(d As System.Decimal)
Sub M4(d As System.Drawing.Color)
End Interface
public interface IValueTypes {
void M1(System.DateTime d);
void M2(System.Guid d);
void M3(System.Decimal d);
void M4(System.Drawing.Color d);
}
Representação da biblioteca de tipos
[…]
interface IValueTypes : IDispatch {
HRESULT M1([in] DATE d);
HRESULT M2([in] GUID d);
HRESULT M3([in] DECIMAL d);
HRESULT M4([in] OLE_COLOR d);
};