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
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’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.
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).
I'm trying create a C# wrapper for C++ code that rewrites all void-returning methods to return a reference to the class instance itself to allow chained method invocation with the C#-side API.
However, this seems to be a non-trivial thing; csout typemap allows for rewriting the body of void-returning methods, but not the function signature. This doesn't work:
%typemap(csout, excode=SWIGEXCODE) void {
$imcall;$excode
return this;
}
On the other hand, cstype typemap allows for rewriting the type void, but when trying to rewrite that, $csclassname refers to the void type itself (producing SWIGTYPE_void), not to the class containing the method that returns a void type:
%typemap(cstype, out="$csclassname") void "void"
I know SWIG isn't intended for this type of transformation, but it seems to be so close. It'd be quite irritating to stack another build tool to do the one more transformation. Does SWIG stretch to provide this?
EDIT: It just occured me that I can change the C++ return types to return pointer to the class itself. (I'm the author of the C++ lib and I'm developing further it at the moment.) Then using a rule like this, I can get what I wanted:
%typemap(csout, excode=SWIGEXCODE) SWIGTYPE * {
$imcall;$excode
return this;
}
The only downside is now that SWIGTYPE * refers to ANY kind of pointer. I'd like to make it to match only pointers of the type of the class itself. Isn't there any way to have more expressive type matching? Or $special_variables that have the name of the containing class?
I have an interface, written in C#, defined as this :
public interface IWidget
{
object Evaluate();
event EventHandler Invalidated;
}
When I try to implement this interface in F#, I look at what F# thinks the IWidget interface is (by hovering my mouse over it), and I see
type IWidget =
interface
member Evaluate : unit -> obj
end
It appears to ignore the Invalidated event entirely... is this a known issue with F# and if so is there any way to work around it? When implementing my F# version of IWidget, can I just implement this event outside of the IWidget section or what? It seems really nasty that f# handles the "event" keyword so poorly...
UPDATE:
After further fiddling around, studio was then saying things like:
'no implementation was given for IWidget.remove_Invalidate(value:EventHandler):unit'
then, when I added those methods so the whole thing looked like:
interface IWidget with
member w.Evaluate() = new obj()
member w.add_Invalidated(value:EventHandler) = ()
member w.remove_Invalidated(value:EventHandler) = ()
end
it compiled fine, even though the tooltip was still saying the only member of IWidget was Evaluate()... it seems like the way F# (or at least the IDE) handles this stuff is really screwy...
ANOTHER UPDATE:
According to the tooltip in the IDE, the [] tag allows an event to be compiled as a CLI metadata event, by transforming it to a pair of add_/remove_ methods... just FYI for anyone who was as confused by this as I was. In short, either implementing those two methods or using that tag work fine, though the fact that the tooltip view of the IWdiget interface lacks any mention of the Invalidate event, and the necessity of implementing such an event is only noticed when the compiler throws an error, is still a clear bug and is pretty confusing. For anyone curious, the following code works fine:
let invalidated = new DelegateEvent<System.EventHandler>()
interface IWidget with
member w.Evaluate() = new obj()
[<CLIEvent>]
member w.Invalidated = invalidated.Publish
end
Thanks for all the help everyone!
F# does support events.
For example:
let invalidated = new DelegateEvent<System.EventHandler>()
[<CLIEvent>]
member this.Invalidated = invalidated.Publish