Search code examples
c#.netcom-interopfoxprovisual-foxpro

Confused about data types in COM, VFP and C#


I'm about to migrate from VFP to C#, but I'm a bit confused about the data types. I know that I can use the CLSCompliant-attribute to make sure that my data types are valid COM-types. But let's take the following few methods:

[assembly: CLSCompliant(true)]
namespace SampleNSpace {
    [ComVisible(true)]
    [Guid("111B0014-EB08-4093-A818-1D11EB4C489D")]
    public class AnyClass {
        public int GetAnyInt() { return int.maxValue; }
        public long GetAnyLong() { return long.maxValue; }
        public decimal GetAnyDecimal() { return decimal.maxValue; }
        public double GetAnyDouble() { return double.maxValue; }
    }
}

Alright, calling GetAnyInt() works as expected and the return value is exposed as long (as described in http://msdn.microsoft.com/en-us/library/sak564ww.aspx). But calling GetAnyLong() and GetAnyDouble() doesn't work and I currently don't know why. I'm always getting "Function argument value, type, or count is invalid.". I first thought, that the reason is that double and long are 8 byte/64 bits long (because max exact number in VFP is 2^53), but calling GetAnyDecimal() works without any error and decimal is 8 byte longer (128 bit overall). Anyone know what's the reason why DECIMAL works and double/long doesn't?Thanks for any thoughts!


Solution

  • First of all, CLSCompliant attribute doesn't have anything to do with COM. It's for Common Language Runtime compliance.

    OLE Automation specification lists the types which are automation-compatible.

    Your C# class, if compiled as 32-bit assembly and registered with RegAsm, exposes the following COM interface:

    [
      odl,
      uuid(AFA13243-F593-3B28-A4D3-4E4138AA1F22),
      hidden,
      dual,
      nonextensible,
      oleautomation,
      custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "SampleNSpace.AnyClass")
    
    ]
    interface _AnyClass : IDispatch {
        [id(00000000), propget,
          custom(54FC8F55-38DE-4703-9C4E-250351302B1C, 1)]
        HRESULT ToString([out, retval] BSTR* pRetVal);
        [id(0x60020001)]
        HRESULT Equals(
                        [in] VARIANT obj, 
                        [out, retval] VARIANT_BOOL* pRetVal);
        [id(0x60020002)]
        HRESULT GetHashCode([out, retval] long* pRetVal);
        [id(0x60020003)]
        HRESULT GetType([out, retval] _Type** pRetVal);
        [id(0x60020004)]
        HRESULT GetAnyInt([out, retval] long* pRetVal);
        [id(0x60020005)]
        HRESULT GetAnyLong([out, retval] int64* pRetVal);
        [id(0x60020006)]
        HRESULT GetAnyDecimal([out, retval] wchar_t* pRetVal);
        [id(0x60020007)]
        HRESULT GetAnyDouble([out, retval] double* pRetVal);
    };
    

    I'm not sure if int64 is considered automation-compatible (it's not included in the list I mentioned above), but double is certainly automation-compatible. Thus, I suspect it might a be a problem on the VFP side. To work it around, you could try changing the definition for your C# class to use object for those types. Note also how MarshalAs(UnmanagedType.Currency) is used to marshal decimal as OLE CURRENCY type.

    [assembly: CLSCompliant(true)]
    namespace SampleNSpace
    {
        [ComVisible(true), ClassInterface(ClassInterfaceType.AutoDual)]
        [Guid("111B0014-EB08-4093-A818-1D11EB4C489D")]
        public class AnyClass
        {
            public int GetAnyInt() { return int.MaxValue; }
    
            [return: MarshalAs(UnmanagedType.Struct)]
            public object GetAnyLong() { return long.MaxValue; }
    
            [return: MarshalAs(UnmanagedType.Currency)]
            public decimal GetAnyDecimal() { return decimal.MaxValue; }
    
            [return: MarshalAs(UnmanagedType.Struct)]
            public object GetAnyDouble() { return double.MaxValue; }
        }
    }
    

    This produces the following COM interface using VARIANT which I'd expect to work with VFP for granted:

    [
      odl,
      uuid(671A483A-5327-391A-AF09-4D734F9DFDCF),
      hidden,
      dual,
      nonextensible,
      oleautomation,
      custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "SampleNSpace.AnyClass")
    
    ]
    interface _AnyClass : IDispatch {
        [id(00000000), propget,
          custom(54FC8F55-38DE-4703-9C4E-250351302B1C, 1)]
        HRESULT ToString([out, retval] BSTR* pRetVal);
        [id(0x60020001)]
        HRESULT Equals(
                        [in] VARIANT obj, 
                        [out, retval] VARIANT_BOOL* pRetVal);
        [id(0x60020002)]
        HRESULT GetHashCode([out, retval] long* pRetVal);
        [id(0x60020003)]
        HRESULT GetType([out, retval] _Type** pRetVal);
        [id(0x60020004)]
        HRESULT GetAnyInt([out, retval] long* pRetVal);
        [id(0x60020005)]
        HRESULT GetAnyLong([out, retval] VARIANT* pRetVal);
        [id(0x60020006)]
        HRESULT GetAnyDecimal([out, retval] CURRENCY* pRetVal);
        [id(0x60020007)]
        HRESULT GetAnyDouble([out, retval] VARIANT* pRetVal);
    };