1. 程式人生 > 實用技巧 >關鍵字extern用法——C#

關鍵字extern用法——C#

C# 關鍵字extern用法

修飾符用於宣告在外部實現的方法。extern 修飾符的常見用法是在使用 Interop 服務調入非

託管程式碼時與 DllImport 屬性一起使用;在這種情況下,該方法還必須宣告為 static,如下面的示例所示:[DllImport("avifil32.dll")]
private static extern void AVIFileInit();

注意
extern 關鍵字還可以定義外部程式集別名,使得可以從單個程式集中引用同一組件的不同版本。

將 abstract(C# 參考)和 extern 修飾符一起使用來修改同一成員是錯誤的。使用 extern 修飾符意味著方法在 C# 程式碼的外部實現,而使

用 abstract 修飾符意味著在類中未提供方法實現。注意
extern 關鍵字在使用上比在 C++ 中有更多的限制。若要與 C++ 關鍵字進行比較,請參見 C++ Language Reference 中的 Using extern to

Specify Linkage。

在該示例中,程式接收來自使用者的字串並將該字串顯示在訊息框中。程式使用從 User32.dll 庫匯入的 MessageBox 方法。

using System;  
using System.Runtime.InteropServices;  
class MainClass   
{  
  [DllImport("User32.dll")]  
  public static extern int MessageBox(int h, string m, string c, int type);  
  //hovertree.com
  static int Main()   
  {  
      string myString;   
      Console.Write("Enter your message: ");  
      myString = Console.ReadLine();  
      return MessageBox(0, myString, "My Message Box", 0);  
  }  
}

此示例使用 C 程式建立一個 DLL,在下一示例中將從 C# 程式呼叫該 DLL。

[cpp]
// cmdll.c   何問起
// compile with: /LD  
int __declspec(dllexport) SampleMethod(int i)  
{  
  return i*10;  
} 

該示例使用兩個檔案 CM.cs 和 Cmdll.c 來說明 extern。C 檔案是示例 2 中建立的外部 DLL,它從 C# 程式內呼叫。

[c-sharp]
// cm.cs  
using System;  
using System.Runtime.InteropServices;  
public class MainClass   
{  
  [DllImport("Cmdll.dll")]  
  public static extern int SampleMethod(int x);  
  //hovertree.com
  static void Main()   
  {  
      Console.WriteLine("SampleMethod() returns {0}.", SampleMethod(5));  
  }  
} 

輸出

SampleMethod() returns 50.
生成專案:
使用 Visual C++ 命令列將 Cmdll.c 編譯為 DLL:cl /LD Cmdll.c使用命令列編譯 CM.cs:csc CM.cs這將建立可執行檔案 CM.exe。執行此程

序時,SampleMethod 將值 5 傳遞到 DLL 檔案,該檔案將此值乘以 10 返回extern "C"
  extern "C" 包含雙重含義,從字面上即可得到:首先,被它修飾的目標是“extern”的;其次,被它修飾的目標是“C”的。讓我們來詳細解讀這兩重含義。

  (1) 被extern "C"限定的函式或變數是extern型別的;

  extern是C/C++語言中表明函式和全域性變數作用範圍(可見性)的關鍵字,該關鍵字告訴編譯器,其宣告的函式和變數可以在本模組或其它模組中使用。記住,下列語句:

  extern int a;

  僅僅是一個變數的宣告,其並不是在定義變數a,並未為a分配記憶體空間。變數a在所有模組中作為一種全域性變數只能被定義一次,否則會出現連線錯誤。

  通常,在模組的標頭檔案中對本模組提供給其它模組引用的函式和全域性變數以關鍵字extern宣告。例如,如果模組B欲引用該模組A中定義的全域性變數和函式時只需包含模組A的標頭檔案即可。這樣,模組B中呼叫模組A中的函式時,在編譯階段,模組B雖然找不到該函式,但是並不會報錯;它會在連線階段中從模組A編譯生成的目的碼中找到此函式。

例項:

[cpp]
//cppExample.h  
  
#ifndef MODULE_A_H  
#define MODULE_A_H  
extern int foo( int x, int y );// 宣告函式,其為extern型別  
#endif  
  
//  
  //hovertree.com
  
//  
  
//cppExample.cpp  
  
#include <iostream>  
#include "cppExample.h"  
using namespace std;  
  
int foo( int x, int y )//定義extern型別的函式  
{  
cout << x+y;  
return 0;  
}  
  
  
  
  
//main.cpp  
  
#include <iostream>   
#include"cppExample.h"  
using namespace std;  
  
void main()  
{  
foo(5,6);//函式呼叫  
}  
  
/


這裡呢,就有了個疑問:其實如果再cppExample.h中聲明瞭一個全域性函式int foo(Int x, int y),然後在main.cpp中呼叫foo函式,其都是一樣的,都可以成功編譯,函式成功呼叫,那這個extern到底有什麼用呢,不是多此一舉麼?(暫放,以後補充)

與extern對應的關鍵字是static,被它修飾的全域性變數和函式只能在本模組中使用。因此,一個函式或變數只可能被本模組使用時,其不可能被extern “C”修飾。


(2) 被extern "C"修飾的變數和函式是按照C語言方式編譯和連線的;
  未加extern “C”宣告時的編譯方式
  首先看看C++中對類似C的函式是怎樣編譯的。

  作為一種面向物件的語言,C++支援函式過載,而過程式語言C則不支援。函式被C++編譯後在符號庫中的名字與C語言的不同。例如,假設某個函式的原型為:


  void foo( int x, int y );

  該函式被C編譯器編譯後在符號庫中的名字為_foo,而C++編譯器則會產生像_foo_int_int之類的名字(不同的編譯器可能生成的名字不同,但是都採用了相同的機制,生成的新名字稱為“mangledname”)。_foo_int_int這樣的名字包含了函式名、函式引數數量及型別資訊,C++就是靠這種機制來實現函式過載的。例如,在C++中,函式void foo( int x, int y )與void foo( int x, float y)編譯生成的符號是不相同的,後者為_foo_int_float。


  同樣地,C++中的變數除支援區域性變數外,還支援類成員變數和全域性變數。使用者所編寫程式的類成員變數可能與全域性變數同名,我們以"."來區分。而本質上,編譯器在進行編譯時,與函式的處理相似,也為類中的變數取了一個獨一無二的名字,這個名字與使用者程式中同名的全域性變數名字不同。

  未加extern "C"宣告時的連線方式
  假設在C++中,模組A的標頭檔案如下:

[cpp]
// 模組A標頭檔案 moduleA.h  
  
  #ifndef MODULE_A_H  
  #define MODULE_A_H  
  int foo( int x, int y );  
  #endif  
  
  在模組B中引用該函式:  
  
  // 模組B實現檔案 moduleB.cpp  
  #include "moduleA.h"  
  foo(2,3); 
//hovertree.com

  

  實際上,在連線階段,聯結器會從模組A生成的目標檔案moduleA.obj中尋找_foo_int_int這樣的符號!
  加extern "C"聲明後的編譯和連線方式
  加extern "C"聲明後,模組A的標頭檔案變為:

  

[cpp]
// 模組A標頭檔案 moduleA.h  
  
  #ifndef MODULE_A_H  
  #define MODULE_A_H  
  extern "C" int foo( int x, int y );  
  #endif  
//hovertree.com

  在模組B的實現檔案中仍然呼叫foo( 2,3 ),其結果是:

  (1)模組A編譯生成foo的目的碼時,沒有對其名字進行特殊處理,採用了C語言的方式;

  (2)聯結器在為模組B的目的碼尋找foo(2,3)呼叫時,尋找的是未經修改的符號名_foo。

  如果在模組A中函式聲明瞭foo為extern "C"型別,而模組B中包含的是extern int foo( int x, int y ) ,則模組B找不到模組A中的函式;反之亦然。

  所以,可以用一句話概括extern“C”這個宣告的真實目的(任何語言中的任何語法特性的誕生都不是隨意而為的,來源於真實世界的需求驅動。我們在思考問題時,不能只停留在這個語言是怎麼做的,還要問一問它為什麼要這麼做,動機是什麼,這樣我們可以更深入地理解許多問題):

二. extern "C"的慣用法

  (1)在C++中引用C語言中的函式和變數,在包含C語言標頭檔案(假設為cExample.h)時,需進行下列處理:
  

[cpp]
extern "C"  
  {  
  #include "cExample.h"  
  }  


  而在C語言的標頭檔案中,對其外部函式只能指定為extern型別,C語言中不支援extern "C"宣告,在.c檔案中包含了extern "C"時會出現編譯語法錯誤。

筆者編寫的C++引用C函式例子工程中包含的三個檔案的原始碼如下:

[cpp]
/* c語言標頭檔案:cExample.h */  
  
  #ifndef C_EXAMPLE_H  
  #define C_EXAMPLE_H  
  extern int add(int x,int y);  
  #endif  
  
  /* c語言實現檔案:cExample.c */  
  
  #include "cExample.h"  
  int add( int x, int y )  
  {  
  return x + y;  
  }  
  
  // c++實現檔案,呼叫add:cppFile.cpp  
  //以下這段可以替換成 extern "C" int foo(int x, int y);  
  
    extern "C"  
    {  
  #include "cExample.c"  
  }  
  
    //替換結束  hovertree.com
  
  int main(int argc, char* argv[])  
  {  
  add(2,3);  
  return 0;  
  }  


    如果C++呼叫一個C語言編寫的.DLL時,當包括.DLL的標頭檔案或宣告介面函式時,應加extern "C" { }。

(2)在C中引用C++語言中的函式和變數時,C++的標頭檔案需新增extern "C",但是在C語言中不能直接引用聲明瞭extern "C"的該標頭檔案,應該僅將C檔案中將C++中定義的extern "C"函式宣告為extern型別。

  筆者編寫的C引用C++函式例子工程中包含的三個檔案的原始碼如下:

[cpp]
//C++標頭檔案 cppExample.h  
#ifndef CPP_EXAMPLE_H  
#define CPP_EXAMPLE_H  
extern "C" int add( int x, int y );  
#endif  
  
//---------------------------------------------------------//  
  
//C++實現檔案 cppExample.cpp  
#include <stdio.h>  
#include "cppHeader.h"  
int add( int x, int y )  
{  
return x + y;  
}  
  
//----------------------------------------------------------  
#include<stdio.h>  
/* C實現檔案 cFile.c 
/* 這樣會編譯出錯:#include "cExample.h"  */  
extern int add( int x, int y );  
  
int main()  
{  
int a = add(2,3);  
return 0;  
}   
//hovertree.com

轉自:http://hovertree.com/h/bjaf/1iogox2j.htm

http://www.cnblogs.com/sosoft/p/timu.html

轉載於:https://www.cnblogs.com/cjm123/p/8660584.html