Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds CorelDRAW #964

Merged
merged 10 commits into from
Feb 8, 2016
Merged

Adds CorelDRAW #964

merged 10 commits into from
Feb 8, 2016

Conversation

ThunderFrame
Copy link
Member

Based on a CreativeSuite X6 (Update 4.1) TLB/PIA.

I'm unsure about Rubberduck.VBEEditor/VBEHost/CorelDRAWApp.cs

  • Please review my use of null as the third argument to a method that expects a ParamArray:
Application.GMSManager.RunMacro(qualifiedMemberName.QualifiedModuleName.ProjectName.ToString(), qualifiedMemberName.ToString(), null);

I haven't yet run unit tests under CorelDRAW, but will do on next commit.

@retailcoder
Copy link
Member

Please confirm Unit Test Explorer runs test methods and displays expected test outcomes, given:

  • TestModule1 with at least 3 test methods, including one that is set up to succeed (e.g. Assert.IsTrue True, "there is only one truth"), one set up to fail (e.g. Assert.Fail "wasn't meant to be") and another set up to be inconclusive (e.g. Assert.Inconclusive "we'll never know")

If that works, then the qualified method calls are ok, so add another test module with the same test schemes (keep the default method names, so there will be one TestMethod1 in each test module) - the idea is to test whether the host behaves like Excel (i.e. is ok with fully-qualified method calls) or like Word (i.e. method names must be unique across modules).

Thanks!

@ThunderFrame
Copy link
Member Author

I can get Rubberduck to load, and I can use nearly all of the functionality. For tests, I can add a Test Module and I can add Test Methods using the UI.
But I can't run tests because I think the call to RunMacro is failing because of the VB ParamArray variant expected by that method. That is, CorelDRAW crashes when running tests.
If only I could find a way to pass a parameter to that ParamArray. I did find this which might be good or bad news.

The RunMacro signature in CorelDRAW is

Function RunMacro(ModuleName As String, MacroName As String, ParamArray Parameters() As Variant)

So I can call it from VBA using

Application.GMSManager.RunMacro "Proj", "TestModule1.TestMethod1"

The interop signature is

dynamic RunMacro(string ModuleName, string MacroName, params object[] Parameters);


