C#中extern关键字的作用是什么?

78
每当我深入研究反射器(reflector)时,就会遇到没有源代码的extern方法。我阅读了位于http://msdn.microsoft.com/en-us/library/e59b22c5(v=vs.80).aspx的MSDN文档。从那篇文章中得到的信息是带有extern修饰符的方法必须被注入。我理解这意味着它的工作方式有点像抽象工厂模式。我还注意到我从未见过非静态的extern方法。静态声明是否是必需的(我可以理解为什么这样做会有意义)?我仍然在猜测,不确定它实际上是如何工作的。对我来说,编译器似乎必须识别某些属性以缓解处理,但我不知道除了我遇到的MethodImplAttributeDllImportAttribute之类的属性外,还有哪些属性。如何利用extern属性?它说在许多情况下,这可以提高性能。另外,我该如何查看extern方法的源代码,例如Object.InternalGetEquals()

1
在 .Net 中,System.Object 类上的 MemberwiseClone() 方法是 extern 的,但不是 static 的。 - RBT
1
当一个外部方法包含 DllImport 属性时,方法声明也必须包括静态修饰符。 - RBT
4个回答

120
考虑阅读C#规范的第10.6.7节,其中回答了您许多问题。我在这里为您方便重现其部分内容:

当方法声明包括extern修饰符时,该方法称为外部方法。外部方法是通过外部实现的,通常使用其他语言而不是C#。因为外部方法声明不提供实际实现,所以外部方法的方法体只包含一个分号。外部方法可能不是泛型的。extern修饰符通常与DllImport属性一起使用,允许外部方法由DLL(动态链接库)来实现。执行环境可能支持其他机制,通过这些机制可以提供外部方法的实现。当外部方法包括DllImport属性时,方法声明也必须包括static修饰符。


如何利用extern属性?

  • 使用您选择的非托管语言编写代码。
  • 将其编译为DLL,导出代码的入口点。
  • 创建一个Interop库,将给定DLL中的方法定义为extern方法。
  • 从C#中调用它。
  • 获得收益!

如何查看像Object.InternalGetEquals()之类的extern方法的源代码?

转到https://github.com/dotnet/coreclr/tree/master/src/vm


8
SSLI20源代码对于像我这样的凡人来说仍然是内部方法的相当精确的副本。无论如何,足以回答一系列CLR问题。请参考clr/src/vm/ecall.cpp,了解从框架名称到C++函数的映射。下载链接在此处:http://www.microsoft.com/downloads/en/details.aspx?FamilyId=8C09FD61-3F26-4555-AE17-3121B4F51D4D&displaylang=en - Hans Passant
2
@Hans:请注意,在查看了SSCLI之后,您不被允许为Mono项目做出贡献。 - Dan Abramov
1
嗯,你最好也要避免接受微软员工的回答。 - Hans Passant
@Eric,为什么外部方法可能不是通用的?我注意到C#编译器可以很好地编译通用的外部方法。我正在开发一个后编译器,将方法插入类中。所以我想使用extern作为占位符,供后编译器插入代码。我认为extern并不是设计用于仅与[DllImport]一起使用的,对吗? - Jordão
1
@Eric:感谢你的解释。我现在明白它们的目的了。我只是想广泛解释一下,以涵盖我的情况,而不是用我的情况替换原始情况。有点像人们滥用using语句一样。 - Jordão
显示剩余4条评论

38

带有extern[DllImport]属性标记的方法通常是对C库的调用。这个特性对于调用WinAPI或遗留代码非常有用。

这是MSDN上的例子:

using System;
using System.Runtime.InteropServices;
class MainClass 
{
   [DllImport("User32.dll")]
   public static extern int MessageBox(int h, string m, string c, int type);

   static int Main() 
   {
      string myString; 
      Console.Write("Enter your message: ");
      myString = Console.ReadLine();
      return MessageBox(0, myString, "My Message Box", 0);
   }
}

它调用了在Windows user32.dll库中定义的MessageBox。运行时会为您完成所有繁重的工作,但有时需要手动管理内存。如果签名错误,您的程序可能会在调用时失败,可能会引入泄漏或方法可能返回完全不同的内容,所以要小心!我发现pinvoke.net是纠正不同API签名的好工具。

.NET Framework中一些没有[DllImport]属性但使用[MethodImpl(MethodImplOptions.InternalCall)]属性装饰的extern方法通常是在CLR本身中实现的,CLR本身也是用C语言编写的。其中一些方法无法在C#中实现,因为它们管理运行时本身,而一些方法之所以使用C实现是因为其性能很重要且C更快。

MSDN says关于它们的内容如下:

指定一个内部调用。内部调用是对在公共语言运行时内部实现的方法的调用。

就实际的实现代码而言,我怀疑你可能无法从 Microsoft 获取到它,但是有一些很酷的 CLR 替代实现,所以一定要查看它们。


1
需要记住的一件事是,当使用extern和DLLImport时,你经常会发现也会涉及到“不安全”的引用。请记住,这允许您使用实际指针,并且在编写自己的extern方法时,必须非常小心,以避免任何内存泄漏。 - VulgarBinary

3

extern 是用于平台调用(pinvoke)以方便托管程序集调用非托管代码的。 extern 关键字告知编译器需要生成正确的代码,以实现正确的数据封送。


3
我们在方法声明中使用“extern”修饰符。它用于指示该方法是外部实现的。 “extern”修饰符的通常用途是与DllImport属性一起使用。使用此属性来管理非C#函数调用。
如果您使用extern修饰符,则必须包括以下命名空间: using System.Runtime.InteropServices; 语法如下所示: [DllImport("User32.dll")] public static extern int MessageBox(int h, string m, string c, int type);

丹·阿布拉莫夫有说过任何不同的话吗? - Mahyar Pasarzangene

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接