仕事で RtdServer を使用して、リアルタイムの Excel オートメーション アドインを C# で作成する仕事を任されました。私は Stack Overflow で見つけた知識に大きく依存していました。私が学んだことすべてを結びつける方法を文書化することによって、感謝の意を表すことにしました。Kenny Kerr のExcel RTD サーバー: 最小限の C# 実装の記事は、私が始めるのに役立ちました。Mike RosenblumとGovertのコメントが特に役に立ちました。
3 に答える
(以下で説明するアプローチの代わりに、Excel-DNA の使用を検討する必要があります。Excel -DNA を使用すると、登録不要の RTD サーバーを構築できます。COM 登録には管理者権限が必要であり、インストールに問題が生じる可能性があります。正常に動作します。)
RtdServer を使用して C# でリアルタイム Excel オートメーション アドインを作成するには:
1) Visual Studio で C# クラス ライブラリ プロジェクトを作成し、次のように入力します。
using System;
using System.Threading;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;
namespace StackOverflow
{
public class Countdown
{
public int CurrentValue { get; set; }
}
[Guid("EBD9B4A9-3E17-45F0-A1C9-E134043923D3")]
[ProgId("StackOverflow.RtdServer.ProgId")]
public class RtdServer : IRtdServer
{
private readonly Dictionary<int, Countdown> _topics = new Dictionary<int, Countdown>();
private Timer _timer;
public int ServerStart(IRTDUpdateEvent rtdUpdateEvent)
{
_timer = new Timer(delegate { rtdUpdateEvent.UpdateNotify(); }, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
return 1;
}
public object ConnectData(int topicId, ref Array strings, ref bool getNewValues)
{
var start = Convert.ToInt32(strings.GetValue(0).ToString());
getNewValues = true;
_topics[topicId] = new Countdown { CurrentValue = start };
return start;
}
public Array RefreshData(ref int topicCount)
{
var data = new object[2, _topics.Count];
var index = 0;
foreach (var entry in _topics)
{
--entry.Value.CurrentValue;
data[0, index] = entry.Key;
data[1, index] = entry.Value.CurrentValue;
++index;
}
topicCount = _topics.Count;
return data;
}
public void DisconnectData(int topicId)
{
_topics.Remove(topicId);
}
public int Heartbeat() { return 1; }
public void ServerTerminate() { _timer.Dispose(); }
[ComRegisterFunctionAttribute]
public static void RegisterFunction(Type t)
{
Microsoft.Win32.Registry.ClassesRoot.CreateSubKey(@"CLSID\{" + t.GUID.ToString().ToUpper() + @"}\Programmable");
var key = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(@"CLSID\{" + t.GUID.ToString().ToUpper() + @"}\InprocServer32", true);
if (key != null)
key.SetValue("", System.Environment.SystemDirectory + @"\mscoree.dll", Microsoft.Win32.RegistryValueKind.String);
}
[ComUnregisterFunctionAttribute]
public static void UnregisterFunction(Type t)
{
Microsoft.Win32.Registry.ClassesRoot.DeleteSubKey(@"CLSID\{" + t.GUID.ToString().ToUpper() + @"}\Programmable");
}
}
}
2) プロジェクトを右クリックし、[追加] > [新しい項目...] > [インストーラー クラス] を選択します。コード ビューに切り替えて、次のように入力します。
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace StackOverflow
{
[RunInstaller(true)]
public partial class RtdServerInstaller : System.Configuration.Install.Installer
{
public RtdServerInstaller()
{
InitializeComponent();
}
[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
public override void Commit(IDictionary savedState)
{
base.Commit(savedState);
var registrationServices = new RegistrationServices();
if (registrationServices.RegisterAssembly(GetType().Assembly, AssemblyRegistrationFlags.SetCodeBase))
Trace.TraceInformation("Types registered successfully");
else
Trace.TraceError("Unable to register types");
}
[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
public override void Install(IDictionary stateSaver)
{
base.Install(stateSaver);
var registrationServices = new RegistrationServices();
if (registrationServices.RegisterAssembly(GetType().Assembly, AssemblyRegistrationFlags.SetCodeBase))
Trace.TraceInformation("Types registered successfully");
else
Trace.TraceError("Unable to register types");
}
public override void Uninstall(IDictionary savedState)
{
var registrationServices = new RegistrationServices();
if (registrationServices.UnregisterAssembly(GetType().Assembly))
Trace.TraceInformation("Types unregistered successfully");
else
Trace.TraceError("Unable to unregister types");
base.Uninstall(savedState);
}
}
}
3) プロジェクトの [プロパティ] を右クリックし、次の項目をチェックします: [アプリケーション] > [アセンブリ情報...] > [アセンブリを COM 可視にしてビルド] > [COM 相互運用に登録] の順に選択します。
3.1) プロジェクトを右クリック [参照の追加...] > [.NET] タブ > [Microsoft.Office.Interop.Excel]
4) ソリューションのビルド (F6)
5) エクセルを実行します。[Excel のオプション] > [アドイン] > [Excel アドインの管理] > [自動化] に移動し、[StackOverflow.RtdServer] を選択します。
6) セルに「=RTD("StackOverflow.RtdServer.ProgId",,200)」と入力します。
7) 指を交差させて、うまくいくことを願っています!
タイマー スレッドから UpdateNotify を呼び出すと、最終的に奇妙なエラーが発生したり、Excel から切断されたりします。
UpdateNotify() メソッドは、ServerStart() を呼び出す同じスレッドからのみ呼び出す必要があります。RTDServer のヘルプには記載されていませんが、COM の制限です。
修正は簡単です。DispatcherSynchronizationContext を使用して ServerStart を呼び出すスレッドをキャプチャし、それを使用して UpdateNotify への呼び出しをディスパッチします。
public class RtdServer : IRtdServer
{
private IRTDUpdateEvent _rtdUpdateEvent;
private SynchronizationContext synchronizationContext;
public int ServerStart( IRTDUpdateEvent rtdUpdateEvent )
{
this._rtdUpdateEvent = rtdUpdateEvent;
synchronizationContext = new DispatcherSynchronizationContext();
_timer = new Timer(delegate { PostUpdateNotify(); }, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
return 1;
}
// Notify Excel of updated results
private void PostUpdateNotify()
{
// Must only call rtdUpdateEvent.UpdateNotify() from the thread that calls ServerStart.
// Use synchronizationContext which captures the thread dispatcher.
synchronizationContext.Post( delegate(object state) { _rtdUpdateEvent.UpdateNotify(); }, null);
}
// etc
} // end of class
RTDサーバーの前の2つの回答に従うと、うまくいきました。ただし、Excel x64 を実行している x64 マシンで問題が発生しました。私の場合、プロジェクトの「ターゲット プラットフォーム」を x64 に切り替えるまで、Excel は常に #N/A を表示していました。