public override void Run(QualifiedMemberName qualifiedMemberName)
{
Application.GMSManager.RunMacro(qualifiedMemberName.QualifiedModuleName.ProjectName.ToString(), qualifiedMemberName.ToString(), null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the 3rd parameter is params (ParamArray in VBA), then it should be omitted; passing a null there amounts to passing a Nothing parameter value to a parameterless procedure - not going to work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also the ProjectName is already a string, the ToString call is redundant, and calling ToString on the qualifiedMemberName would return the qualified member name, like Proj.TestModule1.TestMethod1, which doesn't seem like what the API is expecting. Would this work?

Application.GMSManager.RunMacro(qualifiedMemberName.QualifiedModuleName.ProjectName, qualifiedMemberName.MemberName);

@ThunderFrame
Copy link
Member Author

Despite the misleading parameter names, The method is expecting:
`"proj", "TestModule1.TestMethod1", where proj is the name of my project, TestModule1 is the name of my module and TestMethod1 is the name of my procedure
Interop is demanding that I provide a 3rd argument as the signature requires 3 arguments, and there is no overload for 2.
I've just tried these 2 alternatives, and still CorelDRAW crashes

Application.GMSManager.RunMacro(qualifiedMemberName.QualifiedModuleName.ProjectName, qualifiedMemberName.MemberName, null);
Application.GMSManager.RunMacro(qualifiedMemberName.QualifiedModuleName.ProjectName, qualifiedMemberName.MemberName, new object[] );

@retailcoder
Copy link
Member

Ok. None of the two alternatives you've tried provide the expected "TestModule1.TestMethod1" identifier.

As for the 3rd argument, it's a ParamArray, which is used to determine what arguments to pass to the method being called. Since we're calling a parameterless method, it must be unspecified. If you pass null, you're telling it to pass Nothing as a parameter. If you pass an empty array, you're telling it to pass an empty array as a parameter. There must be no parameter specified - params just works like that, consider it an optional list of values to pass in as parameters.

Try this:

var projectName = qualifiedMemberName.QualifiedModuleName.ProjectName;
var memberName = qualifiedMemberName.QualifiedModuleName.ComponentName + "." + qualifiedMemberName.MemberName;
Application.GMSManager.RunMacro(projectName, memberName);

Your two snippets pass "TestMethod1" for a member name, and a parameter value.

Quoting your working VBA code:

Application.GMSManager.RunMacro "Proj", "TestModule1.TestMethod1"

The ParamArray / params parameter is correctly omitted.

@ThunderFrame
Copy link
Member Author

I'll give it a try tonight, but VS complains when passing 2 arguments for a method that doesn't have a 2-argument overload.

That said, passing the correct project name and module.method name should at least concentrate the bug tracking on the 3rd argument.

I'm thinking of tenporarily creating an auxiliary helper method TestMethod1dummy(value as string) that takes an argument, and then I can assure myself that passing anything but an empty paramarray is working...

var projectName = qualifiedMemberName.QualifiedModuleName.ProjectName;
var memberName = qualifiedMemberName.QualifiedModuleName.ComponentName + "." + qualifiedMemberName.MemberName + "dummy";
Application.GMSManager.RunMacro(projectName, memberName, "foo");

@retailcoder
Copy link
Member

@ThunderFrame the signature is this, right?

dynamic RunMacro(string ModuleName, string MacroName, params object[] Parameters);

If that's the case, the 3rd parameter isn't an array, but a params, which in C# is effectively an optional list of comma-separated values, just like ParamArray is in VBA. It should compile fine with only the first two parameters specified. If it doesn't, then that 3rd parameter isn't a params.

@rubberduck203
Copy link
Member

Random thought, is the interop method expecting an instance of the Missing type?

@ThunderFrame
Copy link
Member Author

@ckuhn203 Yes, I was just thinking that. It's very 2008ish, but I'll try it tonight.

@ThunderFrame
Copy link
Member Author

I just had a thought... I can add a private helper method that accepts a params object[] p and then pass p to the RunMacro call. It's kludgey, but I'm thinking that makes p have the right attributes, and it might just work.

Will try tonight.

@ThunderFrame
Copy link
Member Author

Well, it seems that a COM signature of:

Function RunMacro(ModuleName As String, MacroName As String, ParamArray Parameters() As Variant)

gets turned into an Interop signature of:

dynamic RunMacro(string ModuleName, string MacroName, params object[] Parameters);

And you'd think that the params syntax would allow a params type call, but if I try to call the above Interop method, C# is forcing me to provide a third argument.

//Calling with just 2 arguments gives error
//Error - No overload for method 'RunMacro' takes 2 arguments

//calling with the 3rd argument = System.Reflection.Missing gives errors:
//Error - The best overloaded method match for 'Corel.GraphicsSuite.Interop.CorelDRAW.IDrawGMSManager.RunMacro(string, string, ref object[])'
//Error - Argument 3: cannot convert from 'System.Reflection.Missing' to 'ref object[]'
//Error - 'System.Reflection.Missing' is a 'type', which is not valid in the given context

If I create my own private method in C#, then I can call it without any 3rd argument, as you'd expect the params keyword to allow.

//...
{
RunHelper(projectName, memberName);
}
//...
private void RunHelper(string ProjectName, string MemberName, params object[] p)

So it seems that despite the params in the Interop signature, Interop/COM is forcing me to pass a variant array, and that variant array can be empty (as I need it to be). I can pass an empty object[], but CorelDRAW crashes, regardless of how I create it or pass it.

I've come to the conclusion that there's either some bizarre COM factors at play, or, as seems more likely, CorelDRAW's GMSManager object (short for Global Macros Manager) is conflicting with Rubberduck trying to call a Macro that GMSManager manages. That is, I don't think the RunMacro method was ever intended to be called by an automation client.

@rubberduck203
Copy link
Member

@ThunderFrame have you tried passing in an empty object array?

..., new object[]);

@ThunderFrame
Copy link
Member Author

yes, I tried that....

But I just found this, which had this gem:

There is currently a problem of calling RunMacro through IDispatch interface when the macro doesn't have any parameters.
Unfortunately RunMacro method has a problem in that when it is being called through IDispatch and the macro being run doesn't have any parameters it fails.

So I adjusted my code to omit the RunMAcro method, and just tried to get a reference to the GMSManager, and just that is enough to crash CorelDRAW.

//This kills CorelDRAW
 object GMS = Application.GMSManager;

@rubberduck203
Copy link
Member

Also make sure that before you call RunMacro from another application, you call Application.InitializeVBA method first.

You've not done that. May be worth trying. If it works, try calling it in the classes ctor so it doesn't get called on every test.

@retailcoder
Copy link
Member

Well... if RunMacro can't run a parameterless method, we're kinda stuck. Application.InitializeVBA would have to be called at application startup though, when IHostApplication is CorelDrawApp.

@rubberduck203
Copy link
Member

@retailcoder the whole blowing up thing seems to only apply to late binding.
I'm thinking that it's failing because Application.InitializeVBA wasn't called.

If it works, this ctor

        public CorelDRAWApp() : base("CorelDRAW") { }

Would just need to be changed to

    public CorelDRAWApp() : base("CorelDRAW") 
    { 
        Application.InitalizeVBA();
    }

@retailcoder
Copy link
Member

retailcoder commented Feb 3, 2016 via email

@ThunderFrame
Copy link
Member Author

I think the InitializeVBA method is necessary when you're automating CorelDRAW as a new instance, but RubberDuck is operating on an existing instance, and an instance that must have VBE/VBA open before RubberDuck can be called. RubberDuck can insert a test module/method, for example.

I will try the constructor nonetheless.

The GMSManager loads a number of document-less projects by default, which I manually unload (because RubberDuck complains about the compiler directives), so there is a chance that the GMSManager object requires some of those projects to be present, and so maybe that's why my attempt to get a handle to GMSManager fails.

And if the test methods require a parameter, then can't we just inform the user of that requirement? RubberDuck would still be able to call a method with a parameter.

@ThunderFrame
Copy link
Member Author

I think Application is never set, so for example, in the Run override method for CorelDRAW, even this line crashes CorelDRAW:

string version = Application.Version;

I think it is this line that is failing

Application = (TApplication)Marshal.GetActiveObject(applicationName + ".Application");

Indeed, I can't get CorelDraw.Application using GetObject from Excel either:

'This fails
Set app = GetObject(,"CorelDraw.Application")

'This works
Set app = GetObject("","CorelDraw.Application")

Any ideas on how to get the Application?

…iably for Excel and Word.

Added a ctor override to HostApplication Base, that gets the host
application via vbe.vbProject.vbComponent.Properties. That allows the
vbe to reliably find the real host, if there is more than 1 copy of a
host application open.
@ThunderFrame
Copy link
Member Author

In order to get CorelDraw working, this PR also addresses:
#1020
#811

@retailcoder
Copy link
Member

So.. unit testing works? Got a screenshot? :-)

@@ -62,11 +62,11 @@ public static IHostApplication HostApplication(this VBE vbe)
switch (reference.Name)
{
case "Excel":
return new ExcelApp();
return new ExcelApp(vbe);
case "Access":
return new AccessApp();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change looks like it's going to break the Access, PowerPoint, & Publisher hosts. Can we just call it quits on this? Would it be nice to support CorelDraw? Yeah, is it worth making changes that could break many more existing users? No.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't break anything. I've overloaded the HostApplication constructor, so ExcelApp() is valid, and so is ExcelApp(vbe). Unit testing still works under Excel and Word.

But, because #811 , ExcelApp() doesn't necessarily get you the correct instance of Excel, whereas, the overload Excel(app), does get you the right instance (and if it can't get the instance from vbe, then it gets an instance in the same way as ExcelApp().

All of the host applications could benefit from using the overloaded ctor, but I didn't want to change all of them at once, and Access, for example, won't always have a document-type VBComponent (Access forrm or report), so it doesn't get as much benefit as overloading Excel and Word ctors.

@ThunderFrame
Copy link
Member Author

Yes, unit testing works. Here's a screenshot with 3 tests (1 test, and 2 expected error tests). One of the Expected Error tests intentionally fails (because the expected error doesn't occur.

The Insert Test Module, insert Test Method and insert Test Method Expected Error menu items all work. Test Explorer finds all test methods.

I can Run all the menu items for Running tests.

coreldraw tests

@retailcoder
Copy link
Member

@ThunderFrame can you confirm that PowerPoint isn't broken to calm @ckuhn203 down (:wink:)? If you have MS-Access to test as well, would be nice. Good job!

@ThunderFrame
Copy link
Member Author

Powerpoint 2013 tested. Unit Tests work
Access 2013 tested. Unit tests work

@retailcoder
Copy link
Member

@ckuhn203 why call quits? it works!

@Hosch250
Copy link
Member

Hosch250 commented Feb 8, 2016

I'll take the blame if anything breaks.

Hosch250 added a commit that referenced this pull request Feb 8, 2016
@Hosch250 Hosch250 merged commit efebe3c into rubberduck-vba:next Feb 8, 2016
@retailcoder
Copy link
Member

@Hosch250 🙉 🙈

@rubberduck203
Copy link
Member

Ok. Ok. Just calling out an odd looking change. Good work @ThunderFrame!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants