在 Visual Basic 中调用 NAG 函数

Visual Basic (VB) 4/5/6 版与 Visual Basic for Applications (VBA) 有许多相似之处,所以大部分 VBA 调用 NAG 函数的相关资讯都能直接应用上。 尤其是关数组与字符串的处理。不论是 VBA 或者 VB 都没有提供好的机制传递调用的参数到 DLL。 为了解决这样的问题,如果您是 Fortran 的使用者,我们建议采用以下的方式。一般说来,函数会以下面的方式呈现:

       SUBROUTINE N_C05ADF(A,B,EPS,ETA,X,IFAIL)
         DOUBLE PRECISION A
         DOUBLE PRECISION B
         DOUBLE PRECISION EPS
         DOUBLE PRECISION ETA
         DOUBLE PRECISION F
         EXTERNAL F
         DOUBLE PRECISION X
         INTEGER IFAIL

         CALL C05ADF (A,B,EPS,ETA,F,X,IFAIL)
         END

VB 与 VBA 会透过另一个函数接口调用 NAG 函数,此函数与 NAG 函数会有相同的参数,但是会移除掉部分的参数。在此例中,用户提供的函数 F 将被移除。新的函数能够简单的调用原有 NAG 函数, 并会以固定的名称提供调用参数。我们以下列的例子进行说明:

第一步使用 CVF 编译器编译用户函数 F,第一步产生 F 的 DLL:

 DF /c /Op /Ox /Tf F.FOR

  链结函数:

  LINK @F.LNK

  其中 F.LNK 为文件

  /DLL
  /OUT:F.DLL
     F.OBJ
  /EXPORT:_F@4
  /EXPORT:_F=_F@4
  /EXPORT:F=_F@4

先忽略其他细节,除了 F.DLL 外,还会产生 F.IMP 与 F.LIB 两个文件。将会在之后使用到此档:

 编译函数:

  DF /c /Op /Ox /Tf N_C05ADF.FOR

  链接函数:

  LINK @NAG.LNK

  其中 NAG.LNK 为文件:

  /DLL
  /OUT:NAG.DLL
     f.lib
     DLL20DDS.LIB
     N_C05ADF.OBJ
  /EXPORT:_N_C05ADF@24
  /EXPORT:Alias=_N_C05ADF@24

请注意 /EXPORTS 所输出的算法库文件。其说明 DLL 中所使用的符号。此例中说明了在 DLL20DDS DLL 中的 C05ADF 函数,以及在 F.DLL 中的 F 函数。这些资讯是在被建立前就写到文件中的。 透过 /EXPORT 告诉 DLL 输出。请注意这些符号是以下底线构成的,函数名称会跟着 @ 符号以及数字。 此数字包含了被 CVF 编译器使用到的参数数量与型别。此称为 '装饰名'。也可使用公用程序 DUMPBIN 取得:

       DUMPBIN  /SYMBOLS N_C05ADF.OBJ

因为装饰名称可能在使用上相当繁琐,所以可以透过 /EXPORT 第二行指定的 'Alias' 来取代。

VB5 提供了传递函数参数的机制 - AddressOf。若您想要了解其如何运作,请参考以下示例,示例将说明如何在 VB5 中使用 E04UCF 函数:


  Option Explicit
  Private Sub Command1_Click( )

  'Starting values of variables
      x(1) = 1
      x(2) = 5
      x(3) = 5
      x(4) = 1


 'Bounds of the variables
      bl(1) = 1:    bu(1) = 5
      bl(2) = 1:    bu(2) = 5
      bl(3) = 1:    bu(3) = 5
      bl(4) = 1:    bu(4) = 5

  'Coefficients of the general linear constraint
      a(1, 1) = 1
      a(1, 2) = 1
      a(1, 3) = 1
      a(1, 4) = 1

  'Bounds of the general linear constraint
      bl(n + 1) = -1E+21:    bu(n + 1) = 20

  'Bounds of the nonlinear constraints
      bl(n + nclin + 1) = -1E+21:     bu(n + nclin + 1) = 40
      bl(n + nclin + 2) = 25:         bu(n + nclin + 2) = 1E+21

  'Solve the problem
      Label1.Caption = ""
      ifail = +1
      mode=1
      Call X04ACF(NOUT, "\results.lis", 12, mode, ifail)
      'X04ACF opens the file \results.lis and saless it to Fortran channel 6
      'Note that the "12" refers to the length of the string "\results.lis"
      Call E04UEF("NoList", 6)
      Call E04UEF("Major Print Level=9", 19)
      Call E04UEF("Minor Print Level=9", 19)
      Call E04UEF("Der=0", 5)

      ifail = +1
      Call E04UCF(n, nclin, ncnln, lda, ldcj, ldr, a(1, 1), bl(1), bu(1), _
      AddressOf confun, AddressOf objfun, iter, istate(1), c(1), cjac(1, 1), _
      clamda(1), objf, objgrd(1), r(1, 1), x(1), iwork(1), liwork, work(1), _
          lwork, iuser, user, ifail)

      Label1.Caption = "E04UCF Example Program Results" + Chr(13) + _
      "------------------------------------------------------" + Chr(13) + _
      "Value of objective function:"+Str(objf)+Chr(13)+"x1 = " + Str(x(1)) + _
       Chr(13)+"x2 = "+Str(x(2)) + Chr(13) + "x3 = " + Str(x(3)) + Chr(13) + _
          "x4 = " + Str(x(4)) + Chr(13) + "IFAIL = " + Str(ifail)
      Call X04ADF(NOUT, ifail)
      'X04ADF closes the file on channel 6 (NOUT)
  End Sub


