none
関数の引数にVBAの多次元配列を渡す方法 RRS feed

  • 質問

  • C++で作成したdll内の関数をVBAから呼んで利用しようと思っています。

    そのとき関数の引数として多次元配列を渡すにはどうすれば良いのでしょうか?

     

    1次元配列では下記のようにすることで成功しています。

     

    ■C++のコード

     

    #include "stdafx.h"
    #define DllExport extern "C" __declspec (dllexport)

    DllExport double WINAPI kakezan(double *a);

    DllExport double WINAPI kakezan(double *a)
    {
     return a[0]*a[1]*a[2];
    }

    ■VBAのコード

     

    Private Declare Function kakezan Lib _
       "C:\test.dll" Alias "_kakezan@4" _
       (ByRef a As Double) As Double
    Sub Test()
        Dim a(1 To 3) As Double
        a(1) = 3
        a(2) = 4
        a(3) = 5
        MsgBox kakezan(a(1))
    End Sub

    2007年10月25日 8:32

回答

  • 2次元限定とかなら次のような感じで。

    何次元でも、というのは私は知りません。

    VB2005ですが。

     

    コード ブロック

    extern "C" __declspec(dllexport) void __stdcall func(int FirstLength,int SecondLength,int *data1)
    {
        int i, j;

        for (i = 0; i < FirstLength; i++)
        {
            for (j = 0; j < SecondLength; j++,data1++)
            {
                *data1 = *data1+i*SecondLength+j;
            }
        }
    }

     

     

    コード ブロック

    Declare Sub func Lib "dlltest.dll" Alias "_func@12" (ByVal length1 As Integer, ByVal length2 As Integer, ByRef data As Integer)

     

     

    コード ブロック

    Dim d(5, 4) As Integer

    Dim i, j As Integer
    For i = 0 To 5
        For j = 0 To 4
            d(i, j) = i * j
        Next
    Next

            func(6, 5, d(0, 0))

            Dim dt As New DataTable
            dt.Columns.Add("0")
            dt.Columns.Add("1")
            dt.Columns.Add("2")
            dt.Columns.Add("3")
            dt.Columns.Add("4")

            For i = 0 To 5
                Dim dr As DataRow = dt.NewRow
                For j = 0 To 4
                    dr.Item(j) = d(i, j)
                Next
                dt.Rows.Add(dr)
            Next

            Me.DataGridView1.DataSource = dt
            Me.DataGridView1.Refresh()

     

     

    2007年10月25日 13:22

