2

I did a prototype some time ago, using VSTO and exposing its methods to an other project.

Now I tried to implement my then successful use to a project I am currently working on, but it doesn't work.

I get an Exception Message "System.__ComObject enthält keine Definition für Open.", which in English means as much, as there is no definition in that ComObject.

What am I doing wrong here?

I checked for missspellings, references and if I forgot to implement something I did with the prototype, without success.

Originally I got pointed to the way I am implementing in this answer:

https://stackoverflow.com/a/35555413/3664953

While the containing link is awsome and does help with working at VSTO, I don't see a solution for my particular problem here. Also I checked for problems, this answer pointed out:

https://stackoverflow.com/a/3690214/3664953

I tried using x86-compiled binaries and switched the Framework from 4.5.2 back to 4.5 without change.

€: Well, ok there are changes in behaviour, with the x64-compiled bins (I usually use "Any CPU") there is a null in the ComAddIn's Object ...

I don't really know where this behaviour is orginating, but maybe you do.

Lastly, what is a already bad question, which probably only got created because I am too close up and don't see the mistake, without the broken code.

So here it is, I start with the working Prototype and also include the new code. I cut a little, so now there is only the Open-method in both solutions.

I did this, since I am aware this question is already extremely long and I don't want to kill too much of your time.


The prototype looks like this:

Controler:

static void Main(string[] args)
{
  Microsoft.Office.Interop.Word.Application wd = 
    new Microsoft.Office.Interop.Word.Application();
  wd.Visible = false;
  object addinName = "Worker_AddIn";
  foreach (Microsoft.Office.Core.COMAddIn comaddin in wd.COMAddIns)
  {
    if (comaddin.ProgId.Equals(addinName.ToString(), 
           StringComparison.InvariantCultureIgnoreCase))
    {
      object addinObj = comaddin.Object;
      object[] invokeArgs = { "Dummy" };

      object retVal = 
                    addinObj.
                        GetType().
                        InvokeMember("Open", System.Reflection.BindingFlags.InvokeMethod, 
                        null, addinObj, invokeArgs);

      //dynamics ...
      var t1 = comaddin.Object.Open("Dummy");
      var t2 = comaddin.Object.GetCustomProperties();
      var t3 = comaddin.Object.SetCustomProperty("Test",
                 PropertyTypes.msoPropertyTypeBoolean, 42);
    }
  }
  //Properly close Word
  wd.Quit();
}

AddIn:

PropertyReaderWriter with its Interface:

public enum WordBuiltinProperties
{
  //some enums
}

