I'm trying to implement the IOpenControlPanel interface, which is not documented in sites like pinvoke.net, so for this task I built the definitions from scratch as I think they should be, then I tried to manually retrieve the interface CLSID from registry, which seems to be D11AD862-66DE-4DF4-BF6C-1F5621996AF1, and a Class that implements that inferface, which seems to be 06622D85-6856-4460-8DE1-A81921B41C4B.
The problem is that in the following code If I call GetCurrentView function I don't get the expected value, and a call to Open function does nothing (I'm using a proper canonical name like Microsoft.DefaultPrograms as explained in this MSDN article from this list of canonical names.)
Dim cp As New COpenControlPanel
Dim view As ControlPanelView
DirectCast(cp, IOpenControlPanel).GetCurrentView(view)
DirectCast(cp, IOpenControlPanel).Open("Microsoft.DefaultPrograms", "", Nothing)
So, I think that my definitions are wrong in some way, I need help to fix it.
These are the the definitions:
VB.Net:
Friend NotInheritable Class NativeMethods
Enum ControlPanelView As Integer
Classic = 0
Category = 1
End Enum
<ComImport()>
<Guid("06622D85-6856-4460-8DE1-A81921B41C4B")>
Class COpenControlPanel
End Class
<ComImport>
<InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
<Guid("D11AD862-66DE-4DF4-BF6C-1F5621996AF1")>
Public Interface IOpenControlPanel
<PreserveSig()>
Function Open(<MarshalAs(UnmanagedType.BStr)> ByVal name As String,
<MarshalAs(UnmanagedType.BStr)> ByVal page As String,
ByVal punkSite As IntPtr
) As Integer ' HResult
<PreserveSig()>
Function GetPath(<MarshalAs(UnmanagedType.BStr)> ByVal name As String,
<MarshalAs(UnmanagedType.LPWStr)> ByVal path As StringBuilder,
ByVal bufferSize As Integer
) As Integer ' HResult
<PreserveSig()>
Function GetCurrentView(ByRef refView As ControlPanelView
) As Integer ' HResult
End Interface
End Class
C# (online translation):
internal sealed class NativeMethods {
public enum ControlPanelView : int {
Classic = 0,
Category = 1
}
[ComImport()]
[Guid("06622D85-6856-4460-8DE1-A81921B41C4B")]
class COpenControlPanel {}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("D11AD862-66DE-4DF4-BF6C-1F5621996AF1")]
public interface IOpenControlPanel
{
[PreserveSig()]
int Open([MarshalAs(UnmanagedType.BStr)] string name,
[MarshalAs(UnmanagedType.BStr)] string page,
IntPtr punkSite);
[PreserveSig()]
int GetPath([MarshalAs(UnmanagedType.BStr)] string name,
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder path,
int bufferSize);
[PreserveSig()]
int GetCurrentView(ref ControlPanelView refView);
}
}
Your interface definition is wrong because you did not defined methods in the same order as MSDN does (in fact, names are not important, what's important is the interface methods layout: matching binary signatures in the correct order). The order must be exactly what's defined in .h files available with the Windows SDK, not what MSDN displays - this is actually misleading :-). In this case, the header file is Shobjidl.h. This is how it's defined in C/C++:
MIDL_INTERFACE("D11AD862-66DE-4DF4-BF6C-1F5621996AF1")
IOpenControlPanel : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE Open(
/* [string][unique][in] */ __RPC__in_opt_string LPCWSTR pszName,
/* [string][unique][in] */ __RPC__in_opt_string LPCWSTR pszPage,
/* [unique][in] */ __RPC__in_opt IUnknown *punkSite) = 0;
virtual HRESULT STDMETHODCALLTYPE GetPath(
/* [string][unique][in] */ __RPC__in_opt_string LPCWSTR pszName,
/* [size_is][string][out] */ __RPC__out_ecount_full_string(cchPath) LPWSTR pszPath,
/* [in] */ UINT cchPath) = 0;
virtual HRESULT STDMETHODCALLTYPE GetCurrentView(
/* [out] */ __RPC__out CPVIEW *pView) = 0;
};
There are multiple equivalent definition in .NET, C#, but here is one that should work:
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("D11AD862-66DE-4DF4-BF6C-1F5621996AF1")]
public interface IOpenControlPanel
{
[PreserveSig]
int Open([MarshalAs(UnmanagedType.LPWStr)] string name,
[MarshalAs(UnmanagedType.LPWStr)] string page,
IntPtr punkSite);
[PreserveSig]
int GetPath([MarshalAs(UnmanagedType.LPWStr)] string name,
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder refPath,
int bufferSize);
// if you remove PreserveSig, you can return the [out] param directly
// note in this case, the function could throw instead of returning an error int like with PreserveSig
ControlPanelView GetCurrentView();
}
Related
I am working on migrating legacy VB6 COM library that registered with regsvr32 and used primarily in vbscript and ASP Classic.
The new .NET 4.8 substitution library is registered with regasm.
All was good until I came across some features that I can not directly migrate because I have not enough knowledge in this field.
Legacy VB6 library has the following Typelibrary code (from OleView, reduced for convenience):
[id(0x00068), propget]
VARIANT_BOOL IncomeOverride([in] short index);
[id(0x00068), propput]
void IncomeOverride(
[in] short index,
[in] VARIANT_BOOL rhs
);
[id(0x00069), propget]
CY IncomeOverrideAmt([in] short index);
[id(0x00069), propput]
void IncomeOverrideAmt(
[in] short index,
[in] CY rhs
);
The caller is using these methods like this:
oTest.IncomeOverride(1) = True
oTest.IncomeOverrideAmt(1) = 12
Firstly I have tried to use properties.
But since there is no parameterized properties available in C#, I had to try out the methods like this:
[DispId(0x00068)]
bool IncomeOverride([In] short index);
[DispId(0x00068)]
void IncomeOverride([In] short index, [In] bool v);
[DispId(0x00069)]
decimal IncomeOverrideAmt([In] short index);
[DispId(0x00069)]
void IncomeOverrideAmt([In] short index, [In] decimal v);
That compiles but does not work because regasm makes a warning on registering and refuses generating correct tlb. The warning as follows:
Type library exporter warning processing 'TestLibrary'. Warning: The
type had one or more duplicate DISPIDs specified. The duplicate
DISPIDs were ignored.
The produced typelibrary is far from what is needed:
[id(0x0001)]
VARIANT_BOOL IncomeOverride([in] short index);
[id(0x0002)]
void IncomeOverride_2(
[in] short index,
[in] VARIANT_BOOL v
);
[id(0x0003)]
CY IncomeOverrideAmt([in] short index);
[id(0x0004)]
void IncomeOverrideAmt_2(
[in] short index,
[in] CY v
);
Next iteration was to use indexers.
[DispId(0x00068)]
[System.Runtime.CompilerServices.IndexerName("IncomeOverride")]
bool this[short index]
{
[return: MarshalAs(UnmanagedType.Currency)]
get;
[param: In, MarshalAs(UnmanagedType.Currency)]
set;
}
[DispId(0x00069)]
[System.Runtime.CompilerServices.IndexerName("IncomeOverrideAmt")]
decimal this[short index]
{
[return: MarshalAs(UnmanagedType.Currency)]
get;
[param: In, MarshalAs(UnmanagedType.Currency)]
set;
}
That is actually a good solution but unfortunately it does not even compile because you can not have multiple indexers with different IndexerName values in a single interface/class.
The last attempt to try to accomodate such feature was to use properties that return arrays/objects with indexers:
[DispId(0x00068)]
bool[] IncomeOverride { get; }
[DispId(0x00069)]
decimal[] IncomeOverrideAmt { get; }
[DispId(0x00068)]
SomeClassWithBoolIndexer IncomeOverride { get; }
[DispId(0x00069)]
SomeClassWithDecimalIndexer IncomeOverrideAmt { get; }
That compiles and registers but since it is generating incorrect typelibrary - it can not be used in vbscript/VB6/ASP Classic.
So the question is - how to implement such feature?
Or just emulate?
Writing custom Typelibrary for C# library?
Is this even possible with the means of C#?
Thank in advance.
Update:
Found a similar question here: How to make C# COM class support parameterized properties from VB6
It actually makes most of the work done but unlike the author of that question - it does not work without using .Value for the parameterized property.
Trying to dig in and find out why VBscript does not recognize default property (Value). Unless this - all works like a champ!
But without default property recognition it is dead because requires rewriting tonns of legacy code... we currently do not need binary compat, just to make the late binding (vbscript) work.
Anyone has any idea why it is not working as supposed?
Update 2
Ok as for now two main solutions (apparently none of them working):
1. using PropertyAccessor class
VBScript:
Dim oTestObject: Set oTestObject = CreateObject("TestSharpLib.TestObject")
wsh.echo "oTestObject.IncomeOverride(1): [" & oTestObject.IncomeOverride(1) & "]" 'THIS LINE WORKS
oTestObject.IncomeOverride(1).Value = "True" 'THIS LINE WORKS
oTestObject.IncomeOverride(1) = "True" 'THIS LINE DOES NOT WORK
wsh.echo "oTestObject.IncomeOverride(1): [" & oTestObject.IncomeOverride(1) & "]"
C#
using System;
using System.Runtime.InteropServices;
using TestVBNET;
namespace TestSharpLib
{
[ComVisible(true)]
[ProgId("TestSharpLib.TestObject")]
[Guid("49067FAC-A1B3-4469-9032-4E958AA7381C")]
public class TestObject: : ITestObject
{
public object IncomeOverride(short index)
{
return new PropertyAccessor(this, index);
}
public class PropertyAccessor
{
TestObject _owner;
short _index;
public PropertyAccessor(TestObject owner, short index)
{
_owner = owner;
_index = index;
}
[DispId(0)]
public string Value {
get => _index.ToString();
set { }
}
}
}
[ComVisible(true)]
[Guid("4C0FDA39-1027-415A-968C-9A6DF9BEB499")]
public interface ITestObject
{
[DispId(1)]
object IncomeOverride(short index);
}
}
Seems like according to Erik Lippert you can not omit default properties in VBScript.
https://learn.microsoft.com/en-us/archive/blogs/ericlippert/vbscript-default-property-semantics
2. Using interface declared in VB.NET
VBScript:
Dim oTestObject: Set oTestObject = CreateObject("TestSharpLib.TestObject")
wsh.echo "oTestObject.IncomeOverride(1): [" & oTestObject.IncomeOverride(1) & "]" 'THIS LINE NOT WORKING
oTestObject.IncomeOverride(1) = "True" 'THIS LINE NOT WORKING
wsh.echo "oTestObject.IncomeOverride(1): [" & oTestObject.IncomeOverride(1) & "]"
C#
using System;
using System.Runtime.InteropServices;
using TestVBNET;
namespace TestSharpLib
{
[ComVisible(true)]
[ProgId("TestSharpLib.TestObject")]
[Guid("49067FAC-A1B3-4469-9032-4E958AA7381C")]
public class TestObject: IIncomeOverride
{
public string get_IncomeOverride(int index)
{
return index.ToString();
}
public void set_IncomeOverride(int index, string Value)
{
//throw new NotImplementedException();
}
}
}
VB.NET
Imports System.Runtime.InteropServices
<ComVisible(True)>
<Guid("F40144F0-6BA1-4983-B6EE-D593CA0A2F75")>
Public Interface IIncomeOverride
<DispId(1745027127)>
Property IncomeOverride(ByVal index As Integer) As String
End Interface
This solution does not work at all. Although regasm says the dll registered and OleView actually shows correct typelibrary - VBScript does not see IncomeOverride property at all.
c:\Users\Administrator\Desktop\test.vbs(2, 1) Microsoft VBScript runtime error: Object doesn't support this property or method: 'IncomeOverride'
Typelibrary:
[
uuid(f40144f0-6ba1-4983-b6ee-d593ca0a2f75),
dual,
oleautomation
]
interface IIncomeOverride : IDispatch#i {
[id(0x68030037), propget]
HRESULT IncomeOverride(
[in] long index,
[out, retval] BSTR* pRetVal
);
[id(0x68030037), propput]
HRESULT IncomeOverride(
[in] long index,
[in] BSTR pRetVal
);
};
[
uuid(f40144f0-6ba1-4983-b6ee-d593ca0a2f75),
dual
]
dispinterface IIncomeOverride {
methods:
**removed object methods***
[id(0x68030037), propget]
BSTR IncomeOverride([in] long index);
[id(0x68030037), propput]
void IncomeOverride(
[in] long index,
[in] BSTR rhs
);
};
So for now I am still digging in but out of new ideas.
Main disappointment was that VBScript does not see implemented (declared in VB.NET interface) properties/methods.
I am working with SQL VDI and attempting to pass a structure from C# to C++ via a COM interface. The structure is defined in the C++ header as:
#pragma pack(8)
struct VDConfig
{
unsigned long deviceCount;
unsigned long features;
unsigned long prefixZoneSize;
unsigned long alignment;
unsigned long softFileMarkBlockSize;
unsigned long EOMWarningSize;
unsigned long serverTimeOut;
unsigned long blockSize;
unsigned long maxIODepth;
unsigned long maxTransferSize;
unsigned long bufferAreaSize;
} ;
To emulate this, I have defined the structure in C# as:
[StructLayout(LayoutKind.Explicit)]
public struct VDConfig
{
[FieldOffset(0)]
public uint deviceCount;
[FieldOffset(4)]
public uint features;
[FieldOffset(8)]
public uint prefixZoneSize;
[FieldOffset(12)]
public uint alignment;
[FieldOffset(16)]
public uint softFileMarkBlockSize;
[FieldOffset(20)]
public uint EOMWarningSize;
[FieldOffset(24)]
public uint serverTimeout;
[FieldOffset(28)]
public uint blockSize;
[FieldOffset(32)]
public uint maxIODepth;
[FieldOffset(36)]
public uint maxTransferSize;
[FieldOffset(40)]
public uint bufferAreaSize;
}
I have also tried to define the structure as LayoutKind.Sequential and tried it with Pack=8. However I define the structure, when I attempt to pass it to the function, it fails and I receive the error "Alignment must be 2**n and <= system allocation granularity." I've tried defining the function that accepts the structure as:
int CreateEx([MarshalAs(UnmanagedType.LPWStr)]string instanceName,
[MarshalAs(UnmanagedType.LPWStr)]string name,
IntPtr config);
and
int CreateEx([MarshalAs(UnmanagedType.LPWStr)]string instanceName,
[MarshalAs(UnmanagedType.LPWStr)]string name,
ref VDConfig config);
I get the same result with either definition. Can anyone tell me what I'm doing wrong here?
Edit:
In looking a little closer, I'm also getting the error "Device count must be in [1..64]." I'm setting the device count to 1 and in concert with the error above, it almost looks like the function isn't getting my structure at all. Don't know if this helps or not, but maybe it'll spark something for someone.
Per request, here are the interface structures. In C++:
MIDL_INTERFACE("d0e6eb07-7a62-11d2-8573-00c04fc21759")
IClientVirtualDeviceSet2 : public IClientVirtualDeviceSet
{
public:
virtual HRESULT STDMETHODCALLTYPE CreateEx(
/* [in] */ LPCWSTR lpInstanceName,
/* [in] */ LPCWSTR lpName,
/* [in] */ struct VDConfig *pCfg) = 0;
virtual HRESULT STDMETHODCALLTYPE OpenInSecondaryEx(
/* [in] */ LPCWSTR lpInstanceName,
/* [in] */ LPCWSTR lpSetName) = 0;
};
And my C# version:
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("d0e6eb07-7a62-11d2-8573-00c04fc21759")]
public interface IClientVirtualDeviceSet2
{
void CreateEx([In, MarshalAs(UnmanagedType.LPWStr)]string instanceName,
[In, MarshalAs(UnmanagedType.LPWStr)]string name,
[In]ref VDConfig config);
void OpenInSecondaryEx([MarshalAs(UnmanagedType.LPWStr)]string instanceName,
[MarshalAs(UnmanagedType.LPWStr)]string lpSetName);
}
For anyone else who comes across this, this is the answer:
As you can see in vdi.h, IClientVirtualDeviceSet2 "inherits" from IClientVirtualDeviceSet. As far as COM is concerned, there is no such thing as interface inheritance.
Therefore, when calling CreateEx on IClientVirtualDeviceSet2, you're actually calling Create on IClientVirtualDeviceSet (because Create is the first method in the vtable of that combined IClientVirtualDeviceSet + IClientVirtualDeviceSet2). That's why you end up getting invalid parameters.
The fix for this is to create a single interface (IClientVirtualDeviceSet2) with all the methods, IClientVirtualDeviceSet first, then the two IClientVirtualDeviceSet2 methods (obviously in order). This ensures when CreateEx() is called, it uses the correct DispId.
I'm sure you could probably use inheritance and set the DispIdAttribute accordingly:
https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.dispidattribute(v=vs.110).aspx
but there is probably little point.
What is the purpose of ComDefaultInterfaceAttribute attribute, if the managed object with ClassInterfaceType.None is marshaled as either IUnknown or IDispatch, anyway?
Consider the following C# class AuthenticateHelper, which implements COM IAuthenticate:
[ComImport]
[Guid("79eac9d0-baf9-11ce-8c82-00aa004ba90b")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IAuthenticate
{
[PreserveSig]
int Authenticate(
[In, Out] ref IntPtr phwnd,
[In, Out, MarshalAs(UnmanagedType.LPWStr)] ref string pszUsername,
[In, Out, MarshalAs(UnmanagedType.LPWStr)] ref string pszPassword);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IAuthenticate))]
public class AuthenticateHelper: IAuthenticate
{
public int Authenticate(ref IntPtr phwnd, ref string pszUsername, ref string pszPassword)
{
phwnd = IntPtr.Zero;
pszUsername = String.Empty;
pszPassword = String.Empty;
return 0;
}
}
I've just learnt that .NET interop runtime separates its implementation of IUnknown from IAuthenticate for such class:
AuthenticateHelper ah = new AuthenticateHelper();
IntPtr unk1 = Marshal.GetComInterfaceForObject(ah, typeof(IAuthenticate));
IntPtr unk2 = Marshal.GetIUnknownForObject(ah);
Debug.Assert(unk1 == unk2); // will assert!
I've learn that while implementing IServiceProvder, because the following did not work, it was crashing inside the client code upon returning from QueryService:
[ComImport]
[Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IServiceProvider
{
[PreserveSig]
int QueryService(
[In] ref Guid guidService,
[In] ref Guid riid,
[Out, MarshalAs(UnmanagedType.Interface, IidParameterIndex=1)] out object ppvObject
}
// ...
public readonly Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046");
AuthenticateHelper ah = new AuthenticateHelper();
int IServiceProvider.QueryService(ref Guid guidService, ref Guid riid, out object ppvObject)
{
if (guidService == typeof(IAuthenticate).GUID && (riid == IID_IUnknown || riid == guidService))
{
ppvObject = this.ah; // same as ppvObject = (IAuthenticate)this.ah
return S_OK;
}
ppvObject = null;
return E_NOINTERFACE;
}
I naively expected the instance of AuthenticateHelper would be marshaled as IAuthenticate because the class declares [ComDefaultInterface(typeof(IAuthenticate))], so IAuthenticate is the only and the default COM interface implemented by this class. However, that did not work, obviously because the object still gets marshaled as IUnknown.
The following works, but it changes the signature of QueryService and makes it less friendly for consuming (rather than providing) objects:
[ComImport]
[Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IServiceProvider
{
[PreserveSig]
int QueryService(
[In] ref Guid guidService,
[In] ref Guid riid,
[Out] out IntPtr ppvObject);
}
// ...
int IServiceProvider.QueryService(ref Guid guidService, ref Guid riid, out IntPtr ppvObject)
{
if (guidService == typeof(IAuthenticate).GUID && (riid == IID_IUnknown || riid == guidService))
{
ppvObject = Marshal.GetComInterfaceForObject(this.ah, typeof(IAuthenticate));
return S_OK;
}
ppvObject = IntPtr.Zero;
return E_NOINTERFACE;
}
So, why would I specify ComDefaultInterface at all, if it doesn't affect marshaling? The only other use I see is for type library generation.
It's unmanaged client COM code that calls my managed implementation of IServiceProvider::QueryService. Is there a way to make QueryService work in my example without resorting to low-level stuff like GetComInterfaceForObject?
The ComDefaultInterface attribute is only really useful if you have more than one interface implemented on a single object. The "first" interface exposed by an object can be important in certain cases, but the order is not actually specified by the language. The attribute forces the interface you specify to be emitted first, with any others coming in a non-specified order.
It is also meant for classes that you are exporting from managed code to COM, so that clients who get your class returned to them in ways other than CoCreateObject get the correct 'default' interface (e.g. if your class is marked as [ClassInterface(ClassInterfaceType.None)]).
For imported classes that you work with via managed code, or classes that only implement a single interface, the attribute is harmless but essentially useless.
Also, as far as your last question, you rarely have to resort to low-level interface querying when using COM objects in fully managed code. The C# compiler will automatically handle the QueryInterface calls if you use the normal as and is type coercion keywords. In your case, AuthenticationHelper is being created as a managed AuthenticationHelper class because that's what you asked for; if you know what interface you want and you know it's implemented, ask for that:
AuthenticateHelper ah = new AuthenticateHelper();
IAuthenticate ia = ah as IAuthenticate;
I'm trying to call the following "foo" COM method from c#:
interface IFoo : public IUnknown
{
virtual HRESULT __stdcall foo( BSTR * log ) = 0;
}
Here is how I declare it in c#:
[ComImport, Guid( "98D93A58-2889-43a5-A182-47DEE781D41E" ),
InterfaceType( ComInterfaceType.InterfaceIsIUnknown )]
public interface IFoo
{
void foo( out string log );
}
Here is how I call it in c#:
(x is an instance of the COM class that implements IFoo)
string log;
x.foo( out log );
When I run this code, I get the following exception:
System.Runtime.InteropServices.MarshalDirectiveException was unhandled
Message=Marshaler restriction: Excessively long string.
What am I doing wrong?
By the way, the following works correctly if I call "foo" from c++ as follows:
CComBSTR log;
pX->foo( &log );
You should use the ref specifier. See the examples at Default Marshaling for Strings, especially PassStringRef1 and PassStringRef2.
The following example shows strings declared in the IStringWorker interface.
Visual C++
public interface IStringWorker {
void PassString1(String s);
void PassString2([MarshalAs(UnmanagedType.BStr)]String s);
void PassString3([MarshalAs(UnmanagedType.LPStr)]String s);
void PassString4([MarshalAs(UnmanagedType.LPWStr)]String s);
void PassStringRef1(ref String s);
void PassStringRef2([MarshalAs(UnmanagedType.BStr)]ref String s);
void PassStringRef3([MarshalAs(UnmanagedType.LPStr)]ref String s);
void PassStringRef4([MarshalAs(UnmanagedType.LPWStr)]ref String s);
);
The following example shows the corresponding interface described in a type library.
[…]
interface IStringWorker : IDispatch {
HRESULT PassString1([in] BSTR s);
HRESULT PassString2([in] BSTR s);
HRESULT PassString3([in] LPStr s);
HRESULT PassString4([in] LPWStr s);
HRESULT PassStringRef1([in, out] BSTR *s);
HRESULT PassStringRef2([in, out] BSTR *s);
HRESULT PassStringRef3([in, out] LPStr *s);
HRESULT PassStringRef4([in, out] LPWStr *s);
);
I have in existing COM-interface. I wan't to create a .net assembly that exposes a new interface as COM (with a new GUID), but the structure of the interface needs to be the same.
How can i create a .net class (C#) that exposes this interface?
[
odl,
uuid(1ED4C594-DDD7-402F-90DE-7F85D65560C4),
hidden,
oleautomation
]
interface _IFlashPhase : IUnknown {
[propget]
HRESULT _stdcall ComponentName(
[in] short i,
[out, retval] BSTR* pVal);
[propput]
HRESULT _stdcall ComponentName(
[in] short i,
[in] BSTR pVal);
[propget]
HRESULT _stdcall ComponentMolePercent(
[in] short i,
[out, retval] double* pVal);
[propput]
HRESULT _stdcall ComponentMolePercent(
[in] short i,
[in] double pVal);
[propget]
HRESULT _stdcall ComponentFugacity(
[in] short i,
[out, retval] double* pVal);
[propput]
HRESULT _stdcall ComponentFugacity(
[in] short i,
[in] double pVal);
};
Your IDL isn't valid, an interface that is attributed with [oleautomation] should derive from IDispatch, not IUnknown. I'll give the proper declarations and hint where you need to modify them to get yours.
You cannot declare indexed properties in C#, the C# team refuses to implement them. Version 4 has support for indexed properties that are declared in a COM type library but still doesn't allow declaring them yourself. The workaround is to use the VB.NET language, it has no qualms about it. Add a VB.NET class library project to your solution. Make it look similar to this:
Imports System.Runtime.InteropServices
Namespace Mumble
<ComVisible(True)> _
<Guid("2352FDD4-F7C9-443a-BC3F-3EE230EF6C1B")> _
<InterfaceType(ComInterfaceType.InterfaceIsDual)> _
Public Interface IExample
<DispId(0)> _
Property Indexer(ByVal index As Integer) As Integer
<DispId(1)> _
Property SomeProperty(ByVal index As Integer) As String
End Interface
End Namespace
Note the use of <DispId>, dispid 0 is special, it is the default property of an interface. This corresponds to the indexer in the C# language.
All you need VB.NET for is the declaration, you can still write the implementation of the interface in the C# language. Project + Add Reference, Projects tab and select the VB.NET project. Make it look similar to this:
using System;
using System.Runtime.InteropServices;
namespace Mumble {
[ComVisible(true)]
[Guid("8B72CE6C-511F-456e-B71B-ED3B3A09A03C")]
[ClassInterface(ClassInterfaceType.None)]
public class Implementation : ClassLibrary2.Mumble.IExample {
public int get_Indexer(int index) {
return index;
}
public void set_Indexer(int index, int Value) {
}
public string get_SomeProperty(int index) {
return index.ToString();
}
public void set_SomeProperty(int index, string Value) {
}
}
}
You need to run Tlbexp.exe on both the VB.NET and the C# assembly to generate the type libraries. The C# one with the implementation includes the VB.NET one.
To get the interface to derive from IUnknown instead of IDispatch, edit the interface declaration. Remove the DispId attributes and use ComInterfaceType.InterfaceIsUnknown.