すべての返信

  • 2次元限定とかなら次のような感じで。

    何次元でも、というのは私は知りません。

    VB2005ですが。

     

    コード ブロック

    extern "C" __declspec(dllexport) void __stdcall func(int FirstLength,int SecondLength,int *data1)
    {
        int i, j;

        for (i = 0; i < FirstLength; i++)
        {
            for (j = 0; j < SecondLength; j++,data1++)
            {
                *data1 = *data1+i*SecondLength+j;
            }
        }
    }

     

     

    コード ブロック

    Declare Sub func Lib "dlltest.dll" Alias "_func@12" (ByVal length1 As Integer, ByVal length2 As Integer, ByRef data As Integer)

     

     

    コード ブロック

    Dim d(5, 4) As Integer

    Dim i, j As Integer
    For i = 0 To 5
        For j = 0 To 4
            d(i, j) = i * j
        Next
    Next

            func(6, 5, d(0, 0))

            Dim dt As New DataTable
            dt.Columns.Add("0")
            dt.Columns.Add("1")
            dt.Columns.Add("2")
            dt.Columns.Add("3")
            dt.Columns.Add("4")

            For i = 0 To 5
                Dim dr As DataRow = dt.NewRow
                For j = 0 To 4
                    dr.Item(j) = d(i, j)
                Next
                dt.Rows.Add(dr)
            Next

            Me.DataGridView1.DataSource = dt
            Me.DataGridView1.Refresh()

     

     

    2007年10月25日 13:22
  • はなはなはなさん、回答ありがとうございました。

     

    教えていただいたコードを検証しましたところ、

    Dim dt As New DataTable
    でエラーが起きました。

    「DataTable」というのはVBAでは見かけないのですがいかがでしょうか?

     

    それはさておき、今のところ2次元配列を想定していて、VBAの2次元配列を縦に並んだ1次元配列としてCに渡せるということはわかったので、それで十分満足しています。

    ありがとうございました。

    2007年10月26日 10:50
  • あ~、私のコードはVB2005のサンプルです。

    VBAは書く気がしなくて。ww

    2007年10月26日 12:00
  • 追加で関連する質問をさせてください。

     

    CからVBへ配列を戻すにはどうすればいいのでしょうか。

    2次元の配列をVBからCへ渡し、何らかの演算をした後、CからVBへ2次元の配列を返そうと思っています。

    具体的には行列の転置や積、逆行列、固有値を求めるものです。

     

    ご助言いただけると幸いです。

    2007年10月29日 4:40
  • 自分で調べた結果、課題が解決しました。

    VBAから2次元の配列を渡したときにC内では1次元の配列上で計算されても、VBAに戻ってきたときにVBA上では2次元の配列としてデータが格納されています。参照渡しになっていることもキーです。

     

    なお、VBA上の2次元配列とC上の1次元配列の対応は、a(1 to 3, 1 to 3)の場合、

    a(1, 1) = a[0]

    a(2, 1) = a[1]

    a(3, 1) = a[2]

    となります。

     

     

    ■C++のコード

     

    #include "stdafx.h"
    #define DllExport extern "C" __declspec (dllexport)

    DllExport bool WINAPI kakezan(double *a, double *b);{

    DllExport bool WINAPI kakezan(double *a, double *b)
    {
     b[1] = a[0] * a[1] * a[2];
     return TRUE;
    }

     

    ■VBAのコード

     

    Private Declare Function kakezan Lib "C:\Test.dll" Alias "_kakezan@8" (ByRef a As Double, ByRef b As Double) As Boolean

     

    Sub Test()
       Dim a(1 To 3, 1 To 3) As Double
       Dim b(1 To 3, 1 To 3) As Double
       a(1, 1) = 3
       a(2, 1) = 4
       a(3, 1) = 5
       MsgBox kakezan(a(1, 1), b(1, 1))
       MsgBox b(2, 1)
    End Sub

    2007年10月30日 7:14
  • 追記です。

     

    このやり方では、VBAからC++へデータが渡せても、C++からVBAへのデータ渡しが不完全なようで、不規則にエラーが発生し、実装できません。なぜC++からVBAへのデータ渡しがうまくいかないのかはわかりませんが、「ヒープが壊れている可能性があります。」と言われてしまいます。データが壊れてしまってはどうしようもありません。

     

    このようなエラーがあるから「SAFEARRAY」なんてのがあるんでしょうね。

    2007年11月6日 2:24
  • どういうコードを書いているのか、教えてください。

    2007年11月6日 3:04
  • 下記はテストコードです。このコードではうまくいきましたが、実装したコードでは、実行するたびにエラーが起こり、しかも毎回エラーが起こる行が異なりました。さらに、dll内の関数を呼んでいない行でもエラーが発生しました。エラー内容は先ほども申し上げましたが、「ヒープが壊れている可能性があります。」とのことです。

    どうぞよろしくお願いします。

     

    ■C++のコード

     

    #include "stdafx.h"
    #include "math.h"     //数学関数

    #define DllExport extern "C" __declspec (dllexport)

     

    DllExport int WINAPI Sweep(long intSize, long intCs, long intCe, double *adbDat, double *adbRes);
    DllExport int WINAPI Transpose(long intR, long intC, double *adbDat, double *adbRes);
    DllExport int WINAPI MMult(long intR1, long intC1, long intC2, double *adbDat1, double *adbDat2, double *adbRes);
    DllExport bool WINAPI MInverse(long intN, double *adbDat, double *adbInv, double *adbDet);

    //掃き出し
    DllExport int WINAPI Sweep(long intSize, long intCs, long intCe, double *adbDat, double *adbRes)
    {
       long i, j, k, n;
     
     n = intSize;
     for(i=intCs-1;i<intCe;i++) {
      if(fabs(adbDat[i + n * i]) > 0.0000000001) {
       for(j=0;j<intSize;j++) {
        for(k=0;k<intSize;k++) {
         if(j == i && k == i) {
                      adbRes[j + n * k] = 1 / adbDat[i + n * i];
         } else if (j == i) {
                      adbRes[j + n * k] = adbDat[j + n * k] / adbDat[i + n * i];
         } else if (k == i) {
                      adbRes[j + n * k] = -adbDat[j + n * k] / adbDat[i + n * i];
                   } else {
                      adbRes[j + n * k] = adbDat[j + n * k] - adbDat[j + n * i] * adbDat[i + n * k] / adbDat[i + n * i];
         }
        }
       }
       for(j=0;j<intSize;j++) {
        for(k=0;k<intSize;k++) {
                   adbDat[j + n * k] = adbRes[j + n * k];
        }
       }
      }
     }

     return 0;
    }

    //転置
    DllExport int WINAPI Transpose(long intR, long intC, double *adbDat, double *adbRes)
    {
       long i, j;
     
     for(i=0;i<intC;i++) {
      for(j=0;j<intR;j++) {
             adbRes[i + intC * j] = adbDat[j + intR * i];
          }
       }

       return 0;
    }

    //積
    DllExport int WINAPI MMult(long intR1, long intC1, long intC2, double *adbDat1, double *adbDat2, double *adbRes)
    {
       long i, j, k;
     
     for(i=0;i<intR1;i++) {
      for(j=0;j<intC2;j++) {
       for(k=0;k<intC1;k++) {
                adbRes[i + intC2 * j] += adbDat1[i + intR1 * k] * adbDat2[k + intC2 * j];
             }
          }
       }

       return 0;
    }

    //行列式と逆行列
    DllExport bool WINAPI MInverse(long intN, double *adbDat, double *adbInv, double *adbDet)
    {
       const double cstEPS = 0.0000000001;
       long i, j, k, n, intMax;
       double dbPivot, dbWKR, dbDet;
     
     n = intN;
       dbDet = 1;

       //初期化
     for(i=0;i<intN;i++) adbInv[i + n * i] = 1;

       //逆行列
     for(i=0;i<intN;i++) {
          intMax = i;
      if(intMax < intN - 1) {
       for(j=i+1;j<intN;j++) {
        if(fabs(adbDat[j + n * i]) > fabs(adbDat[intMax + n * i])) {
         intMax = j;
        }
       }
       if(intMax != i) {
        for(j=0;j<intN;j++) {
         Swap(adbDat[i + n * j], adbDat[intMax + n * j]);
         Swap(adbInv[i + n * j], adbInv[intMax + n * j]);
        }
       }
      }
          dbPivot = adbDat[i + n * i];
          if(fabs(dbPivot) < cstEPS) {
             return FALSE;
          } else {
             dbDet *= dbPivot;
          }
          if(intMax != i) dbDet = -dbDet;
      for(j=0;j<intN;j++) {
             adbDat[i + n * j] /= dbPivot;
             adbInv[i + n * j] /= dbPivot;
          }
      for(j=0;j<intN;j++) {
       if(j != i) {
                dbWKR = adbDat[j + n * i];
        for(k=0;k<intN;k++) {
         adbDat[j + n * k] -= adbDat[i + n * k] * dbWKR;
                   adbInv[j + n * k] -= adbInv[i + n * k] * dbWKR;
                }
             }
          }
       }
     
    adbDet[0] = dbDet;
     return TRUE;
    }

     

     

    ■VBAのコード

     

    Private Declare Function TransposeC Lib "C:\test.dll" _
       Alias "_Transpose@16" (ByVal r As Long, ByVal c As Long, _
       ByRef dat As Double, ByRef res As Double) As Double
    Private Declare Function MMultC Lib "C:\test.dll" _
       Alias "_MMult@24" (ByVal r1 As Long, ByVal c1 As Long, _
       ByVal c2 As Long, ByRef dat1 As Double, _
       ByRef dat2 As Double, ByRef res As Double) As Double
    Private Declare Function InverseC Lib "C:\test.dll" _
       Alias "_MInverse@16" (ByVal n As Long, _
       ByRef dat As Double, ByRef inv As Double, _
       ByRef det As Double) As Boolean

    Sub Test2()
      
       Dim bool As Boolean
       Dim i As Long
       Dim j As Long
       Dim k As Long
       Dim n As Long
       Dim a() As Double
       Dim b() As Double
       Dim c() As Double
       Dim d(0) As Double
      
       n = 10
       ReDim a(1 To n, 1 To n)
      
       For i = 1 To 1000
          For j = 1 To n
             For k = 1 To n
                a(j, k) = Int(Rnd * 10) * 1E+100
             Next
          Next
         
          ReDim b(1 To n, 1 To n)
          TransposeC n, n, a(1, 1), b(1, 1)
          ReDim c(1 To n, 1 To n)
          MMultC n, n, n, a(1, 1), b(1, 1), c(1, 1)
          ReDim b(1 To n, 1 To n)
          MMultC n, n, n, c(1, 1), a(1, 1), b(1, 1)
          ReDim a(1 To n, 1 To n)
          bool = InverseC(n, b(1, 1), a(1, 1), d(0))
       Next
      
    End Sub

     

    2007年11月6日 9:05
  • ざっとしか見てませんが、Cでintで宣言したものがVBAのLongに相当します。

    CのLongはVBAにはないんじゃないかな。

    2007年11月6日 10:21
  • Cのintは、VBAでinteger、CのlongもVBAのlongでいいのでは?

     

    参考

    [XL]DLL へ引数を渡す方法

    http://support.microsoft.com/kb/402464/ja

    2007年11月7日 6:42
  • int ・・・ (in many cases) 32-bit signed integer.

    2007年11月7日 7:31
  • じゃんぬねっとさん、ご返信ありがとうございます。

     

    そうでしたね。intは環境依存ですよね。

     

    ところで、さきほどの参考URLの中に、参照渡しでは64KBまでしか配列は渡せないとあるので、これが原因だと思いました。

     

    「数値配列の場合は「参照渡し」であれば、配列全体を一つの引数として引き渡すことが可能です。ただし、「値渡し」で引き渡すことや文字列配列全体を一つの引数として引き渡すことはできませんので注意が必要です。(中略)また、Huge 配列 (64 KB よりも大きな配列) の場合は最初の 64 KB だけにしかアクセスすることができませんので注意が必要です。」

     

    参考

    [XL]DLL へ引数を渡す方法

    http://support.microsoft.com/kb/402464/ja

     

     

    2007年11月7日 8:28
  • 私がやる限り、64kbの壁はないですね。(Excel2003で検証)

    VB2ぐらいの頃にそういう制約があったかもしれないが。

    2007年11月7日 12:38
  • それから、資料を読む時は、いつごろ書かれた記事なのか(更新日)、どの製品についての記事なのか、

    ということにも注意しましょう。

    実はExcel5を使っているのならば(ないと思うが)その旨、明記すべきです。

    2007年11月7日 15:19
  • なるほど。たしかに更新日に気を配っていませんでした。

    ちなみにExcel 2003を使用しています。

     

    あと、少し外れているかもしれませんが、このような記述もありました。

     

    [VB5] Visual Basic 5.0 から呼び出し可能な DLL の作成例
    http://support.microsoft.com/kb/410837/ja

     

    数値配列の引き渡し および C++ 言語での記述の サンプル
    Visual Basic 5.0 では数値型の 1 次元の配列に関しては比較的簡単に配列全体を引き渡すことができます。 ただし DLL 内での配列のインデックスのチェックは一切行われないので、 オーバーラン させると ページ 違反などの アプリケーション エラー が発生するので注意が必要です。 なお、 数値の 1 次配列以外の配列はこの方法では引き渡せません、偶然動作する場合もありますが動作保証の対象外となります。

    2007年11月8日 4:19
  • 今現在、Microsoftが保証しているかどうかは知りませんが、

    現実問題として私のところでは問題なく動いていますね。(VB6ベース)

    それにしても偶然とはね。

     

    2007年11月8日 8:26