No definition found for GetActiveObject from System.Runtime.InteropServices.Marshal C#
Asked Answered
U

4

20

I'm trying to connect to a running Excel instance, but when I try to use the following code snippet:

using Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;

public Application StartExcel()
{
    Application instance = null;
    try
    {
        instance = (Application)Marshal.GetActiveObject("Excel.Application");
    }
    catch (COMException ex)
    {
        instance = new ApplicationClass();
    }

    return instance;
}

I found similar snippets on the internet, but when I compile this code I get the following error:

error CS0117: 'Marshal' does not contain a definition for 'GetActiveObject'.

I do not know why, because I have the following NuGet packages: - Microsoft.Office.Interop.Excel - System.Runtime.InteropServices

Understate answered 19/9, 2019 at 11:57 Comment(1)
You used the wrong project template to get started. It can't be a .NETCore project, must be .NETFramework. Which is fine, you need Windows anyway to run Excel and this code. Give .NETCore v3.0 at least a couple of months to get stable.Vaudeville
S
6

This is available only .Net Framework but not for .Net Core . Please check the .Net project type .

https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.getactiveobject?view=netframework-4.8&viewFallbackFrom=netcore-2.2

Saccule answered 19/9, 2019 at 12:19 Comment(2)
I'm quite new to the C world in total, does that mean I have to create a new project or ...?Understate
Yes . You may have to create a new project targeting .Net Framework instead of .Net Core .Saccule
G
38

We need to pull the GetActiveObject(String ProgID) function from the source code GitHub.Microsoft

Create your own class, for example - Marshal2

And use as before

Marshal2.GetActiveObject(progID);

Source code

public static class Marshal2
{
    internal const String OLEAUT32 = "oleaut32.dll";
    internal const String OLE32 = "ole32.dll";

    [System.Security.SecurityCritical]  // auto-generated_required
    public static Object GetActiveObject(String progID)
    {
        Object obj = null;
        Guid clsid;

        // Call CLSIDFromProgIDEx first then fall back on CLSIDFromProgID if
        // CLSIDFromProgIDEx doesn't exist.
        try
        {
            CLSIDFromProgIDEx(progID, out clsid);
        }
        //            catch
        catch (Exception)
        {
            CLSIDFromProgID(progID, out clsid);
        }

        GetActiveObject(ref clsid, IntPtr.Zero, out obj);
        return obj;
    }

    //[DllImport(Microsoft.Win32.Win32Native.OLE32, PreserveSig = false)]
    [DllImport(OLE32, PreserveSig = false)]
    [ResourceExposure(ResourceScope.None)]
    [SuppressUnmanagedCodeSecurity]
    [System.Security.SecurityCritical]  // auto-generated
    private static extern void CLSIDFromProgIDEx([MarshalAs(UnmanagedType.LPWStr)] String progId, out Guid clsid);

    //[DllImport(Microsoft.Win32.Win32Native.OLE32, PreserveSig = false)]
    [DllImport(OLE32, PreserveSig = false)]
    [ResourceExposure(ResourceScope.None)]
    [SuppressUnmanagedCodeSecurity]
    [System.Security.SecurityCritical]  // auto-generated
    private static extern void CLSIDFromProgID([MarshalAs(UnmanagedType.LPWStr)] String progId, out Guid clsid);

    //[DllImport(Microsoft.Win32.Win32Native.OLEAUT32, PreserveSig = false)]
    [DllImport(OLEAUT32, PreserveSig = false)]
    [ResourceExposure(ResourceScope.None)]
    [SuppressUnmanagedCodeSecurity]
    [System.Security.SecurityCritical]  // auto-generated
    private static extern void GetActiveObject(ref Guid rclsid, IntPtr reserved, [MarshalAs(UnmanagedType.Interface)] out Object ppunk);

}
Grillwork answered 29/12, 2020 at 17:32 Comment(3)
I don't know why this is not the accepted answer. This works. I tried it with .NET 5.0Bosley
Worked for me. But I had to open the application as the Administrator.Factory
Works like a charm. I failed myself at the Marshal GetObject in LinqPad7 but this is the solution!Sickness
S
6

This is available only .Net Framework but not for .Net Core . Please check the .Net project type .

https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.getactiveobject?view=netframework-4.8&viewFallbackFrom=netcore-2.2

Saccule answered 19/9, 2019 at 12:19 Comment(2)
I'm quite new to the C world in total, does that mean I have to create a new project or ...?Understate
Yes . You may have to create a new project targeting .Net Framework instead of .Net Core .Saccule
S
2

Actually, BindToMoniker solves the issue for obtaining an Interop Application Object. It's available in 4.8 .Net Framework and 6.0/.Net Core. Using this api is what I converged to.

From my understanding there are few issues:

  1. Identifying the app to Marshel to
  2. Obtaining the Marshal Object for interop

GetActiveObject, while it works, from other's questions/answers will only give the active object (which becomes a problem when there is more than on process running for the app in question), and only works in 4.8 .Net Framework.

When I used GetActiveObject I did the following:

acApp = new Application();
acApp = (Application)Marshal.GetActiveObject("Access.Application");

While this worked fine, I needed to establish a two process model as the core app with the data to send to the Interop App was built in 6.0. One process built with 4.8 providing the Application control I desired and an RPC conduit to another 6.0 built process. The other process built with 6.0 RPC'd the desired data to be used in the Application Control. This worked fine, albeit with working set size increased, delays for the RPC transfers, and left the issue when there was more than one Application running unaddressed.

The other technique I found was to instead use:

  acApp = new Application();
  acApp = (Application)Marshal.BindToMoniker(_conf.Moniker);

where "_conf.Moniker" was a string sent to the process identifying the file used by the presently running application. BindToMoniker eliminated the need to use 4.8 entirely. I sent this string on the command line from the app, which started the Net6.0 app. The VBA I used to pass the filename used within the Application looked like:

AppToRun = "the60Netapp" & " -f " & """" & CurrentProject.FullName
 ProcessID = Shell(AppToRun, 0)

I saved the ProcessID to enable me to later end the process. The BindToMoniker helped resolve both the identification issue and how to keep the .Net entirely 6.0 (or what ever Framework is desired).

Spancel answered 2/9, 2022 at 21:41 Comment(0)
S
0

The way I solved this problem was to craft a two process solution. One using .Net 6, the other .Net Framework 4.8. I tried using other Marshal Object routines, though decided the future folks would have all manner of development issues.

The app using 4.8 Framework calls the GetActiveObject, and operates with the Office Apps VBA Functions. The app using .Net 6 RPCs to the 4.8 app to handle the calls to the Office Apps.

Personally, was worried about the Invoke solution, as it seemed too tightly coupled to the OS dlls. The downside of the two app with two frameworks solution is the working set for the full solution is larger than the Invoke solution.

Spancel answered 27/8, 2022 at 1:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.