Does anyone know how MSXML2.XMLHTTP implement its event like onreadystatechange?Since I need construct a COM component with C# which will raise a event, but the client which use vbscript to call this COM object does not support WScript.CreateObject, so I can't handle the COM event.
But I find that MSXML2.XMLHTTP's event can be handled in my client as follows:
Function Hello()
If(objHttp.readyState=4) Then
MsgBox objHttp.responseText
End If
End Function
Set objHttp = CreateObject("MSXML2.XMLHTTP")
Set xmlDoc = CreateObject("MSXML.DOMDocument")
strWebserviceURL="http://localhost:8083/WebService.asmx/HelloWorld"
objHttp.onreadystatechange=getRef("Hello")
objHttp.Open "POST", strWebserviceURL
objHttp.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
objHttp.send()
I've learnt from here that the event of MSXML2.XMLHTTP is not implemented as a COM automation event.
So I wonder How does MSXML2.XMLHTTP implement its event, can anyone give a hint, any help will be appreciated.
"onreadystatechange" is not a COM Automation event because COM Automation events require the caller (mainly written in scripting languages) to implement specific COM Interfaces, which scripting languages often cannot do (look up COM Connection Points to get the full details).
If you start with the link you provided, and add information about MSXML that you can gather with the "OLE View" utility (also called the "OLE-COM Object Viewer"), you have everything you need to figure it out.
Use Ole View and navigate to the MSXML type library ("\Type Libraries\Microsoft XML, v6.0 (ver 6.0)"). Open that, which will open a separate window with the Type Library shown in detail.
From the Type Library viewer, open "CoClasses" and find XMLHTTP60 (that's the exact class name used in the MSDN post). You will verify that it implements the IXMLHTTPRequest interface.
Now, open the Interfaces node and find IXMLHTTPRequest. That will yield the following code (which is generated IDL based on the metadata stored in the type library):
...
[
odl,
uuid(ED8C108D-4349-11D2-91A4-00C04F7969E8),
helpstring("IXMLHTTPRequest Interface"),
dual,
oleautomation
]
interface IXMLHTTPRequest : IDispatch {
...
[id(0x0000000e), propput, helpstring("Register a complete event handler")]
HRESULT onreadystatechange([in] IDispatch* rhs);
};
That tells you how it all works. You will need to add a method to your code that takes an IDispatch reference as an argument. Save the reference. The contract for your object will say that the object provided must have a method named "OnWhateverMyEventNameIs", and possibly specify a list of arguments the method must take.
When your event needs to be signaled, your code will take that reference that was previously provided, look for a method with that name, and execute it if found.
Since you're working on C#, your setter method or property needs to be [COMVisible] (obviously) and it should take an Object reference. The simplest thing for your event triggering implementation might be to use the dynamic support in the language to just call the method (inside a try/catch, in case the method doesn't exist or is otherwise invalid).
Related
Apple recently deprecated the MIDIDestinationCreate method and replaced it with a MidiDestinationCreateWithProtocol (MIDIDestinationCreate)
The old method required to pass a simple callback method 'MIDIReadProc' which from C# could be done by creating the following delegate signature, then creating a delegate and assigning a method as the callback. We declare the delegate signature:
internal delegate void MIDIReadProc(MIDIPacketListPtr pktlist, IntPtr readProcRefCon, IntPtr srcConnRefCon);
Create a delegate to assign our callback method to:
private CoreMidiInterop.NativeMethods.MIDIReadProc m_readProcDelegate;
Assign a method to the delegate we created:
m_readProcDelegate = CallMessageReceived;
Create a callback method, that should receive MIDI messages from macOS Core MIDI:
private void CallMessageReceived(MIDIPacketListPtr pktlist, IntPtr readProcRefCon, IntPtr srcConnRefCon)
{
...
}
Eventually pass this delegate to the apple Core MIDI method:
CoreMidiInterop.NativeMethods
.MIDIDestinationCreate(m_clientRef, CoreFoundationUtils.ToCFStringRef(name), m_readProcDelegate, CFStringRef.Zero, out MIDIEndpointRef destinationRef);
This all works as expected.
That's now deprecated and no longer works on macOS Big Sur. The new method "MidiDestinationCreateWithProtocol" requires an apple block to use as the callback param (called MIDIReceiveBlock readBlock):
OSStatus MIDIDestinationCreateWithProtocol(MIDIClientRef client, CFStringRef name, MIDIProtocolID protocol, MIDIEndpointRef *outDest, MIDIReceiveBlock readBlock);
The documentation here: MIDIReceiveBlock
How is it possible to create an apple block with C# code? I've been searching for examples but cannot find any. I did start looking at the underlying implementation of apple blocks here:
Block Implementation Specification
It's no simple thing, so any help/example of how to do this in C# would be really helpful.
First, I feel that Apple is being inconsistent with their use of the word "deprecate". Since these new functions involving the words "WithProtocol" are only valid for Big Sur at this time, I don't see how all previous code can be deprecated until the previous versions of the operating system are no longer supported. See https://bradleyross.github.io/ObjectiveC-Examples/Documentation/BRossTools/FunctionalArea.html.
See https://bradleyross.github.io/ObjectiveC-Examples/Documentation/BRossTools/CoreMidi.html#MIDIDestinationCreateWithBlock MIDIDestinationCreate should indeed be deprecated, but MIDIDestinationCreateWithProtocol is only for BigSur. In addition the "WithProtocol" functions are to support MIDI 2.0, and the full specifications for MIDI 2.0 have not yet been released to the general public. I would therefore prefer to use MIDIDestinationCreateWithBlock. See the Xcode project BRossTools in the GitHub project at https://github.com/BradleyRoss/ObjectiveC-Examples. You can search this repository for the names of the CoreMIDI functions you are looking for and it may help. You might also want to look at https://github.com/mixedinkey-opensource/MIKMIDI. Part of the problem is that the functions ending with "WithBlock" and most of the functions ending with "WithProtocol" use closures (another name for blocks). I'm not sure how C# closures and CLANG closures (used in Objective-C) compare. Try to look for information on C# closures.
I have a C# class that has a property(name is List) of type Microsoft.VisualBasic.Collection. We need to exposed this property to COM. For that, I was writing an interface for my class such that the property would marshaled as of UnmanagedType.IDispatch.
Something like this:
[DispId(0x68030000)]
Collection List { [DispId(0x68030000)] [return: MarshalAs(UnmanagedType.IDispatch)] get; }
This piece of code was earlier in VB and was being used by C++ as tye VT_DISPATCH. However, while building the C# library, I get the following error:
C:\Program Files
(x86)\MSBuild\14.0\bin\Microsoft.Common.CurrentVersion.targets(4335,5):
error MSB3212: The assembly "Name.dll" could not be converted to a
type library. Type library exporter encountered an error while
processing 'Namespace.InterfaceName.get_List(#0), ProjectName'. Error:
Error loading type library/DLL.
I read through few of the posts online which suggested that such errors might cause because of repetitive GUIDs. But that is not the case. I tried with multiple GUIDs. I don't feel this is an issue with any other attribute that I had set on my Interface since, I am able to marshal other properties and function calls ( some of them use primitive types and others use custom classes) successfully.
This is how it is consumed in the consuming C++ application:
LPDISPATCH result;
InvokeHelper(0x68030000, DISPATCH_PROPERTYGET, VT_DISPATCH, (void*)&result, NULL);
return result;
This has become a go live issue with the client and I really do not have much time to continue investigation since this is due tomorrow.
Appreciate any help!
I don't think Microsoft.VisualBasic.Collection is COM-visible. Therefore, you cannot use this type as a return value or parameter in a COM class or interface. However, ICollection (which Microsoft.VisualBasic.Collection implements) is COM visible. If that would suit your purposes, use that as the type of your property rather than Microsoft.VisualBasic.Collection.
I’m trying to pass a COM object from C# code to Perl.
At the moment I’m wrapping my Perl code with PerlNET (PDK 9.4; ActiveState) and I have defined a simple subroutine (+ required pod declaration) in Perl to pass objects from C# to the wrapped Perl module.
It seems that the objects I pass are not recognized correctly as COM objects.
An example:
In C# (.NET 4.0), the ScriptControl is used to load a simple class from a file written in VBScript.
var host = new ScriptControl();
host.Language = "VBScript";
var text = File.ReadAllText("TestScript.vbs");
host.AddCode(text);
dynamic obj = host.Run("GetTestClass");
What I get (obj) is of type System.__ComObject. When I pass it to my Perl/PerlNET assembly and try to call method Xyz() in Perl I get the following (runtime) exception:
Can't locate public method Xyz() for System.__ComObject
If, however, I do more or less the same thing in Perl, it works. (In the following case, passing only the contents of my .vbs file as parameter.)
I can even use the script control :
sub UseScriptControl {
my ($self, $text) = #_;
my $script = Win32::OLE->new('ScriptControl');
$script->{Language} = 'VBScript';
$script->AddCode($text);
my $obj = $script->Run('GetTestClass');
$obj->Xyz();
}
Now, calling Xyz() on obj works fine (using Win32::OLE).
In both cases I use:
use strict;
use Win32;
use Win32::OLE::Variant;
Another approach:
I can invoke methods by using InvokeMember of class System.Type if I specify exactly which overload I want to use and which types I’m passing:
use PerlNET qw(typeof);
typeof($obj)->InvokeMember("Xyz",
PerlNET::enum("System.Reflection.BindingFlags.InvokeMethod"),
PerlNET::null("System.Reflection.Binder"),
$obj,
"System.Object[]"->new());
Using this approach would mean rewriting the whole wrapped Perl module. And using this syntax..
Now I am wondering if I am losing both the advantages of the dynamic keyword in .NET 4.0 and the dynamic characteristics of Perl (with Win32::OLE) by using PerlNET with COM objects.
It seems like my preferred solution boils down to some way of mimicking the behaviour of the dynamic keyword in C#/.NET 4.0.
Or, better, finding some way of converting the passed COM object to something that will be recognized as compatible with Win32::OLE. Maybe extract some information of the __ComObject for it to be identified correctly as COM object.
I have to add that I posted to the PDK discussion site too (but didn’t get any response yet): http://community.activestate.com/node/18247
I also posted it to PerlMonks - as I'm not quite sure if this is more a Perl or C#/.NET question:
http://www.perlmonks.org/?node_id=1146244
I would greatly appreciate any help - or advise on where to look further.
The Background (Or, look how far I've gone on my own!)
I'm starting from the Windows 8 Media Extension Sample. I'm using the grayscale example as a starting point to learn how to pass values from managed code into a COM object, and how to pass values from my COM object back to managed code. In my IDL file, I've got a GrayscaleEffect class (taken exactly from the example) and a custom interface that should let me query a string over in the c# world.
The IDL file:
namespace GrayscaleTransform
{
[version(NTDDI_WIN8), uuid(553B5684-4C22-4D21-8638-1E7D86D84F10)]
interface MyInterface : IInspectable {
HRESULT GetMsg([out] HSTRING *message);
}
[version(NTDDI_WIN8)]
runtimeclass GrayscaleEffect {
interface Windows.Media.IMediaExtension;
interface MyInterface;
}
}
My GrayscaleEffect class implementation implements GetMsg to return the string "Woozle"
The relevant c#:
cap = new MediaCapture();
await cap.InitializeAsync();
previewElement1.Source = cap;
await cap.StartPreviewAsync();
PropertySet props = new PropertySet();
await cap.AddEffectAsync(
Windows.Media.Capture.MediaStreamType.VideoPreview,
"GrayscaleTransform.GrayscaleEffect",
props);
if (this.props.ContainsKey("ref"))
{
var augGui = (GrayscaleTransform.MyInterface)this.props["ref"];
string message;
augGui.GetMsg(out message);
}
After the call to GetMsg, I can see that message contains the string "Woozle" just like I expect - Excellent!
The Problem
Now I want to do something a little bit fancier. Instead of a method that lets me pass a string from COM to C#, I want to implement a method that lets me pass a delegate from C# to COM. I'd like to get the MFT class to call this method every 10th video frame or something - that part's not important. I want my interface to have a method called SubscribeEvent that takes a delegate as a parameter. For now, it can be a void delegate that takes no arguments.
This page leads me to believe that I should be able to declare a WinRT delegate over in the COM world and be able to pass a delegate of the same type from C# (see the 3rd item from the end). Cool - seems easy. My best try so far has been something like this in the idl:
delegate void CallbackMethod();
[version(NTDDI_WIN8), uuid(553B5684-4C22-4D21-8638-1E7D86D84F10)]
interface MyInterface : IInspectable {
HRESULT Subscribe(CallbackMethod cb);
}
Unfortunately this results in an error - seems to be related to the declaration of the delegate CallbackMethod:
error MIDL9008: internal compiler problem - See documentation for suggestions on how to find a workaround.
Edit
This one is obvious. That syntax is a C++/cx thing. Upon closer inspection, I don't have the C++/CX extensions turned on. This is a WRL project which means it's regular flavor c++ with template magic.
I also tried to wire up an event by implementing the IConnectionPoint interface, but when I include OCIdl.h I get a compile error that desktop components cannot be compiled for ARM.
The Question
Am I barking up the wrong tree here? Should I be making my GrayscaleEffect class be a WinRT runtime class using C++/CX? Maybe we're venturing into "New question" territory here, but when I try to implement IMFTransform with a WinRT class in C++/CX, I get
error C2811: 'GrayscaleRT::Class1' : cannot inherit from 'IMFTransform', a ref class can only inherit from a ref class or interface class
A better question:
How about this: Can I pass a delegate here at all? I fell like I ought to be able to, if only I knew the magic combination of WRL templates to make it work.
It looks like this is the answer. In WRL, you declare an event in your idl like this:
[uuid(3FBED04F-EFA7-4D92-B04D-59BD8B1B055E), version(NTDDI_WIN8)]
delegate HRESULT WhateverEvent();
See the To add an event that fires when a prime number is calculated section
My problem was that I initially didn't understand that I wasn't using c++/cx. I was using regular c++ with WRL which is a template library similar to ATL that makes WinRT programming easier and doesn't use non-standard language extensions.
Once I understood that, it was relatively easy to find an example of an event handler using WRL
Initially, it was probably not proper to create COM through late binding, and then call its methods via InvokeMember.
But now, perhaps, too late to redo everything from scratch.
Therefore, tell me how to be.
There is a COM object in the DLL. Written in Delphi7.
In C#, it is used as follows:
Type comType = Type.GetTypeFromProgID(ProgID, false);
object comObj = Activator.CreateInstance(comType);
// and then call methods
comType.InvokeMember("DoLongWork", BindingFlags.InvokeMethod, null, comObj, null);
Now we need to add to it the opportunity to call the methods of the server (i.e. the one who keeps to himself this COM object)
For this, in a COM object in its TLB added additional interface
IHookCallback = interface(IDispatch)
procedure ServerHook(DoStuff: integer); safecall;
end;
And also, in its main interface added initialization method callback
ITestDisp = dispinterface
...
procedure SetupHook(const Callback: IHookCallback); safecall;
Then imported into the VS project DLL - with this COMom inside. Thereby gained access to the interface description.
Then (in VS) created a class that implements this interface.
And I try to transfer it to the COM through InvokeMember
comType.InvokeMember("SetupHook", BindingFlags.InvokeMethod, null, comObj, new object[] {SomeClass as IHookCallback});
and so, too, tried
comType.InvokeMember("SetupHook", BindingFlags.InvokeMethod, null, comObj, new object[] {SomeClass});
I receive an error
Exception has been thrown by the target of an invocation.
InnerException
Specified cast is not valid.
Where did I go wrong?
I did.
Write here may be useful to someone my decision.
Initially I have COM object with its own TLB. Written in Delphi 7.
There was also a project written in VS 2010. He created the COM using "Activator Class". And then the work was carried out through late binding. Ie through InvokeMember.
The task was this: were ready COM objects, which could not be replaced. Ie was necessary to ensure backward compatibility and work with existing code as before.
But at the same time, you had to write another 2 COM object with advanced features - support several new methods.
I solved this problem as follows: created a separate TLB on Delphi 7. It announced a completely independent interface (inherited from IDispatch but it does not matter).
Then I created a new COM object that implements the old interface and the new interface.
Then modified the C #. He also created objects through "Activator Class", but after the establishment immediately brought the created object to the second interface (I imported the description of a second TLB in C # project) with the operator "as". If the result was NULL then it was an old COM does not support the new functionality. Otherwise, we have a new COM with enhanced functionality.
Done.