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.
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 implementing a .NET COM Server for a 3rd party system which is the COM client (C++). I had a IDL provided which I used to generate the .dll with tlbimp. In the API its also recommended to implement the interfaces dual and Apartment. In this COM server I have to instantiate a WPF component, which currently fails because it has to be called from a STA Thread. I did a lot of googeling already on how to enforce my COM Server to be of Threading model Apartment. I changed the registry entry which didn't help and then I found following article on stackoverflow: How to make make a .NET COM object apartment-threaded?
I followed the instructions to implement ICustomQueryInterface but it fails; Here my IDL:
[
object,
uuid(ABB09FED-F7BE-4654-88BC-5F4D2941111A),
dual,
nonextensible,
helpstring("IDVRServer Interface"),
pointer_default(unique)
]
interface IDVRServer : IDispatch{
[id(1), helpstring("Creates a DVR Unit Connection based on connection information
such as IP address, user name...")]
HRESULT CreateConnection(
[in] DVRConnectionInfo* pConnInfo, [out, retval] IDVRUnitConnection** ppConn);
};
Here the implementation in C# (including the ICustomQueryInterface):
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)
public class DVRServer : IDVRServer, ICustomQueryInterface
{
private IntPtr _dvrPointer;
public DVRServer() {
NativeMethods.CoGetStdMarshalEx(this, NativeMethods.SMEXF_SERVER, out _dvrPointer);
}
public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv) {
if (iid == NativeMethods.IID_IMarshal_DVRServerDispatch)
{
res = 0;
res = Marshal.QueryInterface(_dvrPointer, ref NativeMethods.IID_IMarshal_DVRServerDispatch, out ppv);
string hex = res.ToString("X");
if (res != 0)
{
return CustomQueryInterfaceResult.Failed;
}
return CustomQueryInterfaceResult.Handled;
}
return CustomQueryInterfaceResult.Failed;
}
static class NativeMethods
{
public static Guid IID_IMarshal_DVRServerDispatch = new Guid("ABB09FED-F7BE-4654-88BC-5F4D2941111A");
public const UInt32 SMEXF_SERVER = 1;
[DllImport("ole32.dll", PreserveSig = true)]
public static extern void CoGetStdMarshalEx([MarshalAs(UnmanagedType.IUnknown)] object pUnkOuter, UInt32 smexflags, out IntPtr ppUnkInner);
}
The result of Marshal.QueryInterface is always 0x80004002 E_NOINTERFACE. I tried a lot of different things already, also deriving from StandardOleMarshalObject and ServicedComponent but nothing worked. One question is also if the registry does count at all or if I can just ignore the ThreadingModel in there, I didn't really get that from Noseratio's post. Thanks in advance for any help on that topic.
I use VisualStudio 2012 and .Net Framework 4.
I have an C++ DLL in which the following functions are exported.
double getDouble(std::wstring filename, std::string ID, status &stCode);
int getInt(std::wstring filename, std::string ID, status &stCode);
float getFloat(std::wstring filename, std::string ID, status &stCode);
string getString(std::wstring filename, std::string ID, status &stCode);
int* getIntArray(std::wstring filename, std::string ID, status &stCode);
float* getFloatArray(std::wstring filename, std::string ID, status &stCode);
string* getStringArray(std::wstring filename, std::string ID, status &stCode);
where status is of enum type...
Now I want to use this DLL in my C#.NET app... Can anyone tell me how do i delclare the respected methods in C# and can make a call to this methods.... Thanks in advance...
[DllImport("external.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
public static extern MbStatus queue_accept(
int reader,
[MarshalAs(UnmanagedType.LPStr)] string status);
Lookup the parameters for the DllImport attribute. Depending on your DLL those might need to be adjusted!
Side note: I usually wrap the external dll in an interface and a code layer to decouple it for tests and load it with dependency injection. I also don't change the naming conventions.
public interface IExternalDllInterop
{
MB_STATUS queue_accept(int reader, string status);
}
public class AmbInterop : IAmbInterop
{
public MbStatus queue_accept(int reader, string status)
{
return StaticAmbInterop.mb_queue_accept(reader, message, status);
}
}
Yes. You can. Actually, not std::string, std::wstring, any standard C++ class or your own classes can be marshaled or instantiated and called from C#/.NET. You will need to write a wrapper class for each of the C++ class before you can marshal them in .NET.
The basic idea of instantiating a C++ object from .NET world is to allocate exact size of the C++ object from .NET, then call the constructor which is exported from the C++ DLL to initialize the object, then you will be able to call any of the functions to access that C++ object, if any of the method involves other C++ classes, you will need to wrap them in a C# class as well, for methods with primitive types, you can simply P/Invoke them. If you have only a few methods to call, it would be simple, manual coding won't take long. When you are done with the C++ object, you call the destructor method of the C++ object, which is a export function as well. if it does not have one, then you just need to free your memory from .NET.
Here is an example.
public class SampleClass : IDisposable
{
[DllImport("YourDll.dll", EntryPoint="ConstructorOfYourClass", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.ThisCall)]
public extern static void SampleClassConstructor(IntPtr thisObject);
[DllImport("YourDll.dll", EntryPoint="DoSomething", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.ThisCall)]
public extern static void DoSomething(IntPtr thisObject);
[DllImport("YourDll.dll", EntryPoint="DoSomethingElse", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.ThisCall)]
public extern static void DoSomething(IntPtr thisObject, int x);
IntPtr ptr;
public SampleClass(int sizeOfYourCppClass)
{
this.ptr = Marshal.AllocHGlobal(sizeOfYourCppClass);
SampleClassConstructor(this.ptr);
}
public void DoSomething()
{
DoSomething(this.ptr);
}
public void DoSomethingElse(int x)
{
DoSomethingElse(this.ptr, x);
}
public void Dispose()
{
Marshal.FreeHGlobal(this.ptr);
}
}
For the detail, please see the below link,
C#/.NET PInvoke Interop SDK
(I am the author of the SDK tool)
Once you have the C# wrapper class for your C++ class ready, it is easy to implement ICustomMarshaler so that you can marshal the C++ object from .NET.
http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.icustommarshaler.aspx
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.