public enum PropertyTypes
{
  //more enums
}

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IPropertyReadWriter
{
  bool Open(string Path);
  //There are more methods, but this one is already causing problems, 
  //also the others don't work either, so I kept the simplest one ;)
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class PropertyReaderWriter : StandardOleMarshalObject, IPropertyReadWriter
{
  public bool Open(string Path)
  {
    return false;
    //This was a test, if I could actually use those methods outside of VSTO, 
    //so I didn't implement logic, since I know the stuff I need works in VSTO.
  }
}

The AddIn override of RequestComAddInAutomationService():

private IPropertyReadWriter rw;

protected override object RequestComAddInAutomationService()
{
  if (rw == null)
    rw = new PropertyReaderWriter();
  return rw;
}

The Program class:

Microsoft.Office.Interop.Word.Application wd = 
  new Microsoft.Office.Interop.Word.Application();
wd.Visible = false;
object addinName = "Worker_AddIn";
foreach (Microsoft.Office.Core.COMAddIn comaddin in wd.COMAddIns)
{
  if (comaddin.ProgId.Equals(addinName.ToString(), 
       StringComparison.InvariantCultureIgnoreCase))
  {
    object addinObj = comaddin.Object;
    object[] invokeArgs = { "Dummy" };
    object retVal = addinObj.
                     GetType().
                     InvokeMember("Open", System.Reflection.BindingFlags.InvokeMethod, 
                                  null, addinObj, invokeArgs);
    //dynamics ...
    var t1 = comaddin.Object.Open("Dummy");
    var t2 = comaddin.Object.GetCustomProperties();
    var t3 = comaddin.Object.SetCustomProperty("Test", PropertyTypes.msoPropertyTypeBoolean,
                                               42);
  }
}
//Properly close Word
wd.Quit();

The implementation in my project looks like this.

With the interface I have a derived interface, called IPropertyReadWrite_Word, where I add some more methods, I don't override. Also I use IPropertyReadWrite_Word for my final implementation, but since it is this interface, that defines "Open" I only post this to shorten the extremely long post.

Interface:

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IPropertyReadWrite_Common
{
  /// <summary>
  /// Soll die Datei am entsprechenden Pfad öffnen.
  /// </summary>
  /// <param name="Path">Pfad zur Datei</param>
  /// <returns>Gibt Erfolg/true oder Misserfolg/false zurück.</returns>
  bool Open(string Path);
}

The implementation in the AddIn:

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class Word_PropertyReadWrite : StandardOleMarshalObject, IPropertyReadWrite_Word
{
  internal static Word.Document Document;
  internal ThisAddIn home;

  /// <summary>
  /// Erzeugt die Klasseninstanz.
  /// </summary>
  /// <param name="home">Übergibt das ThisAddIn-Objekt, 
  /// um die Funktionen von Word verwenden zu können.</param>
  public Word_PropertyReadWrite(ThisAddIn home)
  {
    this.home = home;
  }

  /// <summary>
  /// Öffnet die Datei am entsprechenden Pfad.
  /// </summary>
  /// <param name="Path">Pfad zur Datei</param>
  /// <returns>Gibt bei Erfolg true zurück.</returns>
  public bool Open(string Path)
  {
    try
    {
        Document = home.Application.Documents.OpenNoRepairDialog(FileName: Path,
            ConfirmConversions: false, ReadOnly: false, AddToRecentFiles: false,
            Revert: false, Visible: false, OpenAndRepair: true, NoEncodingDialog: false);
        return true;
    }
    catch
    {
        //TODO: Logging
        //Rethrow der entstandenden Exception.
        throw;
    }
  }
}

The override of RequestComAddInAutomationService():

private Word_PropertyReadWrite PropertyReadWrite;

protected override object RequestComAddInAutomationService()
{
  //System.Diagnostics.Debugger.Break();
  if (PropertyReadWrite == null)
    PropertyReadWrite = new Word_PropertyReadWrite(this);
  return PropertyReadWrite;
}

And lastly, this is my testclass so far:

static void Main(string[] args)
{
  Microsoft.Office.Interop.Word.Application wd = 
    new Microsoft.Office.Interop.Word.Application();
  wd.Visible = false;
  object addinName = "Dokumentenvorbereitung_Word_AddIn";
  try
  {
    Microsoft.Office.Core.COMAddIn addin = 
      wd.COMAddIns.Item(ref addinName);
    foreach (Microsoft.Office.Core.COMAddIn comaddin in wd.COMAddIns)
    {
      if (comaddin.ProgId.Equals(addinName.ToString(),
          StringComparison.InvariantCultureIgnoreCase))
      {
        var test = (comaddin).Object.Open(@"Path to some valid .docx");
      }
    }
  }
  catch(Exception e)
  {
    //...
  }
  finally
  {
    wd.Quit();
  }
}
Community
  • 1
  • 1
ExNought
  • 193
  • 1
  • 11
  • 1
    So, your (Console?) solution is trying to call code in a VSTO Add-in loaded in the Word UI? In the first implementation you call the Open method using late-binding (PInvoke). In the second one, that doesn't work, you use early-binding. That's a pretty big difference, and there must be a reason you went to all the trouble to use late-binding, originally? – Cindy Meister May 10 '16 at 16:41
  • Hi Cindy, after a night of sleep I could boil it down to my interface. In my project I used a Interface calles 'IPropertyReadWrite_Word' which is derived from 'IPropertyReadWrite_Common' in which almost all methods are defined (including the bugged out Open(string Path). If I add _Common as second Interface for my class, it seems to work... Very strange behaviour and I am not sure why this happens. – ExNought May 11 '16 at 05:56
  • 1
    Mmmm, your Question is very definitely not clear on the situation with the various interfaces. On reading this last comment, it seems logical to me that both need to be "referenced" if the one "builds" on the other... I think a night of sleep was a very good idea :-) – Cindy Meister May 11 '16 at 18:19

1 Answers1

0

Well, I got to a solution and it is a bit strange, I added the parentinterface of IPropertyReadWrite_Word, called IPropertyReadWrite_Common, to the implementation.

Now my header looks like this:

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class Word_PropertyReadWrite : StandardOleMarshalObject, IPropertyReadWrite_Common,
    IPropertyReadWrite_Word

Now it works like a charm, though I am not sure, why it shouldn't have worked to begin with ... Maybe someone can add a hint?

This is such a strange behaviour, I somewhat doubt, anyone will have the very same problem in the near future, since my use-case is quite ... odd :)

At this point, I really want to thank @Cindy Meister, you helped me twice already and again with such a weird question, thanks for your patience!

ExNought
  • 193
  • 1
  • 11