2010-04-14 19 views
1

Je rencontre un problème avec PInvocation de certaines fonctions WinAPI qui acceptent les structures WAVEFORMATEX en tant que paramètres. Puisque la longueur de la structure WAVEFORMATEX peut varier, j'ai implémenté une classe WaveFormatEX qui est rassemblée par une classe de marshaller personnalisée (qui implémente ICustmoMarshaller). C'est à la suite d'un exemple fourni par Aaron Lerch dans son blog (Part 1, Part 2), mais avec quelques modifications de mon côté.C#: objet avec un marshaller personnalisé ne contenant pas de données après l'appel PInvoke

Quand j'appelle la fonction API de mon code, les méthodes et MarshalManagedToNativeMarshalNativeToManaged du placier personnalisé sont appelés et à la fin de MarshalNativeToManaged, l'objet géré contient les valeurs correctes. Mais lorsque l'exécution retourne à mon code appelant, l'objet WaveFormatEx ne contient pas les valeurs lues lors de l'appel d'API.

Donc la question est: Pourquoi les données qui sont correctement ramenées de l'origine à la gestion n'apparaissent pas dans mon objet WaveFormatEx après l'appel API natif? Qu'est-ce que je fais mal ici?

Edit:
Pour clarifier les choses, l'appel de fonction réussit, le fait de la marshalling l'objet WaveFormatEx retour au code managé. Juste au moment où l'exécution revient de la méthode de marshalling à l'étendue à partir de laquelle la méthode a été appelée, l'objet WaveFormatEx qui a été déclaré dans cette étendue d'appel ne contient pas les données de résultat.

Voici le prototype de fonction et la classe WAVEFORMATEX:

[DllImport("avifil32.dll")] 
public static extern int AVIStreamReadFormat(
    int Stream, 
    int Position, 
    [In, Out, MarshalAs(UnmanagedType.CustomMarshaler, 
     MarshalTypeRef = typeof(WaveFormatExMarshaler))] 
    WaveFormatEx Format, 
    ref int Size 
    ); 

[StructLayout(LayoutKind.Sequential)] 
public class WaveFormatEx 
{ 
    public int FormatTag; 
    public short Channels; 
    public int SamplesPerSec; 
    public int AvgBytesPerSec; 
    public short BlockAlign; 
    public short BitsPerSample; 
    public short Size; 
    public byte[] AdditionalData; 

    public WaveFormatEx(short AdditionalDataSize) 
    { 
     WaveFormat.Size = AdditionalDataSize; 
     AdditionalData = new byte[AdditionalDataSize]; 
    } 
} 

Les méthodes de triage ressemblent à ceci:

public object MarshalNativeToManaged(System.IntPtr NativeData) 
{ 
    WaveFormatEx ManagedObject = new WaveFormatEx(0); 
    ManagedObject = (WaveFormatEx)Marshal.PtrToStructure(
     NativeData, typeof(WaveFormatEx)); 

    ManagedObject.AdditionalData = new byte[ManagedObject.Size]; 

    // If there is extra data, marshal it 
    if (ManagedObject.WaveFormat.Size > 0) 
    { 
     NativeData = new IntPtr(
      NativeData.ToInt32() + 
      Marshal.SizeOf(typeof(WaveFormatEx))); 
     ManagedObject.AdditionalData = new byte[ManagedObject.WaveFormat.Size]; 
     Marshal.Copy(NativeData, ManagedObject.AdditionalData, 0, 
      ManagedObject.WaveFormat.Size); 
    } 
    return ManagedObject; 
} 

public System.IntPtr MarshalManagedToNative(object Object) 
{ 
    WaveFormatEx ManagedObject = (WaveFormatEx)Object; 

    IntPtr NativeStructure = Marshal.AllocHGlobal(
     GetNativeDataSize(ManagedObject) + ManagedObject.WaveFormat.Size); 

    Marshal.StructureToPtr(ManagedObject, NativeStructure, false); 

    // Marshal extra data 
    if (ManagedObject.WaveFormat.Size > 0) 
    { 
     IntPtr dataPtr = new IntPtr(NativeStructure.ToInt32() 
      + Marshal.SizeOf(typeof(WaveFormatEx))); 
     Marshal.Copy(ManagedObject.AdditionalData, 0, dataPtr, Math.Min(
      ManagedObject.WaveFormat.Size, 
      ManagedObject.AdditionalData.Length)); 
    } 
    return NativeStructure; 
} 

Et voici mon code d'appel:

WaveFormatEx test = new WaveFormatEx(100); 
int Size = System.Runtime.InteropServices.Marshal.SizeOf(test); 

// After this call, test.FormatTag should be set to 1 (PCM audio), 
// but it is still 0, as well as all the other members 
int Result = Avi.AVIStreamReadFormat(AudioStream, 0, test, ref Size); 

Répondre

2

Il sont plusieurs erreurs dans le code et les déclarations qui empêche ce code de travailler sur un 6 Système d'exploitation 4 bits. Veillez à définir la cible de la plate-forme sur x86. Etes-vous sûr que la fonction native renvoie effectivement des données? Quelle est la valeur de retour du résultat? Une valeur non nulle indique un échec.

La meilleure façon d'appeler cette fonction est de l'appeler deux fois. D'abord avec l'argument lpFormat défini sur null (IntPtr.Zero) afin qu'il vous indique la taille d'un tampon dont il a besoin (retourné par lpbcFormat). Ensuite, vous créez le tampon et l'appelez à nouveau.

Au lieu d'un marshaller personnalisé, je voudrais simplement créer le tampon avec Marshal.AllocHGobal après le premier appel et passer l'IntPtr il renvoie comme l'argument lpFormat dans le deuxième appel. Ensuite, si vous obtenez un code retour réussi, utilisez Marshal.PtrToStructure pour écrire WaveFormatEx. Et Marshal.Copy pour obtenir les données supplémentaires. Fwiw, en utilisant ref, force le P/Invoke marshaller à transmettre un WaveFormatEx ** à la fonction mais attend un WaveFormatEx *. Ce qui l'amènera à écraser les données dans le tas collecté par le garbage, en détruisant son format interne. Un kaboom est le suivant lorsque le CLR remarque cela.

Découvrez le NAudio project comme une bonne alternative pour ce faire vous-même.

+0

1) La cible est x86 2) La fonction réussit, et j'ai vérifié qu'elle renvoie des données en passant par 'MarshalNativeToManaged'. L'objet renvoyé par cette méthode a les données correctes. 3) Allouer le tampon manuellement et faire le marshalling en place est certainement une option, se penchera sur elle. 4) Cela explique - * Kaboom * est exactement ce qui s'y passe ... 5) Je connais NAudio, mais je voulais tout faire par moi-même (c'est un projet privé, autant dans le but d'apprendre des choses en créant un logiciel utile ...) En tout cas, merci beaucoup pour l'effort que vous avez mis dans votre réponse! – Treb