8

我在 Bloomberg API 方面表现出色(它使用像 =BDP("MSEquipment","ask") 这样的简单调用)。我还有一个 C# 应用程序,它打开一个使用 Bloomberg API 的 excel 文件(通过互操作)。我在这里读到,当您通过互操作加载 excel 时,不会加载插件。我什至尝试过使用那里建议的代码。但是,这似乎只适用于 XLL 和 XLAM 文件,而且 Bloomberg 似乎也使用了未加载的 DLL 文件。有没有办法让所有这些插件通过互操作加载?

4

4 回答 4

12

我假设,如果您手动启动 excel,加载项会正确加载。

Process p = Process.Start("excel.exe");如果是这样,您可以通过调用然后使用获取 Interop Object 的AccessibleObjectFromWindow方法来获取加载了 BB-Addins 的 Excel Interop Object。

可以在此处找到有关如何执行此操作的示例。


快速演练

我决定展示我通过加载的bloomberg插件获取Excel.Interop.Application实例的步骤。也许有人看到这个并发现它很有用,或者知道我在哪里遗漏了一些东西以获得更优雅的解决方案。

我假设您的客户端上安装了彭博终端,并且您可以从 excel 中请求参考和历史数据(BDP 和 BDH)。否则这可能不适合你......

最简单的方法行不通

var excel = new Microsoft.Office.Interop.Excel.Application();
excel.Workbooks.Open(filePath);
excel.Run("RefreshAllStaticData");

Run()会抛出一个COMException

无法运行宏“RefreshAllStaticData”。该工作簿中的宏可能不可用,或者所有宏都可能被禁用。

手动加载插件不起作用

var excel = new Microsoft.Office.Interop.Excel.Application();
var addIn = ex.AddIns.Add(filename);
addIn.Installed = true;
Console.WriteLine(addIn.IsOpen);

错误的

但是,如果通过单击 Windows 任务栏来启动 Excel,则加载项会很好地加载......所以我尝试通过启动可执行文件“excel.exe”来启动 excel。

使用 Process.Start("excel.exe") 有效!!

使用该类启动 excelSystem.Diagnostics.Process会加载bloomberg 插件。(我可以看到Bloomberg-RibbonTab。)

现在我需要的是那个 excel 进程的互操作对象。我可以使用上面的例子得到它

我的实现

//From http://blogs.officezealot.com/whitechapel/archive/2005/04/10/4514.aspx
public class ExcelInteropService
{
    private const string EXCEL_CLASS_NAME = "EXCEL7";

    private const uint DW_OBJECTID = 0xFFFFFFF0;

    private static Guid rrid = new Guid("{00020400-0000-0000-C000-000000000046}");

    public delegate bool EnumChildCallback(int hwnd, ref int lParam);

    [DllImport("Oleacc.dll")]
    public static extern int AccessibleObjectFromWindow(int hwnd, uint dwObjectID, byte[] riid, ref Microsoft.Office.Interop.Excel.Window ptr);

    [DllImport("User32.dll")]
    public static extern bool EnumChildWindows(int hWndParent, EnumChildCallback lpEnumFunc, ref int lParam);

    [DllImport("User32.dll")]
    public static extern int GetClassName(int hWnd, StringBuilder lpClassName, int nMaxCount);

    public static Microsoft.Office.Interop.Excel.Application GetExcelInterop(int? processId = null)
    {
        var p = processId.HasValue ? Process.GetProcessById(processId.Value) : Process.Start("excel.exe");
        try
        {
            return new ExcelInteropService().SearchExcelInterop(p);
        }
        catch (Exception)
        {
            Debug.Assert(p != null, "p != null");
            return GetExcelInterop(p.Id);
        }
    }

    private bool EnumChildFunc(int hwndChild, ref int lParam)
    {
        var buf = new StringBuilder(128);
        GetClassName(hwndChild, buf, 128);
        if (buf.ToString() == EXCEL_CLASS_NAME) { lParam = hwndChild; return false; }
        return true;
    }

    private Microsoft.Office.Interop.Excel.Application SearchExcelInterop(Process p)
    {
        Microsoft.Office.Interop.Excel.Window ptr = null;
        int hwnd = 0;

        int hWndParent = (int)p.MainWindowHandle;
        if (hWndParent == 0) throw new ExcelMainWindowNotFoundException();

        EnumChildWindows(hWndParent, EnumChildFunc, ref hwnd);
        if (hwnd == 0) throw new ExcelChildWindowNotFoundException();

        int hr = AccessibleObjectFromWindow(hwnd, DW_OBJECTID, rrid.ToByteArray(), ref ptr);
        if (hr < 0) throw new AccessibleObjectNotFoundException();

        return ptr.Application;
    }
}

现在我可以使用下面的代码行来获取一个Excel.Interop.Application加载了bloomberg插件的实例!

var excel = ExcelInteropService.GetExcelInterop();
excel.Workbooks.Open(filename);
excel.Run("RefreshAllStaticData");
// works "like a charm"

我希望这可以节省我浪费的时间,如果有人有更优雅的解决方案并想要分享它,将不胜感激。

于 2012-11-27T16:23:24.733 回答
2

我意识到自从提出这个问题以来已经有一段时间了。不过,我想分享我的经验,因为我遇到了同样的问题。一个简单的解决方案是按照https://blogs.msdn.microsoft.com/accelerating_things/2010/09/16/loading-excel-add-ins-at-runtime/中的建议进行操作,首先将 .Installed 设置为 false,然后为真:

this._application = new Application
                {
                    EnableEvents = true,
                    Visible = true
                };

                //Load Bloomberg Add-In
                for (var i = 1; i <= this._application.AddIns.Count; i++)
                {
                    var currentAddIn = this._application.AddIns.Item[i];

                    if (currentAddIn.Name != "BloombergUI.xla") continue;

                    currentAddIn.Installed = false;
                    currentAddIn.Installed = true;
                }
于 2018-05-04T07:11:48.857 回答
1

我能够使用NetOffice获得类似的结果。此特定功能将检索最后启动的 excel 进程实例,如果确实存在,则创建一个新实例。从那里你可以自由地打开/创建一个新的工作簿。

使用 netoffice 和这种方法的好处是它不依赖于特定于版本的互操作 DLL,如果您处理 NetOffice 对象,那么所有 excel COM 对象都会被正确清理。

using Xl = NetOffice.ExcelApi;    
private static Xl.Application GetOrStartExcel(bool started = false)
{
    Xl.Application application = null;

    try
    {
        object nativeProxy = System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");
        application = new Xl.Application(null, nativeProxy);
        return application;
    }
    catch (Exception ex)
    {
        if (!started) Process.Start("excel.exe");
        Thread.Sleep((int) TimeSpan.FromSeconds(1).TotalMilliseconds);
        return GetOrStartExcel(true);
    }
}

用法:

using (var excel = GetOrStartExcel())
{
    //Do work with excel object
}
于 2014-06-03T15:40:31.877 回答
0

如果要将 DLL 加载到 Excel 插件中,则必须使用 RegAsm 注册此 DLL 文件。并检查HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\Excel\AddIns是否注册成功。

于 2012-11-27T01:13:17.037 回答