none
VBAからC#のアセンブリに配列を渡したいがエラーになる。 RRS feed

  • 質問

  • EXCEL上にあるデータ処理の一部演算部分をc#で組むべく下記の様なコードを書いてみました。
    環境はEXCEL2007、VS2008 Express C# sp1です。

    VBA側

    Sub Macro1()
    '
    ' Macro1 Macro
    '
    Dim caloc As New Myaddin.Mymath
    '
    Dim x(3) As Integer
    Dim arx As Variant
    
        x(0) = Range("A1")
        x(1) = Range("A2")
        x(2) = Range("A3")
        
        Range("B1") = caloc.test0(x(0)) 'OK
        
        Range("C1") = caloc.test1(y, UBound(y)) 'コンパイルエラー
        
        arx = Range("A1:A3").Value
    
        Range("D1") = caloc.test2(arx, UBound(arx)) 'コンパイルエラー
    
    End Sub

    C#

    using System;
    using System.Runtime.InteropServices;
    
    namespace Myaddin
    {
        [ComVisible(true)]
        [ClassInterface(ClassInterfaceType.AutoDual)]
        public class Mymath
        {
            public int test0(int x)
            {
                return x;
            }
    
            public int test1(int[] x, long length)
            {
                return x[0];
            }
    
            public int test2(object[] x, long length)
            {
                int[] inx = new int[length];
    
                Array.Copy(inx, x, length);
    
                return inx[0];
            }
        }
    }

    test0は正常に動作します。
    しかし、test1、test2については、VBAの実行時に、

    「コンパイルエラー
    関数またはインターフェイスが予約されているか、またはVisual Basicでサポートされていないオートメーションタイプが関数で使用されています」 とエラーが出てしまいます。

    ゴールは、EXCEL上の数100のデータに対して演算を施し、EXCEL上へ返却する関数を作らねばならないので、配列で授受する方法が必要です。 お知恵を拝借いたしたく宜しくお願いいたします。

    2010年4月7日 13:28

