共用方式為


HOW TO:移轉至 /clr

更新:2007 年 11 月

本說明主題將討論以 /clr 編譯機器碼時所引發的問題 (如需詳細資訊,請參閱 /clr (Common Language Runtime 編譯))。/clr 可讓 Visual C++ 模組從 .NET 組件中進行叫用與被叫用,同時還能保持與 Unmanaged 模組的相容性。如需以 /clr 進行編譯之優點的詳細資訊,請參閱 混合 (原生和 Managed) 組件原生和 .NET 互通性

以 /clr 編譯程式庫專案的已知問題

Visual C++ 2005 具有一些搭配 /clr 編譯程式庫專案時的已知問題:

  • 在開發環境中以 /clr 編譯 MFC ActiveX 控制項專案時,建置系統會試圖以 regasm 註冊 .dll,而不是使用 regsvr32。您必須手動使用 regsvr32 來註冊控制項

  • 當您建立 ATL 專案然後啟用 /clr 時,.c 檔案會產生錯誤,因為 .c 檔案無法以 /clr 進行編譯。不過,如果您變更檔案設定,以 /TP 來編譯檔案,就會產生連結器錯誤。解決方案是以原生方式來編譯 .c 檔案 (不使用 /clr)

  • 您的程式碼可以在執行階段使用 CRuntimeClass::FromName 來查詢型別。但是,如果型別在 MSIL .dll 中 (以 /clr 編譯),則對 CRuntimeClass::FromName 的呼叫如果是發生在於 Managed .dll 中執行靜態建構函式之前,則可能會失敗 (如果 FromName 呼叫發生在於 Managed .dll 中執行程式碼之後,則不會有這個問題)。若要避開這個問題,您可以在 Managed .dll 中定義函式、將它匯出,以及從原生 MFC 應用程式叫用它,以強制建構 Managed 靜態建構函式。例如:

    // Extention DLL Header file:
    __declspec( dllexport ) void EnsureManagedInitialization () {
       // managed code that won't be optimized away
       System::GC::KeepAlive(System::Int32::MaxValue);
    }
    

以 Visual C++ 2005 進行編譯

對專案的任何模組使用 /clr 之前,請先使用 Visual C++ 2005 來編譯及連結原生專案。

下列步驟是依照順序排列,提供了最簡單的 /clr 編譯方法,執行了下列每一個步驟之後,請務必編譯及執行專案。

Visual C++ 2003 之前的版本

如果您是從 Visual C++ 2003 之前的版本升級至 Visual C++ 2005,則可能會看到與 Visual C++ 2003 中增強的 C++ 標準一致性有關的編譯器錯誤。如需詳細資訊,請參閱 Visual C++ 編譯器的重大變更

從 Visual C++ 2003 升級

先前以 Visual C++ 2003 建置的專案也應該要先在不搭配 /clr 的情況下進行編譯,因為 Visual C++ 2005 已經提升了 ANSI/ISO 相容性,並且包含一些重大變更。如需詳細資訊,請參閱Visual C++ 編譯器的重大變更。最值得注意的變更可能就是 Security Enhancements in the CRT。使用 CRT 的程式碼很可能會產生反對警告訊息,您可以隱藏這些警告訊息,但最好是移轉至新的 Security-Enhanced Versions of CRT Functions,因為它們可以提供更好的安全性,並且可以揭露程式碼中的安全性問題。

從 Managed Extensions for C++ 升級

以 Visual C++ .NET 或 Visual C++ 2003 建置並使用 Managed Extensions for C++ 的專案至少需要變更一項專案設定,因為這些擴充功能現在已經被取代,結果是,以 Managed Extentions for C++ 撰寫的程式碼無法在 /clr 下進行編譯。請改用 /clr:oldSyntax

將 C 程式碼轉換成 C++

雖然 Visual C++ 2005 會編譯 C 檔案,如果要搭配 /clr 進行編譯,您還是必須將 C 程式碼轉換成 C++。實際的檔案名稱並不需要變更,您可以使用 /Tp (請參閱 /Tc、/Tp、/TC、/TP (指定原始程式檔類型))。請注意,雖然 C++ 原始程式碼檔案是 /clr 的必要項目,您不需要重整程式碼也能使用物件導向開發架構。

若要將 C 程式碼編譯成 C++ 檔案,您很可能必須變更 C 程式碼。C++ 型別安全規則很嚴格,因此必須以轉換 (Cast) 明確地進行型別轉換,例如,malloc 會傳回 Void 指標,但可以使用轉換 (Cast) 指派給指標至 C 的任何型別:

int* a = malloc(sizeof(int));   // C code
int* b = (int*)malloc(sizeof(int));   // C++ equivalent

在 C++ 中,函式指標也具有嚴格的型別安全,所以下列 C 程式碼必須加以修改。在 C++ 中,最好是建立會定義函式指標型別的 typedef,然後使用該型別來轉換函式指標:

NewFunc1 = GetProcAddress( hLib, "Func1" );   // C code
typedef int(*MYPROC)(int);   // C++ equivalent
NewFunc2 = (MYPROC)GetProcAddress( hLib, "Func2" );

此外,C++ 還要求函式必須是原型或是經過完整定義,才能參考或叫用函式。

如果 C 程式碼中所用的識別項剛好是 C++ 中的關鍵字 (例如 virtual、new、delete、bool、true、false 等等),就必須加以重新命名,您通常可以使用簡單的搜尋與取代作業來完成這項工作。

最後,C-Style COM 呼叫必須明確地使用 v-table 和 this 指標,但 C++ 並不會:

COMObj1->lpVtbl->Method(COMObj, args);  // C code
COMObj2->Method(args);  // C++ equivalent

重新進行專案設定

在 Visual C++ 2005 中編譯及執行專案之後,您應該建立新的 /clr 專案組態,而不是修改預設組態。/clr 和某些編譯器選項不相容,而且建立分開的組態可讓您將專案建置成原生或 Managed 專案。在 [屬性頁] 對話方塊中選取了 /clr 時,和 /clr 不相容的專案設定會被停用 (如果之後取消選取 /clr,停用的選項並不會自動回復成原狀)。

建立新的專案組態

您可以使用 新增專案組態對話方塊中的 [複製設定值來源] 選項依據現有的專案設定來建立專案組態,請針對偵錯組態進行一次這項作業,並針對發行組態進行一次,然後就可以將後續變更套用至特定的 /clr 組態,並保持原來的專案組態不變。

您可能需要額外留意那些使用自訂建置規則的專案。

這個步驟對於使用 Makefile 的專案而言具有不同的含意,在這種情況下,您可以設定分開的建置目標,或者是從原始版本建立 /clr 編譯專用的版本。

變更專案設定

您可以依照 /clr (Common Language Runtime 編譯) 中的指示,在開發環境中選取 /clr。如先前所述,這個步驟會自動停用衝突的專案設定。

注意事項:

將 Managed 程式庫或 Web 服務專案從 Visual C++ 2003 升級到 Visual C++ 2005 時,/Zl 編譯器選項會加入至 [命令列] 屬性頁。這項動作會造成 LNK2001。請從 [命令列] 屬性頁移除 /Zl,以解決問題。如需詳細資訊,請參閱 /Zl (省略預設程式庫名稱)HOW TO:開啟專案屬性頁。或者是,將 msvcrt.lib 和 msvcmrt.lib 加入至連結器的 [其他相依性] 屬性。

如果是使用 Makefile 建置的專案,一旦加入了 /clr,就必須手動停用不相容的編譯器選項。如需和 /clr 不相容的編譯器選項的詳細資訊,請參閱 /clr 限制

先行編譯的標頭

/clr 下,可支援先行編譯的標頭。不過,如果您只以 /clr 來編譯某些 CPP 檔案 (以原生方式編譯其餘的 CPP 檔案),就必須進行一些變更,因為以 /clr 產生的先行編譯標頭和那些不是以 /clr 產生的先行編譯標頭並不相容。這種不相容是由 /clr 會產生及需要中繼資料的事實所造成。因此,以 /clr 編譯的模組不能使用沒有包含中繼資料的先行編譯標頭,而非 /clr 模組則不能使用包含了中繼資料的先行編譯標頭檔。

如果某個專案中有一些模組是以 /clr 編譯而成,則編譯此專案最簡單的方式就是全部停用先行編譯的標頭 (在專案的 [屬性頁] 對話方塊中,開啟 [C/C++] 節點,然後選取 [先行編譯標頭],接著將 [建立/使用先行編譯標頭] 屬性變更為 [未使用先行編譯標頭檔])。

不過,尤其是大型專案,先行編譯的標頭可提供更快的編譯速度,因此停用這項功能並不是十分妥當。在這種情況下,最好是將 /clr 與非 /clr 檔案設定成使用分開的先行編譯標頭。只要一個步驟就能完成這項作業:請使用 [方案總管] 選取多個以 /clr 編譯的模組,以滑鼠右鍵按一下此群組,然後選取 [屬性],接著將 [透過檔案建立/使用 PCH] 與 [先行編譯標頭檔] 這兩個屬性變更為分別使用不同的標頭檔名稱與 PCH 檔案。

