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.
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 currently trying to use Direct3D from c# and I've started by using the D3D12GetDebugInterface API.
The C++ syntax of the function is as follows (copied from the Microsoft documentation):
HRESULT D3D12GetDebugInterface(
REFIID riid,
void **ppvDebug
);
I'm having trouble importing the function into C#. I thought that maybe riid should be a pointer to a Guid struct, and I'm just using an IntPtr for **ppvDebug. Since **ppvDebug is a pointer to a pointer to an ID3D12Debug interface, I tried reimplementing the ID3D12Debug interface in C# code and using Marshal.PtrToStructure() to resolve the IntPtr to a usable ID3D12Debug interface instance, but that won't work. I remember reading about the ID3D12Debug interface being a COM object, but don't you need an ID for a COM object so you can import it? I haven't found any sort of COM ID anywhere in the documentation.
Anyway here's my latest attempt at getting something out of the function:
[DllImport("D3D12.dll")]
static extern int D3D12GetDebugInterface(IntPtr riid, IntPtr ppvDebug);
void func() {
unsafe
{
IntPtr DebugControllerPtr = IntPtr.Zero;
Type InterfaceType = typeof(ID3D12Debug);
Guid ID = InterfaceType.GUID;
IntPtr ptr = Marshal.AllocHGlobal(sizeof(Guid));
Marshal.StructureToPtr(ID, ptr, false);
D3D12GetDebugInterface(ptr, DebugControllerPtr);
Marshal.FreeHGlobal(ptr);
ID3D12Debug DebugController = null;
Marshal.PtrToStructure(DebugControllerPtr, DebugController);
DebugController.EnableDebugLayer();
}
}
In case you want to see my ID3D12Debug interface:
interface ID3D12Debug
{
void EnableDebugLayer();
}
As I said, I think Direct3D makes use of COM, which I am completely missing here, so maybe that's why it doesn't work.
There are usually many ways to declare interop code. Here is one that should work:
public static void Main()
{
D3D12GetDebugInterface(typeof(ID3D12Debug).GUID, out var obj);
var debug = (ID3D12Debug)obj;
debug.EnableDebugLayer(); // for example
}
[DllImport("D3D12")]
public static extern int D3D12GetDebugInterface([MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppvDebug);
[Guid("344488b7-6846-474b-b989-f027448245e0"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ID3D12Debug
{
[PreserveSig]
void EnableDebugLayer();
}
You must add interface Guid and InterfaceType attributes for COM interfaces.
Guid can be passed easily by reference using UnmanagedType.LPStruct
You don't need unsafe code here.
You should check for errors (undone in my code)
If you need .NET interface definitions with DirectX, you can use this open source project here: https://github.com/smourier/DirectN, for example: https://github.com/smourier/DirectN/blob/master/DirectN/DirectN/Generated/ID3D12Debug.cs
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();
}
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 writing a C# COM class using .NET 2.0 to implemented the IActiveScriptParse32 interface. When generating the TLB file using RegAsm.exe the interface methods containing EXCEPINFO structures are not exported. Why is RegAsm.exe not exporting the EXCEPINFO structure?
Interface and class declaration:
using System.Runtime.InteropServices;
using ComTypes = System.Runtime.InteropServices.ComTypes;
namespace ScriptEngine
{
[Guid("BB1A2AE2-A4F9-11cf-8F20-00805F2CD064")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IActiveScriptParse32
{
void InitNew();
void AddScriptlet([MarshalAs(UnmanagedType.LPWStr)] string defaultName,
[MarshalAs(UnmanagedType.LPWStr)] string code,
[MarshalAs(UnmanagedType.LPWStr)] string itemName,
[MarshalAs(UnmanagedType.LPWStr)] string subItemName,
[MarshalAs(UnmanagedType.LPWStr)] string eventName,
[MarshalAs(UnmanagedType.LPWStr)] string delimiter,
IntPtr sourceContextCookie,
uint startingLineNumber,
ScriptTextFlags flags,
[MarshalAs(UnmanagedType.BStr)] out string name,
out ComTypes.EXCEPINFO exceptionInfo);
void ParseScriptText([MarshalAs(UnmanagedType.LPWStr)] string code,
[MarshalAs(UnmanagedType.LPWStr)] string itemName,
[MarshalAs(UnmanagedType.IUnknown)] object context,
[MarshalAs(UnmanagedType.LPWStr)] string delimiter,
IntPtr sourceContextCookie,
uint startingLineNumber,
ScriptTextFlags flags,
out object result,
out ComTypes.EXCEPINFO exceptionInfo);
}
[ComVisible(true)]
[Guid("70C3474B-CFE3-4CBB-89F3-E9C70386BCB5")]
public class MyScriptParser : IActiveScriptParse32
{
// Other interface methods
public void ParseScriptText(string code, string itemName, object context, string delimiter, IntPtr sourceContextCookie, uint startingLineNumber, ScriptTextFlags flags, out object result, out ComTypes.EXCEPINFO exceptionInfo)
{
//Code
}
}
}
RegAsm.exe generates this warning:
Type library exporter warning processing
'ScriptEngine.IActiveScriptParse32. ParseScriptText(exceptionInfo),
MyScriptParser'. Warning: Non COM visible value type
'System.Runtime.InteropServices.ComTypes.EXCEPINFO' is being
referenced either from the type currently being exported or from one
of its base types.
And generates a .tlb file with this declaration:
[
odl,
uuid(BB1A2AE2-A4F9-11CF-8F20-00805F2CD064),
version(1.0),
oleautomation,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "ScriptEngine.IActiveScriptParse32")
]
interface IActiveScriptParse32 : IUnknown {
HRESULT _stdcall InitNew();
};
It is just like it says, the ComTypes.EXCEPINFO structure doesn't have the [ComVisible(true)] attribute. This was intentional. Note that this is only a warning, not an error.
It was intentional because the type library you create from your [ComVisible] C# code will always include stdole2.tlb. Which is a standard type library that declares all the common Automation types, like IDispatch and the structures it uses.
This is something you can see with Oleview.exe. Run it from the Visual Studio Command Prompt, use File + View Typelib and select your type library. Use Tlbexp.exe if you don't have it yet. You'll see the IDL that was decompiled from the type library, it has this near the top:
// TLib : // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
importlib("stdole2.tlb");
Now use File + View Typelib and select c:\windows\system32\stdole2.tlb. You'll see EXCEPINFO declared there.
Or in other words, whatever tool reads your type library will always have a declaration for EXCEPINFO. Regasm.exe just isn't smart enough to realize that, it doesn't look at imported type libraries.
Technically you can suppress the warning by adding a reference to "stdole", you'll see it on the .NET tab. And use stdole.EXCEPINFO in your code instead. This will put the definition of EXCEPINFO in your type library. Not so sure what will happen in the tool you use that reads the type library when it sees two definitions for EXCEPINFO so it's better to just not do that. Just ignore the warning.