Question

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!

Was it helpful?

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);
};

OTHER TIPS

I have same problem. A decimal in an interface is expoted as wchar_t, but the correct behaviour had been to export it to DECIMAL that is a valid COM type.

The DECIMAL data type has more valid values than CURRENCY. So converting to CURRENCY will only help if the data is currency. For other types of data, the CURRENCY is too shallow.

Microsoft documents that decimal is not supposed to be marked specifically in an interface for export. Here is the description, and DECIMAL type is described as a special type.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top