Option Explicit
  Option Base 1

  Global Const _
      n As Long = 4, _
      nclin As Long = 1, _
      ncnln As Long = 2, _
      NOUT = 6, _
      ldr As Long = n, _
      lda As Long = nclin, _
      ldcj As Long = ncnln, _
      liwork As Long = 100, _
      lwork As Long = 1000

 Public _
      a(lda, n) As Double, _
      bl(n + nclin + ncnln) As Double, _
      bu(n + nclin + ncnln) As Double, _
      iter As Long, _
      istate(n + nclin + ncnln) As Long, _
      c(ncnln) As Double, _
      cjac(ldcj, n) As Double, _
      clamda(n + nclin + ncnln) As Double, _
      objf As Double, _
      objgrd(n) As Double, _
      r(ldr, n) As Double, _
      x(n) As Double, _
      iwork(liwork) As Long, _
      work(lwork) As Double, _
      iuser As Long, _
      user As Double, _
      ifail As Long, _
      mode As Long

  #If Win32 Then
      Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
      ByRef hpvDest As Any, ByVal hpvSource As Any, ByVal cbCopy As Long)
  #Else
      Declare Sub CopyMemory Lib "KERNEL" Alias "hmemcpy" ( _
      ByRef hpvDest As Any, ByVal hpvSource As Any, ByVal cbCopy As Long)
  #End If

  Declare Sub E04UCF Lib "DLL20DD.DLL" _
      (n As Long, nclin As Long, ncnln As Long, lda As Long, ldcj As Long, _
       ldr As Long, a As Double, bl As Double, bu As Double, ByVal confun As Any, _
       ByVal objfun As Any, iter As Long, istate As Long, con As Double, _
       cjac As Double, clamda As Double, objf As Double, objgrd As Double, _
       r As Double, x As Double, iwork As Long, liwork As Long, work As Double, _
       lwork As Long, iuser As Long, user As Double, ifail As Long)

  Declare Sub E04UEF Lib "DLL20DD.DLL" (ByVal optparam As String, _
       ByVal stringlength As Long)

  Declare Sub X04ACF Lib "DLL20DD.DLL" (NOUT As Long, ByVal file As String, _
       ByVal length_string As Long, mode As Long, ifail As Long)

  Declare Sub X04ADF Lib "DLL20DD.DLL" (NOUT As Long, ifail As Long)

  Sub objfun(mode As Long, nb As Long, ByVal ptr_x As Long, objf As Double, _
          ByVal ptr_objgrd As Long, nstate As Long, iuser As Long, user As
  Double)