回答

  • 「VBA 配列 C# COMVisible」で検索して、次のスレッドを見つけました。
    ref キーワードを利用してみてはいかがでしょうか。

    http://hanatyan.sakura.ne.jp/logbbs1/wforum.cgi?mode=allread&no=7969&page=0


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    • 回答としてマーク yassan 2010年4月8日 18:15
    2010年4月7日 14:41
    モデレータ
  • Azulean さん

    VBAの仕様を調べてみましたらご指摘の通りで、Integer型の配列を渡すテストは解決しました。ありがとうございました!!

    C#

    using System;
    using System.Runtime.InteropServices;
    
    namespace addintest
    {
        [ComVisible(true)]
        [ClassInterface(ClassInterfaceType.AutoDual)]
        public class Mytest
        {
            public int test0(int x)
            {
                return x;
            }
    
            public int test1(ref int[] x)
            {
                int ret = 0;
    
                foreach (int i in x )
                {
                    ret = ret + x[i];
                }
                return ret;
            }
    
            public int test2(ref object[] x)
            {
                int[] inx = new int[x.Length];
    
                Array.Copy(inx, x, x.Length);
    
                return inx[0];
            }
        }
    }

    VBA

    Sub Macro1()
    '
    ' Macro1 Macro
    '
    Dim caloc As New addintest.Mytest
    Dim x(3) As Long
    Dim arx As Variant
    
        x(0) = Range("A1")
        x(1) = Range("A2")
        x(2) = Range("A3")
        
        Range("B1") = caloc.test0(x(0)) 'OK
        
        Range("C1") = caloc.test1(x) '解決!
        
        arx = Range("A1:A3").Value
    
        Range("D1") = caloc.test2(arx) 'コンパイルエラー
    
    End Sub
    • 回答としてマーク yassan 2010年4月8日 18:12
    2010年4月8日 17:21
  • 自己レスですが、VARIANT型配列を渡すtest2もC#側で受け取れました。

    但し、2つの謎を抱えたままの結果オーライのコードですので、あくまで参考として掲載しておきます。
    謎1) なぜか2次元配列になっている。 (エラーメッセージから発見!)
    謎2) インデックスが1からである。 (VBAに Option Base 0 と明示してみたが・・・)

    using System;
    using System.Runtime.InteropServices; namespace addintest { [ComVisible(true)] [ClassInterface(ClassInterfaceType.AutoDual)] public class Mytest { public int test0(int x) //OK { return x; } public int test1(ref int[] x) //OK { int ret = 0; for (int i = 0; i < x.Length;i++ ) { ret = ret + x[i]; } return ret; } public int test2(ref object x) //OK { object[,] inObj; //なぜか2次元配列で渡される int ret = 0; inObj = (object[,])x; for (int i = 1; i <= inObj.GetLength(0); i++) { ret = ret + Convert.ToInt32(inObj[i, 1]); } return ret; } } }
    • 回答としてマーク yassan 2010年4月9日 11:56
    2010年4月9日 11:56

すべての返信

  • 「VBA 配列 C# COMVisible」で検索して、次のスレッドを見つけました。
    ref キーワードを利用してみてはいかがでしょうか。

    http://hanatyan.sakura.ne.jp/logbbs1/wforum.cgi?mode=allread&no=7969&page=0


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    • 回答としてマーク yassan 2010年4月8日 18:15
    2010年4月7日 14:41
    モデレータ
  • Azuleanさん返答ありがとうございます。

    C#側追加
    public int test3(ref int[] x)
    {
         return x[0];
    }

    VBA側呼び出し
    Range("E1") = caloc.test3(x)

    にてテストしてみたところ、
    「コンパイルエラー
    型が一致しません:配列またはユーザー定義型を指定してください」
    と、メッセージは変化しましたが、実行できません。

    再確認の為、test3( int[] x)  とref を外してビルドすると、
    「コンパイルエラー
    関数または・・・・・・」
    と従来のエラーメッセージに戻ります。

    • 編集済み yassan 2010年4月8日 4:14 表示訂正
    2010年4月8日 4:10
  • 配列をひとつの object としてやり取りをして、C# の側で配列にバラさないといけないということでしょうか。

     Sending an array of doubles from Excel VBA to C# (using COM interop) - CodeProject
     http://www.codeproject.com/KB/office/arraysvbatocssv1.aspx

    2010年4月8日 9:18
  • 「コンパイルエラー
    型が一致しません:配列またはユーザー定義型を指定してください」
    と、メッセージは変化しましたが、実行できません。

    VBA は VB6 と同じで、Integer 型は 16 ビットの整数型ではないのでしょうか。(手元にヘルプがないので未確認)
    そうだとすると、C# では short 型が対応すると思います。
    あるいは C# 側で int 型にしたいのであれば、VB 側で Long 型にするかでしょうか。

    # そういった単純な問題で済むのかはわかりません。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2010年4月8日 14:17
    モデレータ
  • Azulean さん

    VBAの仕様を調べてみましたらご指摘の通りで、Integer型の配列を渡すテストは解決しました。ありがとうございました!!

    C#

    using System;
    using System.Runtime.InteropServices;
    
    namespace addintest
    {
        [ComVisible(true)]
        [ClassInterface(ClassInterfaceType.AutoDual)]
        public class Mytest
        {
            public int test0(int x)
            {
                return x;
            }
    
            public int test1(ref int[] x)
            {
                int ret = 0;
    
                foreach (int i in x )
                {
                    ret = ret + x[i];
                }
                return ret;
            }
    
            public int test2(ref object[] x)
            {
                int[] inx = new int[x.Length];
    
                Array.Copy(inx, x, x.Length);
    
                return inx[0];
            }
        }
    }

    VBA

    Sub Macro1()
    '
    ' Macro1 Macro
    '
    Dim caloc As New addintest.Mytest
    Dim x(3) As Long
    Dim arx As Variant
    
        x(0) = Range("A1")
        x(1) = Range("A2")
        x(2) = Range("A3")
        
        Range("B1") = caloc.test0(x(0)) 'OK
        
        Range("C1") = caloc.test1(x) '解決!
        
        arx = Range("A1:A3").Value
    
        Range("D1") = caloc.test2(arx) 'コンパイルエラー
    
    End Sub
    • 回答としてマーク yassan 2010年4月8日 18:12
    2010年4月8日 17:21
  • totojo さん 情報ありがとうございました。

    DLしてきてdllを手動登録(regasm) してみましたが、VBAの実行時にオートメーションがなんとか(忘れてしまいました)のエラー発生。 regasmのオプション設定の間違いかも?

    そこで、クラスライブラリのプロジェクトを新規作成してソースを組み込んで、アトリビュートを付けて正常実行するようにはなりましたが、どうも思うようには動作してなさそうです。
    といいますのも、オリジナルのコードは固定値7を返しているだけだったので、下記の様に改造してみましたところ、1しか返ってこないもので・・・

    public int callableMethodArray(object a)
    {
         double[] thisVect = LoadComObjectIntoDoubleArray(a);
     //  return 7;
         return thisVect.Length; // change yassan
    }
    VBA側もC#側も同じ型であれば、解決しましたintの配列と同様に引き渡せそうな予感しています。

    ただ、情報をいただいたコードはC#側をobject型で受けているので、test2(Variant型配列)の解決の糸口になるかもと、引き続きトライしてみようと思います。
    2010年4月8日 18:07
  • 自己レスですが、VARIANT型配列を渡すtest2もC#側で受け取れました。

    但し、2つの謎を抱えたままの結果オーライのコードですので、あくまで参考として掲載しておきます。
    謎1) なぜか2次元配列になっている。 (エラーメッセージから発見!)
    謎2) インデックスが1からである。 (VBAに Option Base 0 と明示してみたが・・・)

    using System;
    using System.Runtime.InteropServices; namespace addintest { [ComVisible(true)] [ClassInterface(ClassInterfaceType.AutoDual)] public class Mytest { public int test0(int x) //OK { return x; } public int test1(ref int[] x) //OK { int ret = 0; for (int i = 0; i < x.Length;i++ ) { ret = ret + x[i]; } return ret; } public int test2(ref object x) //OK { object[,] inObj; //なぜか2次元配列で渡される int ret = 0; inObj = (object[,])x; for (int i = 1; i <= inObj.GetLength(0); i++) { ret = ret + Convert.ToInt32(inObj[i, 1]); } return ret; } } }
    • 回答としてマーク yassan 2010年4月9日 11:56
    2010年4月9日 11:56