简体   繁体   English

PInvoke'class'Versus'ref struct'

[英]PInvoke 'class' Versus 'ref struct'

When I googled around I saw posts saying that passing a C# class is the same as passing ref struct to a C API while using PInvoke (here is one the posts C# PInvoke struct vs class access violation ). 当我用Google搜索时,我看到帖子说传递C# class与使用PInvoke时将ref struct传递给C API相同(这里是一个帖子C#PInvoke struct vs class access violation )。

However when running an example I am seeing a different behavior than expected. 但是,在运行示例时,我看到的行为与预期不同。 Where ref struct acts as a real pointer while 'class' does not 其中ref struct充当真正的指针,而'class'则不是

C code : C代码:

//PInvokeProvider.h
#include "stdafx.h" 
typedef struct Animal_s
{
    char Name[10000];
} Animal;

extern "C" void __declspec(dllexport) ChangeName(Animal* pAnimal);


//PInvokeProvider.cpp    
#include "stdafx.h"
#include <stdio.h>
#include "PInvokeProvider.h"

extern "C" {
    void ChangeName(Animal* pAnimal)
    {
        printf("Entered C++\n");
        printf("Recieved animal : %s\n", pAnimal->Name);
        printf("This function will change the first letter of animal to 'A'\n");
        pAnimal->Name[0] = 'A';
        printf("Animal changed to : %s\n", pAnimal->Name);
        printf("Leaving C++\n");
    }
}

Now onto C# using struct for `Animal': 现在使用struct for“Animal”进入C#:

namespace PInvokeConsumer
{
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct Animal
    {
        /// char[10000]
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10000)]
        public string Name;

        public Animal(string name)
        {
            Name = name;
        }
    }

    public partial class NativeMethods
    {
        [DllImportAttribute("PInvokeProvider.dll", 
                            EntryPoint = "ChangeName", 
                            CallingConvention = CallingConvention.Cdecl)]
        public static extern void ChangeName(ref Animal pAnimal);
    }

    internal class Program
    {
        public static void Main(string[] args)
        {
            Animal animal = new Animal("Lion");

            Console.WriteLine("Animal : {0}", animal.Name);

            Console.WriteLine("Leaving C#");
            NativeMethods.ChangeName(ref animal);
            Console.WriteLine("Back to C#");

            Console.WriteLine("Animal : {0}", animal.Name);
            Console.ReadKey();
        }
    }
}

The output using ref struct is as expected changing the the first letter to 'A' in C# realm: 使用ref struct的输出正如预期的那样在C#域中将第一个字母改为'A':

Animal : Lion
Leaving C#
Entered C++
Recieved animal : Lion
This function will change the first letter of animal to 'A'
Animal changed to : Aion
Leaving C++
Back to C#
Animal : Aion


However when I try to use class instead of ref struct : 但是当我尝试使用class而不是ref struct

namespace PInvokeConsumer
{
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public class Animal
    {
        /// char[10000]
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10000)]
        public string Name;

        public Animal(string name)
        {
            Name = name;
        }
    }


    public partial class NativeMethods
    {
        [DllImportAttribute("PInvokeProvider.dll", 
                            EntryPoint = "ChangeName", 
                            CallingConvention = CallingConvention.Cdecl)]
        public static extern void ChangeName(Animal pAnimal);
    }

    public static void Main(string[] args)
    {
        Animal animal = new Animal("Lion");

        Console.WriteLine("Animal : {0}", animal.Name);

        Console.WriteLine("Leaving C#");
        NativeMethods.ChangeName(animal);
        Console.WriteLine("Back to C#");

        Console.WriteLine("Animal : {0}", animal.Name);
        Console.ReadKey();
    }
}

The output is: 输出是:

Animal : Lion
Leaving C#
Entered C++
Recieved animal : Lion
This function will change the first letter of animal to 'A'
Animal changed to : Aion
Leaving C++
Back to C#
Animal : Lion

So when I use a class the memory allocated to Animal does not get modified. 因此,当我使用类时,分配给Animal的内存不会被修改。 The question is why not? 问题是为什么不呢?

  [StructLayout(LayoutKind.Sequential, ...)]

This is what matters. 这才是最重要的。 You had to apply that attribute to the class declaration. 必须将该属性应用于类声明。 You also applied it to the struct declaration but that wasn't actually necessary, the C# compiler emits this automatically for structs. 您还将它应用于结构声明但实际上并不是必需的,C#编译器会自动为结构发出此声明。 Modulo the CharSet property. 模数CharSet属性。

The default [StructLayout] attribute for classes is not LayoutKind.Sequential, it is LayoutKind.Auto. 类的默认[StructLayout]属性不是 LayoutKind.Sequential,它是LayoutKind.Auto。 Which is something the CLR takes advantage of, it will reorganize the fields in a class to come up with the best possible layout. 这是CLR利用的东西,它将重新组织类中的字段以提供最佳布局。 You can read more about it in this post . 您可以在这篇文章中阅读更多相关信息

The big difference is that it makes a class non-blittable. 最大的区别在于它使一个类不闪烁。 Which is a hundred dollar word that means that the pinvoke marshaller cannot just pass a plain pointer to the managed object, it has to convert the managed object to an unmanaged one that has the requested layout. 这是一百美元的单词意味着pinvoke marshaller不能只是将普通指针传递给托管对象,它必须托管对象转换为具有所请求布局的非托管对象。 It does so by allocating memory and copying the fields. 它通过分配内存和复制字段来实现。 With the consequence that any changes that the native code makes to the copy doesn't get propagated back to the original managed object. 结果是,本机代码对副本所做的任何更改都不会传播回原始托管对象。

Unless you explicitly tell the pinvoke marshaller to do this. 除非你明确告诉pinvoke marshaller这样做。 Fix: 固定:

    [DllImportAttribute(...)]
    public static extern void ChangeName([In, Out]Animal pAnimal);

The [OutAttribute] tells it to propagate changes back. [OutAttribute]告诉它传回更改。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM