P调用“类"与“引用结构" [英] PInvoke 'class' Versus 'ref struct'

查看:108
本文介绍了P调用“类"与“引用结构"的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我在Google上四处搜寻时,我看到一些帖子说,在使用PInvoke时传递C#class与将ref struct传递给C API相同(以下是一篇

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).

但是,在运行示例时,我看到的行为与预期不同.其中ref struct充当真正的指针,而'class'不充当

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

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");
    }
}

现在将struct用于动物"使用C#:

Now onto C# using struct for `Animal':

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();
        }
    }
}

使用ref struct的输出符合预期,将C#领域中的第一个字母更改为'A':

The output using ref struct is as expected changing the the first letter to 'A' in C# realm:

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


但是,当我尝试使用class代替ref struct时:

However when I try to use class instead of 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();
    }
}

输出为:

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

因此,当我使用一个类时,分配给Animal的内存不会被修改.问题是为什么不呢?

So when I use a class the memory allocated to Animal does not get modified. The question is why not?

推荐答案

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

这很重要.您必须将该属性应用于类声明.您也将其应用于 struct 声明,但这实际上不是必需的,C#编译器会自动为结构发出此信息.修改CharSet属性.

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. Modulo the CharSet property.

类的默认[StructLayout]属性是不是 LayoutKind.Sequential,它是LayoutKind.Auto.这是CLR所利用的,它将重新组织一个类中的字段以提供最佳的布局.您可以在这篇文章中了解有关此内容的更多信息.

The default [StructLayout] attribute for classes is not LayoutKind.Sequential, it is 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. You can read more about it in this post.

最大的区别是,它使类不可变.这是一个一百美元的单词,表示pinvoke编组不能仅将普通指针传递到托管对象,它必须将托管对象转换到具有所请求布局的非托管对象.它通过分配内存和复制字段来实现.结果,本机代码对副本所做的任何更改都不会传播回原始受管对象.

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. 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.

除非您明确告诉Pinvoke编组人员执行此操作.修复:

Unless you explicitly tell the pinvoke marshaller to do this. Fix:

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

[OutAttribute]告诉它将更改传播回来.

The [OutAttribute] tells it to propagate changes back.

这篇关于P调用“类"与“引用结构"的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