'Routine to evaluate objective function and its 1st derivatives.

  '..Local variables.
    Dim x(n) As Double       'values for which the function is to  be evaluated
    Dim objgrd(n) As Double  'first derivatives of the function
    Dim address As Long      'Address to fetch/store data
    Dim i As Integer         'counter

  'Copy elements pointed to by ptr_x into local array x.
    For i = 1 To n
        address = ptr_x + 8 * (i - 1)
        Call CopyMemory(x(i), address, 8)
    Next i

  'Calculate value of objective function if necessary.
    If mode = 0 Or mode = 2 Then objf = x(1)*x(4)*(x(1) + x(2) + x(3)) + x(3)

  'Calculate values of 1st derivatives if necessary.
      If mode = 1 Or mode = 2 Then
          objgrd(1) = x(4) * (2 * x(1) + x(2) + x(3))
          objgrd(2) = x(1) * x(4)
          objgrd(3) = x(1) * x(4) + 1
          objgrd(4) = x(1) * (x(1) + x(2) + x(3))
          'Copy values to the memory pointed to by ptr_objgrd.
          For i = 1 To n
              address = ptr_objgrd + 8 * (i - 1)
              Call CopyMemory(ByVal (address), VarPtr(objgrd(i)), 8)
          Next i
      End If
  End Sub

  Sub confun(mode As Long, ncnlnb As Long, nb As Long, ldcjb As Long, _
       ByVal ptr_needc As Long, ByVal ptr_x As Long, ByVal ptr_c As Long, _
       ByVal ptr_cjac As Long, nstate As Long, iuser As Long, user As Double)

'Routine to evaluate the nonlinear constraints and their 1st derivatives.

  '..Local variables..
      Dim x(n) As Double
      'Values for which the constraints are to be evaluated.
      Dim needc(ncnln) As Long
      'If needc(i)>0 then data on the ith constraint is required.
      Dim con As Double         'Value of a constraint.
      Dim cjac(n) As Double     'First derivatives of a constraint.
      Dim address As Long       'Address to fetch/store data.
      Dim store As Double       'Data element to store.
      Dim i, j As Integer       'Counters.

  'copy elements pointed to by ptr_x into local array x
      For i = 1 To n
          address = ptr_x + 8 * (i - 1)
          Call CopyMemory(x(i), ByVal address, 8)
      Next i

  'copy elements pointed to by ptr_needc into local array needc
      For i = 1 To ncnln
          address = ptr_needc + 4 * (i - 1)
          Call CopyMemory(needc(i), address, 4)
      Next i

      If nstate = 1 Then
          'First call to CONFUN. Set all Jacobian elements to zero.
          'Note that this will only work when 'Derivative Level = 3'
          '(the default; see Section 11.2).
          store = 0
          For i = 1 To ncnln
              For j = 1 To n
                  address = ptr_cjac + 8 * ncnln * (j - 1) + 8 * (i - 1)
                  Call CopyMemory(ByVal (address), VarPtr(store), 8)
              Next
          Next
      End If

      If needc(1) > 0 Then
          If mode = 0 Or mode = 2 Then
              'Value of first constraint is required
              con = x(1) * x(1) + x(2) * x(2) + x(3) * x(3) + x(4) * x(4)
              Call CopyMemory(ByVal (ptr_c), VarPtr(con), 8)
          End If
          If mode = 1 Or mode = 2 Then
              'Derivatives of first constraint required
              cjac(1) = 2 * x(1)
              cjac(2) = 2 * x(2)
              cjac(3) = 2 * x(3)
              cjac(4) = 2 * x(4)
              For i = 1 To n
                  address = ptr_cjac + 8 * ncnln * (i - 1)
                  Call CopyMemory(ByVal (address), VarPtr(cjac(i)), 8)
              Next
          End If
      End If

      If needc(2) > 0 Then
          If mode = 0 Or mode = 2 Then
              'Value of second constraint is required
              con = x(1) * x(2) * x(3) * x(4)
              Call CopyMemory(ByVal (ptr_c + 8), VarPtr(con), 8)
          End If
          If mode = 1 Or mode = 2 Then
              'Derivatives of second constraint required
              cjac(1) = x(2) * x(3) * x(4)
              cjac(2) = x(1) * x(3) * x(4)
              cjac(3) = x(1) * x(2) * x(4)
              cjac(4) = x(1) * x(2) * x(3)
              For i = 1 To n
                  address = ptr_cjac + 8 * ncnln * (i - 1) + 8
                  Call CopyMemory(ByVal (address), VarPtr(cjac(i)), 8)
              Next
          End If
      End If
  End Sub

此处要特别注意的是用户提供的函数 CONFUN 与 OBJFUN 都是 VB 程序。是以 AddressOf 操作数传递给 DLL。 他们必须要以此方式构成,才可以让 Fortran DLL 取得相关资讯。API 函数 CopyMemory 也必须使用到。 第一个参数是目标地址,第二个参数是要被复制的来源地址,第三个参数是复制的位数。将 Fortran 函数的计算结果取回是需要取得 VB 变量的地址的。此需要透过 VB 未被载明的功能 - VarPtr 函数。 我们建议的这个方式广泛的被程序人员所使用,因此不太可能会移除。然而,我们无法保证此状况。