4

I try to read the information of Microsoft Access (Office) CommandBars contained in an *.mdb. I could use Microsoft.Office.Interop.Access for this; however, these PIA assemblies are tied to specific Office versions. Therefore, to be version independent, I do it the late-bound way through C#'s dynamic type. I.e., I have no references to Microsoft Office specific assemblies. The price for this is that the access to the command bars is now weakly typed.

This approach works well, except when I try to access the custom button images. This is a condensed version of my real code to illustrate the problem:

dynamic access =  Activator.CreateInstance(Type.GetTypeFromProgID("Access.Application", true));
// Starts an Access XP (2002) process in my case, but could be any version.

foreach (dynamic commandBar in access.CommandBars) {
    if (!commandBar.BuiltIn) { // Only my menus and toolbars.
        foreach (dynamic control in commandBar.Controls) {
            if (control.Type == (int)MsoControlType.msoControlButton && !control.BuiltInFace) {
                string caption = control.Caption; // Works.
                stdole.IPictureDisp picture = control.Picture; // <==== Throws exception! ====
                // ...
            }
        }
    }
}

Calling the getter of the CommandBarButton.Picture property throws this exception:

Catastrophic failure (Exception from HRESULT: 0x8000FFFF (E_UNEXPECTED))

at System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo) at System.Dynamic.ComRuntimeHelpers.CheckThrowException(Int32 hresult, ExcepInfo& excepInfo, UInt32 argErr, String message)
at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)
at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)
at MyApplication.MyMethod in MyApplication.cs:line 64

How can I get the picture avoiding this exception?

Note, this question is not about converting the IPictureDisp object to a System.Drawing.Image object. I already have a solution for this. Also, it makes no difference whether picture is typed as object, dynamic, or IPictureDisp. The property does exist, otherwise I would get the exception 'System.__ComObject' does not contain a definition for 'Picture'.

It is a .NET Framework 4.0 Windows Forms project compiled for x86 (32-bit).


Edit: Switching to Primary Interop Assemblies (PIA) allows strong typing, but does not solve the problem. The exception persists.

Meanwhile I have read more about PIAs. Since .NET Framework 4.0 you can tweak the properties of the assembly reference to make them version independent. Right click on References / Microsoft.Office.Interop.Access and

  • Set Embed Interop Types to True.
  • Set Specific Version to False.

Do this for References / office as well. Therefore I will be switching to PIAs instead of using dynamic.

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • "Dumb question": does the command bar control actually have a picture? Since the code is looping all the controls I just have to ask... And which version of Access is actually involved, here? A very quick look at the CommandBars.Control object (2010) doesn't reveal a `Picture` property, not even as a hidden member. A `CommandBarButton`, however, does have such a property. I think you need to determine the type and then cast to an object of the proper type. – Cindy Meister Jan 28 '20 at 17:31
  • The test `control.Type == (int)MsoControlType.msoControlButton` ensures that we have a `CommandBarButton` with such a property. I cannot test the type directly as it shows as `System.__ComObject` in the debugger. (And of course the real types are not available, since I have not referenced any Office libraries or PIA assemblies.) – Olivier Jacot-Descombes Jan 28 '20 at 17:47
  • If a property does not exist, the exception `'System.__ComObject' does not contain a definition for 'NonExistentProperty'` is thrown. – Olivier Jacot-Descombes Jan 28 '20 at 17:53
  • I'm not arguing that it finds a control of that type. I'm saying you need to create a specific object of that type and assign the control to it with a cast so that you have an object that actually has a Picture property. VBA *might* make the conceptual leap (although I wouldn't count on it in this case - I seem to remember it didn't work, but it's been more than 10 years), C# in my experience isn't that flexible. Something along the lines of: `Office.CommandBarButton cbb = (Office.CommandBarButton) control;` – Cindy Meister Jan 28 '20 at 17:53
  • Note: VBA can do it, after a quick test. You might try PInvoke, instead, if you really want to return on the more generic `Control`. – Cindy Meister Jan 28 '20 at 17:59
  • The point of my code is to NOT reference any libraries with these types, as they are bound to a specific Office version. I use `dynamic` in C# instead. Behind the scenes wrapper objects of type `System.__ComObject` are created. You can also do this in VBA by typing all the office-related objects as `Object` and removing the Office references from your project. In VBA and VB `Object` is doing the late-bound magic. In C# it's `dynamic`. – Olivier Jacot-Descombes Jan 28 '20 at 17:59
  • Trouble-shooting always needs to start with correct typing, first. If works with correct typing, that's a starting point. If it doesn't, a more useful error might be generated. But when I want real late-binding, within the Office interface (such as `WordBasic` objects which are late-bound in Word, itself) I use PInvoke. – Cindy Meister Jan 28 '20 at 18:03
  • And how would you access this property with PInvoke? See also: [Using type dynamic (C# Programming Guide)](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/types/using-type-dynamic) and [Dynamic Type in C# (GeeksforGeeks)](https://www.geeksforgeeks.org/dynamic-type-in-c-sharp/). – Olivier Jacot-Descombes Jan 28 '20 at 18:12

0 Answers0