手動生成C#的COM包裝類的常見問題和解決辦法

ATField發表於2007-05-14

看一下如下程式碼:

    [Guid("25088995-7924-4B15-B01A-EA7C422ADC68")]

    public class CHelloClass : IHello

    {

        [DispId(1)]

        [MethodImplAttribute(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime)]

        public extern void HelloWorld();

    }

 

    class Program

    {

        static void Main(string[] args)

        {

            CHelloClass obj = new CHelloClass();

            obj.HelloWorld();

        }

    }       

 

這裡的CHelloClass是一個COM物件,指定了GUID,實現了IHello介面的HelloWorld函式。然而,當執行這條語句CHelloClass obj = new CHelloClass,會產生如下異常:

Unhandled Exception: System.Security.SecurityException: ECall methods must be packaged into a system module.

 

這裡異常資訊需要解釋一下:ECall是一種內部呼叫的方式(還存在其它方式如FCall等),由CLR本身實現,而不由使用者提供實現。當CHelloClass中缺少ComImportAttrib這個屬性的時候,CLR會認為HelloWorld這個函式是在CLR本身實現的,然後又在CLR內部的呼叫表(這個表維護所有CLR內部呼叫的函式)無法查到對應的實現,所以才丟擲異常。當ComImportAttribute存在的時候,CLR才知道這個class是從COM物件Import過來的,從而作一些特殊處理,並不會對HelloWorld按照ECall方式來處理。

我們再看一下,是否MethodImplAttribute這裡真正需要呢?可以試一下加上ComImport然後去掉MethodImplAttri看看:

    [ComImport]

    [Guid("25088995-7924-4B15-B01A-EA7C422ADC68")]

    public class CHelloClass : IHello

    {

        [DispId(1)]

        public extern void HelloWorld();

    }

 

    class Program

    {

        static void Main(string[] args)

        {

            CHelloClass obj = new CHelloClass();

            obj.HelloWorld();

        }

    }       

當到了new CHelloClass這條語句的時候,會產生下面異常:

Unhandled Exception: System.TypeLoadException: Could not load type 'CHelloClass' from assembly 'Program, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' because the method 'HelloWorld' has no implementation (no RVA).   at Program.Main(String[] args)

這一次,CLR則報告HelloWorld函式沒有對應的實現程式碼。沒有RVA的意思是CLR無法找到HelloWorld函式程式碼的位置。這個位置是一個記憶體的相對位置,因此稱之為RVARelative Virtual Address)。由於實際的實現是由CLR提供,準確說是RCW提供,因此這裡是需要MethodImplAttrbute的。

最終正確的版本如下:

    [ComImport]

    [Guid("25088995-7924-4B15-B01A-EA7C422ADC68")]

    public class CHelloClass : IHello

    {

        [DispId(1)]

        [MethodImplAttribute(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime)]

        public extern void HelloWorld();

    }

 

    class Program

    {

        static void Main(string[] args)

        {

            CHelloClass obj = new CHelloClass();

            obj.HelloWorld();

        }

    }       

 

當然了,如果你不自己編寫COM Wrapper程式碼的話則不會遇到類似的問題。所以請儘可能的讓Tlbimp替你生成Interop程式碼,而不是自己手動編寫,除非Tlbimpl生成的程式碼不符合你的要求。

 

相關文章