2012-03-13 93 views
2

cl /c /clr /W4編譯器在編譯下面的代碼表示如何修復模板類的警告C4793?

warning C4793: 'Interface::'vcall'{0}'' : function compiled as native

所以編譯似乎沒有任何效果。是有辦法解決這一問題?或者這是一個錯誤(雜注對非模板類有效)?此警告是否可以安全禁用?

#pragma unmanaged 

struct Interface 
{ 
    virtual void Foo() = 0; 
}; 

template< class T > 
struct UsesFunPtr 
{ 
    UsesFunPtr() 
    { 
    &T::Foo; 
    } 
}; 

void DoIt() 
{ 
    UsesFunPtr<Interface> a; 
} 

#pragma managed 

更新:如果我刪除最後一行警告消失了 - 所以以下ComicSansMs的回答:當到底是at the time of definition for the template?任何人都可以解釋爲什麼最後一行,之後沒有任何代碼,仍然影響之前的代碼?

回答

7

大改版,因爲(在評論)海報解釋了爲什麼& T:富;不是問題。

此警告的起源有點愣神複雜。

經過調查,我們發現,從微軟,ComicSansMS還貼出了以下內容:

當模板函數被實例化,在定義模板時編譯指示狀態確定其是否託管或非託管。

這是爲功能模板,而不是像您使用的類模板。

事實上,這個函數模板實例有關的一個謹慎的方式。但是這種警告行爲的獨特方式與編譯過程有關。

編譯器實際上是生成託管代碼,用於調用和使用UsesFunPtr ctor,當您使用#pragma managed結束文件時。它給出了一個警告,其中有一些非託管代碼。這是對原因的深入分析。

Thunks基本上是圍繞某些vtable調用的包裝函數;維基百科有關該主題的體面文章。你生成一個thunk的原因是因爲你正在使用一個函數的地址(& T :: Foo)。

如果你的文件的最後一行是這樣的:

#pragma unmanaged 

就會停止抱怨,即使你已經在該對象混合託管和非託管代碼。這是因爲前端和後端之間的「混淆」事物之間的斷開:它將編譯我上面用最後一個#pragma命令所述的代碼。

如果您會注意到,此警告不是編譯時警告。如果您修訂DOIT()函數像這樣它來編譯,當它說後「生成代碼...」:

void DoIt() 
{ 
    long a; 
    long long b; 
    b = 4; 
    a = b; 
    UsesFunPtr<Interface> d; 
} 

在「編譯x.cpp」你會得到一個關於截斷警告,則它會轉移到代碼生成階段(生成代碼...),在那裏它會給出這個問題的警告。

編譯器是解析等的前端,並創建一種中間二進制格式,類似於Java字節碼。代碼生成器是後端,通過此字節碼在此目標平臺上創建實際輸出(本例中爲Windows x86)。

直到後端獲得代碼後才進行優化。 thunk是優化的一種形式,因此它是發出警告的代碼生成器(取得半編譯的IL代碼),而不是編譯器。這不是一種可以以任何我知道的方式關閉的優化,因爲這是一種標準做法。

然而,編譯器實例化該模板;後端只看到一個完整的課程,並被告知要理解它。

編譯託管C++/CLI時有所不同。有時編譯器能夠使用所謂的鏈接時代碼生成。發生這種情況時,鏈接器是調用後端的鏈接器;當它不可能時(出於各種原因),它經歷了編譯的通用過程(前端 - >後端 - >鏈接器)。

#pragma按照收到的順序傳入IL。這似乎表明UsesFunPtr的函數支持代碼在被管理的#pragma發生後實際上是「追加到最後」。所以,即使你的代碼是在非託管空間,代碼生成器認爲:

  1. 非託管
  2. 對象定義,用
  3. 管理
  4. 額外的支持代碼的類,你沒有寫,不能看到與其他目標文件接口有關:創建和複製vtable等等等等。

發電機組沒有辦法來區分,如果你意味着UsesFunPtr構造函數,甚至類,是完全託管或非託管代碼,因爲它得到的IL並沒有真正連接兩個。它看到一個函數創建它是非託管的(通過編譯指示),並支持在其上運行的函數並在託管空間中生成thunk(通過編譯指示)。它無法分辨連接。既然你把那個#pragma放在那裏,它只是繼續看到它看到的最後一個#pragma。生成的支持代碼被管理。

你會發現,如果你在最後放的#pragma非託管,即使有管理配的模板代碼,你將不會得到警告。這是因爲它將該代碼編譯爲非託管,因此thunk不是問題。

如何總結這一切嗎?最終被管理的#pragma會導致前端和後端之間的溝通不暢(或者這是一個錯誤的假設?),而後端抱怨它。

凌晨,這是一個有趣的!

+0

我同意prgama的東西可能是一個bug,但我很確定&T :: Foo沒有錯。好吧,它什麼都不做,但是語法是有效的,這裏的目的是爲了解決問題。實際的代碼是std :: bind(&T :: Foo,instanceOfT),它又是完全有效的,並且產生了一個可調用的函數對象。沒有編譯器錯誤。 – stijn 2012-03-22 09:28:55

+0

@stijn哈,你在你的真實代碼中完成了我的建議,使用了std :: bind。這將有助於瞭解這一點。在這種情況下,爲了解決這個問題,我會向微軟提交一份錯誤報告。他們修復它的速度非常快。例如,相關修復:http://connect.microsoft.com/VisualStudio/feedback/details/122489/warning-c4793-and-pragma-unmanaged。我會修改我的答案。 – 2012-03-22 17:13:33

+0

+1這個意外複雜問題的優秀答案。 – ComicSansMS 2012-03-22 18:45:23

2

MSDN從引用:

當模板函數被實例化,在定義爲模板的時間編譯指示狀態確定是否它被託管或非託管。

因此#pragma unmanaged需要爲DoIt的定義激活。如果情況已經如此,那可能是一個錯誤。

從理論上講是安全的禁用此警告,因爲它簡單地告訴您該函數儘管/clr開關編譯爲本地代碼。如果丟失該信息是可以接受的,請隨時將其禁用。

+1

發佈的代碼片段正是我喂編譯器,編譯指示應儘可能活躍,因爲它可以:] – stijn 2012-03-13 15:33:44

+1

模板功能是從一個模板類不同。 – 2012-03-22 17:50:30

0

某些#pragmas,例如#pragma warning(...)對於一些警告,具有全局效果。這意味着編譯器只有在整個包含樹和源文件被解析後才考慮它。因此,託管/非託管編譯指示可能會以一種蹩腳的方式實施,例如將實際開關與警告分開,或者編譯指示本身就是全局的。

我建議,因爲最後一個雜注是'託管'的,那麼你的所有代碼都被編譯爲託管的,除非MSVC絕對必須編譯非託管的,在這種情況下,它看起來像引用的pure_call函數(Foo ()= 0)終止你的程序。

我通常不理MSVC只要我的程序正常運行。