修正錯誤

/clr 進行編譯可能會造成編譯器、連結器或執行階段錯誤。這一節將討論最常見的問題。

中繼資料合併

不同的資料型別版本可能會造成連結器失敗,因為針對兩種型別所產生的中繼資料並不相符 (當某個型別的成員是以條件方式定義,但使用此型別的所有 CPP 檔案的條件並不相同時,通常會造成這種問題)。在這種情況下,連結器會失敗,只報告符號名稱以及定義了型別的第二個 OBJ 檔案的名稱。調換 OBJ 檔案傳送至連結器的順序,以發現資料型別之其他版本的位置,通常會很有用。

載入器鎖定死結

在 Visual C++ .NET 和 Visual C++ 2003 中,在 /clr 下進行初始設定很容易受到非決定性死結的影響,這個問題就是已知的「載入器鎖定死結」。在 Visual C++ 2005 中,比較容易避免發生這個死結,在執行階段就會對它進行偵測並加以報告,並且也不再是不具決定性。您還是可能會遇到載入器鎖定的問題,但現在比較容易避免及修正這個問題。如需詳細背景資訊、指導方針與解決方案,請參閱混合組件的初始化

資料匯出

匯出 DLL 資料很容易發生錯誤,我們不建議這麼做,這是因為直到已經執行了 DLL 的某些 Managed 部分,才能保證會初始化 DLL 的資料區段。請參考 The #using Directive的中繼資料。

型別可視性

原生型別現在預設是私用型別。在 Visual C++ .NET 2002 和 Visual C++ 2003 中,原生型別預設是公用型別,這可能會造成在 DLL 外無法看到原生型別。請將 public 加入至這些型別,以解決這個問題。如需詳細資訊,請參閱Type and Member Visibility

浮點和對齊問題

Common Language Runtime 上不支援 __controlfp (如需詳細資訊,請參閱 _control87, _controlfp, __control87_2)。此外,CLR 也不理會 align (C++)

COM 初始設定

當初始化模組時,Common Language Runtime 會自動初始化 COM (當自動初始化 COM 時,就像是 MTA 一樣)。結果,明確地初始化 COM 會產生傳回碼,指出 COM 早已經初始化了。當 CLR 已經將 COM 初始化為某個執行緒處理模型時,嘗試以另一個執行緒處理模型明確初始化 COM 可能會讓應用程式失敗。

COM 初始設定以及相關的錯誤碼應該可以接受 COM 已經初始化的情況,或者是讓您直接移除 CoInitializeCoUninitialize 的呼叫。根據預設,Common Language Runtime 會以 MTA 的方式啟動 COM;請使用 /CLRTHREADATTRIBUTE (設定 CLR 執行緒屬性) 來修改這個問題。

效能問題

當間接呼叫產生至 MSIL 的原生 C++ 方法時 (虛擬函式呼叫或使用函式指標),您可能會發現效能降低了。若要進一步了解這個問題,請參閱 Double Thunking (C++)

從原生移至 MSIL 時,您會發現工作集的大小增加了,這是因為 Common Language Runtime 會提供許多功能,以確保程式能夠正確執行。如果 /clr 應用程式沒有正確執行,您可能需要啟用 C4793 (預設是停用狀態)。如需詳細資訊,請參閱編譯器警告 (層級 1 和 3) C4793

關機時程式當掉

在某些情況下,CLR 可能會在 Managed 程式碼執行完成之前就先關閉。使用 std::set_terminate 和 SIGTERM 可能會造成這個問題。如需詳細資訊,請參閱 signal Constantsset_terminate (<exception>)

使用新的 Visual C++ 功能

應用程式在編譯、連結與執行之後,您就可以開始在任何以 /clr 編譯的模組中使用 .NET 功能。如需詳細資訊,請參閱Language Features for Targeting the CLR

如果使用了 Managed Extensions for C++,您可以將程式碼轉換成使用新的語法。如需語法差異的摘要列表,請參閱 Managed Extensions for C++ 語法升級檢查清單。如需轉換 Managed Extensions for C++ 的詳細資訊,請參閱 C++/CLI 移轉入門

如需以 Visual C++ 進行 .NET 程式設計的詳細資訊,請參閱:

請參閱

概念

混合 (原生和 Managed) 組件