Comportamento de Marshalling Padrão
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.
Nota
A organizaçã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 você definir o método como um protótipo de invocação de plataforma, substitua cada tipo BSTR por um String tipo e chame MethodOne
, o Common Language Runtime tentará liberar b
duas vezes. Você pode alterar o comportamento de empacotamento usando IntPtr tipos em vez de tipos de cadeia de caracteres .
O tempo de execução sempre usa o método CoTaskMemFree no Windows e o método livre em outras plataformas para liberar memória. Se a memória com a qual você está trabalhando não foi alocada com o método CoTaskMemAlloc no Windows ou o método malloc em outras plataformas, você deve usar um IntPtr e liberar a memória manualmente usando o método apropriado. Da mesma forma, você pode evitar a liberação automática de memória em situações em que a memória nunca deve ser liberada, como ao usar a função GetCommandLine do Kernel32.dll, que retorna 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 gerenciada é passada para COM, o marshaller de interoperabilidade automaticamente encapsula a classe com um proxy COM e passa a interface de 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 gerenciado, o marshaller de interoperabilidade é responsável por envolver a interface com o wrapper adequado e passar o wrapper para o método gerenciado. 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 esse é o caso, o marshaller consulta o objeto para sua interface IUnknown e compara a interface retornada com as interfaces de outros objetos que já estão encapsulados. 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 interface IProvideClassInfo . Se fornecido, o marshaller usa o ITypeInfo 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 gerenciado é organizado como uma interface COM ou como um ponteiro de função, com base no mecanismo de chamada:
Para a invocação de plataforma, um delegado é empacotado como um ponteiro de função não gerenciado por padrão.
Para interoperabilidade COM, um delegado é empacotado como uma interface COM do tipo _Delegate por padrão. A interface _Delegate é definida na biblioteca de tipos Mscorlib.tlb e contém o Delegate.DynamicInvoke método, que permite chamar o método ao qual o delegado faz referência.
A tabela a seguir mostra as opções de empacotamento para o tipo de dados de delegado gerenciado. O MarshalAsAttribute atributo fornece vários UnmanagedType valores de enumeração para delegados marechal.
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 são exportados para uma biblioteca de DelegateTestInterface
tipos COM. Observe que apenas os delegados marcados com a palavra-chave ref (ou ByRef) são passados como parâmetros de entrada/saída.
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 empacotados, int
aqui representa um ponteiro para um vazio (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.
Nota
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 cb
objeto, passada para o SetChangeHandler
método, não se mantém cb
viva além da vida útil do Test
método. Uma vez que o cb
objeto é coletado 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 vírgula 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 é livre para reordenar os membros do tipo para 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 Point
tipos e 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 PtInRect da API do Microsoft Windows da seguinte maneira:
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.
Nota
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.
Nota
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 a seguir, a SystemTime
classe tem layout de membro sequencial e pode ser passada para a função GetSystemTime da API 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 função GetSystemTime é definida da seguinte forma:
void GetSystemTime(SYSTEMTIME* SystemTime);
A definição de invocação de plataforma equivalente 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
valor é passado por. Se o tipo de Point
valor 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.
Nota
Estruturas com o LayoutKind valor de enumeração definido como Explicit não podem ser usadas na interoperabilidade COM porque a biblioteca de tipos exportados não pode expressar um layout explícito.
Tipos de valor do sistema
O System namespace tem vários tipos de valor que representam a forma em caixa dos tipos primitivos de tempo de execução. Por exemplo, a estrutura de tipo System.Int32 de valor representa a forma em caixa 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 a seguir contém uma lista dos tipos de valor no namespace System que são representações em caixa de tipos primitivos.
Tipo de valor do sistema | Tipo de elemento |
---|---|
System.Boolean | ELEMENT_TYPE_BOOLEAN |
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 valor no namespace System 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 a seguir lista os tipos de valor especiais no namespace System , bem como o tipo não gerenciado para o qual eles são empacotados.
Tipo de valor do sistema | Tipo de IDL |
---|---|
System.DateTime | DATE |
System.Decimal | DECIMAL |
System.Guid | GUID |
System.Drawing.Color | OLE_COLOR |
O código a seguir mostra a definição dos tipos não gerenciados 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);
};