C#でExcelワークシート関数を自作する

# はじめに

Excel-DNAやVBAを使用せずに、C#のみで自作のワークシート関数(ユーザー定義関数、User-defined functions、UDFs)を作成する方法です。VBAマクロで作成した関数と異なり、高速で処理をすることができます。  

[Excel から使うマネージDLL を作る。 | supermab's blog](https://supermab.com/wp/excel-%E3%81%8B%E3%82%89%E4%BD%BF%E3%81%86%E3%83%9E%E3%83%8D%E3%83%BC%E3%82%B8dll-%E3%82%92%E4%BD%9C%E3%82%8B%E3%80%82/)  
![udfnibai.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/50eaa199-b251-a89e-74ba-6e586ebc33d8.gif)　　

# 作成方法

1. Visual Studio を起動します。  

2. 「新しいプロジェクトの作成」を押します。  
![udf01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/99a6bbe3-9bb3-fc1c-444e-2da94a967a3f.png)  

3. 「C#」の「クラスライブラリ(.NET Framework)」を選択して「次へ」を押します。  
![udf02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/9840edfc-4eee-4d4a-162a-bc119fcac7d2.png)  

1. プロジェクト名として「MyAddin」を入力し、フレームワークとして「.NET Framework4.8」を選択後「作成」を押します。ここでは「MyAddin」と設定しましたが他の名前でも構いません。  

4.  コードを記載します。  
![udf03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/95ba95bf-3cf3-74e9-781c-a9dcdc18bc5f.png)　　
   
    ```csharp:Class1.cs
    using System;
    using System.Runtime.InteropServices;
    using Microsoft.Win32;
    
    namespace MyAddin
    {
        //インターフェース
        [ComVisible(true)]
        [Guid("71E317FB-04C2-48CF-BEB4-2B4452116B7B")]//GUID（変更必要）
        [InterfaceType(ComInterfaceType.InterfaceIsDual)]
        public interface IUdfs
        {
            //公開したい関数
            double nibai(double x);
        }
    
        //クラス
        [ComVisible(true)]
        [Guid("0679B1F1-A380-4D18-A5BB-BD5B42BF7A0B")]//GUID（変更必要）
        [ProgId("MyAddin.Udfs")]
        [ClassInterface(ClassInterfaceType.None)]
        public class Udfs : IUdfs
        {
            //公開したい関数の実装
            public double nibai(double x)
            {
                return x * 2;
            }
    
            //以下おまじない
            //Regasmでの登録時に行われる処理
            [ComRegisterFunctionAttribute]
            private static void RegisterFunction(Type type)
            {
                //CLSID\{GUID}\Programmable
                //新規作成
                Registry.ClassesRoot.CreateSubKey(@"CLSID\{" + type.GUID.ToString().ToUpper() + @"}\Programmable");
    
                //CLSID\{GUID}\InprocServer32
                //既定の値を変更：mscoree.dll→C:\Windows\system32\mscoree.dll
                RegistryKey key = Registry.ClassesRoot.OpenSubKey(@"CLSID\{" + type.GUID.ToString().ToUpper() + @"}\InprocServer32", true);
                key.SetValue("", System.Environment.SystemDirectory + @"\mscoree.dll", RegistryValueKind.String);
            }

            //Regasmでの登録解除時に行われる処理    
            [ComUnregisterFunctionAttribute]
            private static void UnregisterFunction(Type type)
            {
                //CLSID\{GUID}\Programmable
                //削除
                Registry.ClassesRoot.DeleteSubKey(@"CLSID\{" + type.GUID.ToString().ToUpper() + @"}\Programmable", false);
            }
        }
    }
    ```

1. ソースコード内に2箇所ある`//GUID（変更必要）`のコメント部のGUIDを新規作成したものに置き換えます。2箇所には別々のGUIDを新規に設定します。  
    
    **GUIDの新規作成方法**  
       「ツール」から「GUIDの作成」を選択します。  
![udf04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/ee8831fb-4c50-3449-f5c4-e6378dfcc6dc.png)  
       形式を選択して「コピー」を押しクリップボードにコピーしたら「終了」を押します。  
![udf05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/df3ae216-2349-9d2d-8161-d1a8eca2f7c7.png)  

5. プロジェクトのプロパティを開きます。  
![udf06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/4aa55893-7f7b-1316-e8b3-9099e53953cb.png)  

6. 署名を新規作成します。  
![udf07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/9b672402-80fe-2d27-a493-6509bb4d0e22.png)  

7. キーファイル名を入力して「OK」を押します。  
![udf08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/ad5801f8-4b13-2a22-e786-a8604e65faed.png)  

8. 64bit用のExcelを対象にするので構成をx64※に変更します。「Release」を選択後、「Any CPU」をプルダウンし、「構成マネージャー」を選択します。  
    ※32bit用のExcelを対象にする場合はx86  
![udf09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/79ee1d06-2171-62a2-7c37-edab13638796.png)  

9. 「アクティブ ソリューション プラットフォーム」で「新規作成」を選択します。  
![udf10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/c9627a93-a5da-4075-c0c3-3e9c2a379b01.png)  

10. 「x64」を選択して「OK」を押します。  
![udf11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/de4a9f8f-d1ce-5ef5-be02-c0caafda5707.png)  

11. 「閉じる」を押します。  
![udf12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/2b74700e-d273-5b90-7c78-b43ce01c90cb.png)  

12. ビルドします。  
![udf13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/174ee372-6901-8fbb-6ace-9ed36cc643d2.png)  


# 登録して使用する

1. 管理者権限でコマンドプロンプトを開いてRegAsmを利用して登録します。

    ```bat
    REM 以下は64bit版Excelの場合。32bitの場合はFramework64をFrameworkに変更する。
    C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe /tlb /codebase MyAddin.dll
    ```
    ![udf14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/63a44480-ace0-1026-5f3e-2be3e76cfde6.png)  

2. Excelを開いて「開発」タブ→「Excelアドイン」をクリックします。アドイン画面が出るので「オートメーション」を押します。  
![udf15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/25969763-c328-1989-97ea-1b5cfc10253e.png)  

3. 「MyAddin.Udfs」を選択して「OK」  
![udf16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/0d8ec7e5-abc1-36c7-506c-2df8d36cab92.png)  

4. 「OK」を押します。  
![udf17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/dd4adcbd-eb9b-6b86-8d56-874a3ea04de8.png)  

5. あとは関数を入力して使用します。  
![udf18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/14c1ca9d-1aff-94b0-3368-5869d7ba040b.png)  
:::note warn
通常の関数、例えばSUMであれば「S」を押した時点で関数候補としてSUMが出てきますが、今回の方法で作成したワークシート関数の場合は「n」を押しても「nibai」が候補として出てきませんでした。
:::

# 登録を解除して使用をやめる

1. 管理者権限でコマンドプロンプトを開いてRegAsmを利用して登録を解除します。

    ```bat
    REM 以下は64bit版Excelの場合。32bitの場合はFramework64をFrameworkに変更する。
    C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe /tlb /u MyAddin.dll
    ```
    ![udf19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/b02981f0-bc6f-053a-5a71-0d2b9bf59d1b.png)  

2. Excelを開いて「開発」タブ→「Excelアドイン」をクリック。「Myaddin」のチェックを外そうとするとアドインが見つからないので削除するか聞いてくるので「はい」を押します。その後「OK」を押せば終了です。  
![udf20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/e91b8f53-b546-490b-a6cc-0e36bae1f3dd.png)  

# 補足

上記はVisual Studioを立ち上げて作成しましたが、コマンドラインで作成することも可能です。  

キーペアの作成。Sn.exeの場所は環境によって異なるため[Developer Command Prompt](https://learn.microsoft.com/ja-jp/visualstudio/ide/reference/command-prompt-powershell?view=vs-2022)を立ち上げて`where sn`で確認します。  
```bat
sn -k MyAddin.snk
```
コンパイル&登録（管理者権限で実行）
```bat
REM ソースファイル名はMyAddin.csとする。
REM 以下は64bit版Excelの場合。32bitの場合はFramework64をFrameworkに変更する。
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /target:library /keyfile:MyAddin.snk /platform:x64 MyAddin.cs
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe /tlb /codebase MyAddin.dll
```
登録解除（管理者権限で実行）
```bat
REM 以下は64bit版Excelの場合。32bitの場合はFramework64をFrameworkに変更する。
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe /tlb /u MyAddin.dll
```
またPowerShellを利用することでGUIDを作成しクリップボードにコピーすることができます。
```powershell
(New-Guid).Guid | clip
```


# 参考URL

インターフェース  
* [【C#】インターフェイスの利点が理解できない人は「インターフェイスには３つのタイプがある」ことを理解しよう - Qiita](https://qiita.com/yutorisan/items/d28386f168f2f3ab166d)  
* [インターフェース - C# によるプログラミング入門 | ++C++; // 未確認飛行 C](https://ufcpp.net/study/csharp/oo_interface.html)  

COM  
* [C#でCOM In-Process Server - Qiita](https://qiita.com/EvilSpirit39/items/c959fc990d1660b77180)  
* [C#からのCOMの利用 - COMサーバ](https://ichigopack.net/win32com/com_csharp_3.html)  
* [ソフトウェアを楽しく作ろう！プログラミング言語【C#】を学ぶ | C# EXCEL・DLLを作ってVBAで使う](https://cozver.com/2022/10/10/post-2564/)  
* [COM への .NET コンポーネントの公開 - .NET Framework | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/framework/interop/exposing-dotnet-components-to-com?source=recommendations)  
* [ComRegisterFunctionAttribute クラス (System.Runtime.InteropServices) | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/api/system.runtime.interopservices.comregisterfunctionattribute?view=net-7.0)  
* [Registering Classes | Microsoft Learn](https://learn.microsoft.com/en-us/previous-versions/windows/desktop/automat/registering-classes)  

署名  
* [方法: 公開キーと秘密キーのキー ペアを作成する | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/standard/assembly/create-public-private-key-pair)  
* [方法: 厳密な名前でアセンブリに署名する | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/standard/assembly/sign-strong-name)
* [インサイド .NET Framework ［改訂版］第2回　アセンブリのアイデンティティ（1/5） - ＠IT](https://atmarkit.itmedia.co.jp/fdotnet/technology/idnfw11_02/idnfw11_02_01.html)  

GUID作成  
* [PowerShellでGUIDを生成する](https://tech.la-fra.com/2018/06/30/post-86/)  
* [New-Guid (Microsoft.PowerShell.Utility) - PowerShell | Microsoft Learn](https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/new-guid?view=powershell-7.3)