Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
O marshaling de interoperabilidade opera em regras que determinam como os dados associados aos parâmetros de método se comportam, conforme eles passam entre a memória gerenciada e não gerenciada. Essas regras internas controlam atividades de marshaling como transformações de tipo de dados, se um receptor pode alterar os dados aprovados para ele e retornar essas alterações ao chamador e em quais circunstâncias o marshaler fornece otimizações de desempenho.
Esta seção identifica as características comportamentais padrão do serviço de marshaling de interoperabilidade. Ela apresenta informações detalhadas sobre o marshaling de matrizes, tipos boolianos, tipos char, representantes, classes, objetos, cadeias de caracteres e estruturas.
Observação
Não há suporte ao marshaling de tipos genéricos. Para obter mais informações, consulte como interoperar usando tipos genéricos.
Gerenciamento de memória com o marshaler de interoperabilidade
O marshaler de interoperabilidade sempre tenta liberar a memória alocada pelo 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 o C++nativo.
Poderá haver confusão se você antecipar o comportamento do C++ nativo (sem liberação de memória) ao usar a invocação de plataforma, que libera a memória para ponteiros automaticamente. Por exemplo, chamar o método não gerenciado a seguir 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 BSTR tipo por um String tipo e chame MethodOne, o common language runtime tentará liberar b duas vezes. Você pode alterar o comportamento de marshalling usando tipos IntPtr em vez de usar tipos String.
O runtime sempre usa o método CoTaskMemFree no Windows e o método free em outras plataformas para liberar memória. Se a memória com a qual você está trabalhando não foi alocada com o CoTaskMemAlloc método no Windows ou malloc método em outras plataformas, você deve usar uma 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 GetCommandLine função de 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.
Marshaling padrão para classes
As classes só podem ser agrupadas pela interoperação COM e sempre são agrupadas como interfaces. Em alguns casos, a interface usada para realizar marshaling da classe é conhecida como a interface de classe. Para obter informações sobre como substituir a interface de classe com uma interface de sua escolha, consulte Introdução à interface de classe.
Passando classes para o COM
Quando uma classe gerenciada é passada para COM, o marshalizador de interoperabilidade encapsula automaticamente a classe com um proxy COM e passa a interface de classe criada 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 normalmente não são usadas como argumentos de método no COM. Em vez disso, uma interface padrão geralmente é passada no lugar da coclasse.
Quando uma interface é passada para um código gerenciado, o marshaler de interoperabilidade é responsável por encapsular a interface com o wrapper apropriado e passar o wrapper para o método gerenciado. Determinar qual wrapper usar pode ser difícil. Cada instância de um objeto COM tem um único wrapper exclusivo, independentemente de quantas interfaces o objeto implementa. 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 wrapper mantenha o mesmo tipo em todo seu tempo de vida, o marshaler de interoperabilidade deve identificar o wrapper correto na primeira vez que uma interface exposta pelo objeto é passada por meio do marshaler. O marshaller identifica o objeto examinando uma das interfaces que o objeto implementa.
Por exemplo, o marshaler 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 pelo marshaler pela primeira vez, o marshaler verifica se a interface está sendo recebida de um objeto conhecido. Essa 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 corresponder a interface com o objeto gerenciado que fornece a implementação. Em seguida, o objeto gerenciado é 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
IUnknowninterface 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 usará o CLSID retornado de IProvideClassInfo2.GetGUID para identificar a coclass que fornece a interface. Com o CLSID, o marshaler pode localizar o wrapper no Registro, caso o assembly já tenha sido registrado anteriormente.
O marshaller consulta a interface para a
IProvideClassInfointerface. Se fornecido, o marshaller usará oITypeInforetornado 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 encapsula a interface com uma classe de wrapper genérica chamada System.__ComObject.
Marshaling padrão para representantes
Um representante gerenciado tem o marshaling realizado como uma interface COM ou como um ponteiro de função, com base no mecanismo de chamada:
Para a invocação de plataforma, um representante tem o marshaling realizado como um ponteiro de função não gerenciada, por padrão.
Para interoperabilidade COM, um delegate é passado como uma interface COM do tipo
_Delegatepor padrão. A_Delegateinterface é definida na biblioteca de tipos Mscorlib.tlb e contém o Delegate.DynamicInvoke método, que permite chamar o método que o delegado faz referência.
A tabela a seguir mostra as opções de marshaling para o tipo de dados do representante gerenciado. O atributo MarshalAsAttribute fornece vários valores de enumeração UnmanagedType para realizar marshaling de representantes.
| Tipo de enumeração | Descrição do formato não gerenciado |
|---|---|
| UnmanagedType.FunctionPtr | Um ponteiro de função não gerenciada. |
| 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. Observe que apenas delegados marcados com a ref palavra-chave (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 representantes realizam marshal como UnmanagedType.FunctionPtr, o resultado é um int e um ponteiro para um int. Como os tipos de delegado estão realizando marshal, int representa um ponteiro para um void (void*), que é o endereço do delegado na memória. Em outras palavras, esse resultado é específico para sistemas Windows de 32 bits, pois int aqui representa o tamanho do ponteiro de função.
Observação
Uma referência ao ponteiro de função para um representante gerenciado mantido por um código não gerenciado não impede o Common Language Runtime de executar 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 mantém cb viva além da vida útil do Test método. Depois 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 objeto cb é mantido ativo enquanto o ponteiro de função não gerenciada está em uso. Opcionalmente, você pode fazer com que o código não gerenciado notifique o código gerenciado quando o ponteiro de 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;
}
}
Marshaling padrão para tipos de valor
A maioria dos tipos de valor, como inteiros e números de ponto flutuante, é blittable e não exige marshaling. Outros tipos não blittable têm diferentes representações na memória gerenciada e não gerenciada e exigem marshaling. Outros tipos ainda exigem formatação explícita no limite de interoperação.
Esta seção fornece informações sobre os seguintes tipos de valor formatado:
Além de descrever os tipos formatados, este tópico identifica os tipos de valor do sistema que têm um comportamento de marshaling 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 valores de enumeração LayoutKind:
LayoutKind.Auto
Indica que o Common Language Runtime está 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 realizar marshaling de uma estrutura como essa causa uma exceção automaticamente.
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.Explicit
Indica que os membros sã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 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;
}
Ao ter o marshaling realizado para um código não gerenciado, esses tipos formatados têm o marshaling realizado como estruturas C-style. Isso fornece uma maneira fácil de chamar uma API não gerenciada que tenha argumentos de estrutura. Por exemplo, as estruturas POINT e RECT podem ser passadas para a função de API PtInRect 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 tipo de valor Rect deve ser passado por referência porque a API não gerenciada está esperando que um ponteiro para um RECT seja passado para a função. O tipo de valor Point é passado por valor porque a API não gerenciada espera que POINT seja passado na pilha. Essa diferença sutil é muito importante. As referências são passadas para um código não gerenciado como ponteiros. Os valores são passados para um código não gerenciado na pilha.
Observação
Quando um tipo formatado é empacotado como uma estrutura, somente os campos dentro do tipo são acessíveis. Se o tipo tiver métodos, propriedades ou eventos, eles estarão inacessíveis do código não gerenciado.
As classes também podem ser empacotadas para código não gerenciado como estruturas de estilo C, desde que tenham layout de membro fixo. As informações de layout do 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, as alterações feitas nos membros do tipo pelo receptor 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 nos membros do tipo blittable de um tipo pelo receptor 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: na primeira vez, em que um argumento é passado para o lado não gerenciado e, na segunda, após o retorno da chamada. Devido a essa sobrecarga agregada, os parâmetros de Entrada/Saída devem ser aplicados explicitamente a um argumento se o chamador deseja ver as alterações feitas pelo receptor.
No exemplo a seguir, a SystemTime classe tem layout de membro sequencial e pode ser passada para a função de 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 maneira:
void GetSystemTime(SYSTEMTIME* SystemTime);
A definição de invocação da 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 SystemTime argumento não é digitado 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 membro SetXY não pode ser chamado em um código não gerenciado, mesmo que o objeto seja 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 da interoperabilidade COM. Na verdade, quando exportados para uma biblioteca de tipos, os tipos de valor são convertidos automaticamente em estruturas. Como mostra o exemplo a seguir, o Point tipo de valor se torna 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 realizar marshaling de valores e de referências para chamadas de invocação de plataforma são usadas durante o marshaling por meio de interfaces COM. Por exemplo, quando uma instância do tipo de valor Point é passada do .NET Framework para o COM, o Point é passado por valor. Se o tipo de valor Point é passado por referência, um ponteiro para um Point é passado na pilha. O marshaler de interoperabilidade não dá suporte a níveis mais altos de indireção (Ponto **) em qualquer direção.
Observação
Estruturas com o LayoutKind valor de enumeração definido como Explicit não podem ser usadas na interoperabilidade COM porque a biblioteca de tipos exportada 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 runtime. Por exemplo, a estrutura de valor do tipo System.Int32 representa a forma encapsulada de ELEMENT_TYPE_I4. Em vez de realizar marshaling desses tipos como estruturas, assim como ocorre com outros tipos formatados, realize marshaling deles da mesma maneira como os tipos primitivos demarcados por eles.
System.Int32 é, portanto, empacotado como ELEMENT_TYPE_I4 em vez de como uma estrutura que contém um único membro do tipo long. A tabela a seguir contém uma lista dos tipos de valor no System namespace que são representações em caixa de tipos primitivos.
| Tipo de valor do sistema | Tipo de elemento |
|---|---|
| System.Boolean | ELEMENTO_TIPO_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 valor no System namespace são tratados de forma diferente. Como o código não gerenciado já possui formatos bem estabelecidos para esses tipos, o mapeador tem regras especiais para o mapeamento deles. A tabela a seguir lista os tipos de valor especiais no System namespace, bem como o tipo não gerenciado para o qual eles são empacotados.
| Tipo de valor do sistema | Tipo de IDL |
|---|---|
| System.DateTime | DATA |
| 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);
};