Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

SafeArrays problem between Fortran and VB .Net

187 views
Skip to first unread message

R. MacDonald

unread,
Dec 7, 2005, 7:55:02 AM12/7/05
to
Hello, Group,

I have recently been working with VB .Net and now (after a ~20 year
hiatus) I am again doing some work with Fortran. (I notice things have
changed a bit.) Anyway, I am in far over my head, and am hoping someone
will be able to throw me a life-line.

I have a routine in a Fortran DLL that is invoked by a VB .Net method.
The VB method passes in a reference to a call-back routine that is
intended to provide data to the Fortran procedure. This technique seems
to work quite well provided I am only passing simple values back and
forth. However, I want to be able to pass arrays and I am having some
challenges with this.

The VB Code that I am using for testing looks like this:

...
Private Delegate Function mdgtTestInterface(, _
ByVal strAction As String, _
ByVal intActionLen As Integer, _
<MarshalAs(UnmanagedType.SafeArray)> ByRef intData() As Integer) _
As Integer
...

Public Function Execute() As aenmRunState _
Implements issExecutable.Execute
...
' This sets up the reference to the Call-Back routine.
Dim dgtTestInterface As mdgtTestInterface = _
AddressOf TestInterface
...
' This calls the Fortran routine in the DLL.
Dim intStatus As Integer = RunUnmanaged(intModelAddress, _
intaTest(0), _
dgtTestInterface)
...

Public Function TestInterface(ByVal strRequest As String, _
ByVal intRequestLength As Integer, _
ByRef intData() As Integer) As Integer
Dim strAction As String = _
strRequest.Substring(0, intRequestLength)
Select Case strAction.ToLower
Case "read dummy"
intData(0) = 5
Case Else
intData(0) = 6
End Select
Return 7
End Function


My test Fortran routine (Digital Visual Fortran Standard v5.0a) looks
like this:

Integer Function Tester( intCount, SSData)
!DEC$ ATTRIBUTES DLLEXPORT:: Splitter

Use DFCOMTY ! Declare SafeArray and BSTR interfaces
Use DFCOM
IMPLICIT NONE

INTEGER intCount
Interface
Integer Function SSData(strRequest, intData)
Integer, Pointer :: intData
Character*10 strRequest
End Function SSData
End Interface

INTEGER intElement
INTEGER intUnit
CHARACTER*13 strAction
Integer, Pointer :: iptUnits
Integer intResult
Integer intaIndex(1)

OPEN (Unit=1, File='E:\Test\Temp.log')
WRITE (1,*) 'In Tester: Count = ', intCount
bounddef(0)%Extent = 3
bounddef(0)%LBound = 1

iptUnits = SafeArrayCreate(VT_I4, 1, bounddef(0))

intaIndex(1) = 1
intUNIT = 3
intResult = SafeArrayPutElement(iptUnits, intaIndex(1),
# %Loc(intUNIT))
...
WRITE (1,*) 'In Tester: iptUnits = ', iptUnits
WRITE (1,*) 'Array count = ', intCount
WRITE (1,*) 'Array dims = ', SafeArrayGetDim(iptUnits)
strAction = 'read dummy12345'

! Pass the array to the VB Call-back (SSData).
WRITE (1,*) 'SSData = ', SSData(strAction, iptUnits)

WRITE (1,*) 'Leaving Tester: iptUnits = ', iptUnits
Tester = 2
RETURN
END

The problem is, that when the call-back (SSData) is invoked, VB returns
an error. The message is:

"Safe array of rank 65048 has been passed to
a method expecting an array of rank 1."

The contents of the log file at this point are:

In Tester: Count = 1
In Tester: iptUnits = 1900056
Array count = 1
Array dims = 1

The value 65048 is hex FE18 and the value 1900056 is hex 1CFE18.
Perhaps this is just coincidence, but it looks to me as if there is some
misalignment that is causing part of the array pointer to be interpreted
as the rank.

Can anyone see what I am doing wrong here? Even suggestions about how
to further diagnose the problem will be greatly appreciated.

Jugoslav Dujic

unread,
Dec 7, 2005, 8:18:09 AM12/7/05
to
| Hello, Group,
|
| I have recently been working with VB .Net and now (after a ~20 year
| hiatus) I am again doing some work with Fortran. (I notice things have
| changed a bit.) Anyway, I am in far over my head, and am hoping someone
| will be able to throw me a life-line.
|
| I have a routine in a Fortran DLL that is invoked by a VB .Net method.
| The VB method passes in a reference to a call-back routine that is
| intended to provide data to the Fortran procedure. This technique seems
| to work quite well provided I am only passing simple values back and
| forth. However, I want to be able to pass arrays and I am having some
| challenges with this.

Summary:

| Public Function TestInterface(ByVal strRequest As String, _
| ByVal intRequestLength As Integer, _
| ByRef intData() As Integer) As Integer

| Interface


| Integer Function SSData(strRequest, intData)
| Integer, Pointer :: intData
| Character*10 strRequest
| End Function SSData
| End Interface

| Integer, Pointer :: iptUnits


| iptUnits = SafeArrayCreate(VT_I4, 1, bounddef(0))

| WRITE (1,*) 'SSData = ', SSData(strAction, iptUnits)

By declaring that intData is an Integer, POINTER:: you tell the
compiler to pass a (C-speaking) SAFEARRAY**, but TestInterface
probably expects a SAFEARRAY*.

Another thing that bugs me (I don't have DVF 5.0) is that
INTERFACE of SafeArrayCreate (in CVF6.6C) declares return value
INTEGER(4), not INTEGER(4), POINTER. Thus, you're messing with
memory from the start.

Now, looking in MSDN, SafeArrayCreate returns a SAFEARRAY*.
That means that, in CVF interpretation, its return value is
something opaque (but containing the address of a safearray).

So, try something along these lines:

Interface
Integer Function SSData(strRequest, intData)

!DEC$ATTRIBUTES VALUE:: intData
Integer:: intData


Character*10 strRequest
End Function SSData
End Interface

INTEGER:: iptUnits

iptUnits = SafeArrayCreate(...)

WRITE (1,*) 'SSData = ', SSData(strAction, iptUnits)

I'm not sure whether !DEC$ATTRIBUTES VALUE:: intData is required,
but on the first sight it compensates one level of indirection.

| The problem is, that when the call-back (SSData) is invoked, VB returns
| an error. The message is:
|
| "Safe array of rank 65048 has been passed to
| a method expecting an array of rank 1."
|
| The contents of the log file at this point are:
|
| In Tester: Count = 1
| In Tester: iptUnits = 1900056
| Array count = 1
| Array dims = 1
|
| The value 65048 is hex FE18 and the value 1900056 is hex 1CFE18.
| Perhaps this is just coincidence, but it looks to me as if there is some
| misalignment that is causing part of the array pointer to be interpreted
| as the rank.

That seems plausible, but I don't think it's misalignment, but a
pointer/value/reference mixup overall. Note that

typedef struct FARSTRUCT tagSAFEARRAY {
unsigned short cDims; // Count of dimensions in this array.
unsigned short fFeatures; // Flags used by the SafeArray
// routines documented below.

cDims is 2-byte dimension count, and FE18 is loword of iptUnits. That
matches pretty well, doesn't it? -- you're not passing the SAFEARRAY
by reference, but an address of something instead.

--
Jugoslav
___________
www.xeffort.com

Please reply to the newsgroup.
You can find my real e-mail on my home page above.

R. MacDonald

unread,
Dec 7, 2005, 8:54:49 AM12/7/05
to
Thanks, Yugoslav,

I'm quite sure that you are correct about the problem being connected
with my inappropriate use of pointers. This is an area that is new and
mysterious to me.

I have tried your suggestions. With the !DEC$ATTRIBUTES VALUE::
declaration in place, I get a System.ExecutionEngineException on the VB
side, so apparently it doesn't like that vary much. Omitting the
declaration results in a new message:

"SafeArray can not be marshaled to this array
type because it has either nonzero lower bounds
or multiple dimensions."

Your last point ("cDims is 2-byte dimension count, and FE18 is loword of
iptUnits. That matches pretty well, doesn't it?") seems to be right on.
I will continue to experiment with this until I find the right
combination.

Cheers,
Randy

R. MacDonald

unread,
Dec 7, 2005, 9:30:40 AM12/7/05
to
Re:

> ...With the !DEC$ATTRIBUTES VALUE::

> declaration in place, I get a System.ExecutionEngineException on the VB
> side, so apparently it doesn't like that vary much. Omitting the
> declaration results in a new message:
>
> "SafeArray can not be marshaled to this array
> type because it has either nonzero lower bounds
> or multiple dimensions."
>

Well, I guess that I just needed to read this message a bit more
literally. I was assuming that it was a result of something cockeyed in
the way I was passing the array. But now I see in the .Net documentation:

"Unmanaged arrays are either COM-style safe
arrays or C-style arrays with fixed or variable
length. Safe arrays are self-describing arrays
that carry the type, rank, and bounds of the
associated array data. C-style arrays are
one-dimensional typed arrays with a fixed lower
bound of 0. The marshaling service has limited
support for both types of arrays."

I guess by "limited" they mean only one dimensional arrays with a lower
bound of zero can be used. I think most would have to agree that that
is pretty limited all right.

Anyway, setting my lower bound to zero seems to make the array
accessible to the VB side.

So thanks again for straightening me out on this, Jugoslav. Now I guess
I'll go and write a bunch of silly functions to emulate multidimensional
arrays.

Cheers,
Randy


Jugoslav Dujic

unread,
Dec 7, 2005, 9:36:41 AM12/7/05
to
R. MacDonald wrote:
| Thanks, Yugoslav,
|
| I'm quite sure that you are correct about the problem being connected
| with my inappropriate use of pointers. This is an area that is new and
| mysterious to me.

Well, it's not that complicated once you get your mind tuned to it.
(But that comes partly from experience so I wouldn't expand on it at
the moment). The way I think about it is "always think what I push
to the stack in the caller and what I pop from the stack in the callee
-- is it a value, an address, or an address of an address?."

| I have tried your suggestions. With the !DEC$ATTRIBUTES VALUE::
| declaration in place, I get a System.ExecutionEngineException on the VB
| side, so apparently it doesn't like that vary much.

On the second thought, it does seem that it isn't needed.
I'm not very familiar with full semantics of VB ByRef/ByVal.
It would probably be requred if you had

...ByVal intData() As Integer)

instead.

| Omitting the
| declaration results in a new message:
|
| "SafeArray can not be marshaled to this array
| type because it has either nonzero lower bounds
| or multiple dimensions."

In that case, this line seems to be the culprit, doesn't it?

bounddef(0)%LBound = 1

| Your last point ("cDims is 2-byte dimension count, and FE18 is loword of
| iptUnits. That matches pretty well, doesn't it?") seems to be right on.
| I will continue to experiment with this until I find the right
| combination.

--

Jugoslav Dujic

unread,
Dec 7, 2005, 9:51:15 AM12/7/05
to
R. MacDonald wrote:
| "Unmanaged arrays are either COM-style safe
| arrays or C-style arrays with fixed or variable
| length. Safe arrays are self-describing arrays
| that carry the type, rank, and bounds of the
| associated array data. C-style arrays are
| one-dimensional typed arrays with a fixed lower
| bound of 0. The marshaling service has limited
| support for both types of arrays."
|
| I guess by "limited" they mean only one dimensional arrays with a lower
| bound of zero can be used. I think most would have to agree that that
| is pretty limited all right.

Hmm. Someone recently asked me to straighten up his mixed VB/Fortran
code, and the problem was related with a *2*-D array having row-major/
column-major order mixed up. Specifically, that invoked declaring/Dim-ing
an 2D array in VB and passing it to Fortran dll:

SUBROUTINE burnup(NMIN,NMAX,NUK,N0,TS,P,XDTS,KMAX)
DOUBLE PRECISION N0(2232,2)

Declare Sub burnup Lib "burnup_dll" (ByRef NMIN As Integer, ByRef NMAX As
Integer, ByRef NUK As Integer, ByRef N0 As Double, ByRef TS As Double, ByRef P
As Double, ByRef XDTS As Double, ByRef KMAX As Short)
...
Dim N0(1, 2231) As Double
...
Call burnup(NMIN, NMAX, NUK(0, 0, 0), N0(0, 0), TS1, P1, XTDS1, KMAX1)

So, if you're able to dim your arrays in advance in VB, you probably can
pass them all the way round; I'm not an expert on VB and all that .NET
stuff but if I were you, I'd give it a few experimental tries before
"writing a bunch of silly functions to emulate multidimensional arrays."

R. MacDonald

unread,
Dec 7, 2005, 10:00:21 AM12/7/05
to
Hmmm, Yes, I might be able to do something like that. I'll certainly
give it a try. Thanks Jugoslav.

Cheers,
Randy

Steve Lionel

unread,
Dec 7, 2005, 10:34:20 AM12/7/05
to
On Wed, 07 Dec 2005 15:30:40 +0100, "R. MacDonald" <sci...@NO-SP-AMcips.ca>
wrote:

>So thanks again for straightening me out on this, Jugoslav. Now I guess
>I'll go and write a bunch of silly functions to emulate multidimensional
>arrays.

I have a VB.NET-Intel Fortran example with multidimensional arrays. See
http://softwareforums.intel.com/ids/board/message?board.id=5&message.id=16155

Steve Lionel
Software Products Division
Intel Corporation
Nashua, NH

User communities for Intel Software Development Products
http://softwareforums.intel.com/
Intel Fortran Support
http://developer.intel.com/software/products/support/

R. MacDonald

unread,
Dec 8, 2005, 4:37:34 AM12/8/05
to
Hello, Steve,

Thanks for the reference, Steve. Actually I had already found your
"Arrays" example, and it was very helpful in understanding how to
approach the whole business of passing arrays back and forth.

Unfortunately, the example is creating the arrays in .Net and then
passing them to Fortran for use. It seems as if going in the opposite
direction is a bigger problem. I.e. .Net won't "import" anything other
than a 1-D zero based array.

I don't know why this should be the case, since all of the required
information is available in the array descriptor that is passed from the
Fortran side. Oh well... I've worked in MS development environments
long enough to have accepted frustration as a way of life. Sigh...

But thank you very much for the web link to your samples. Even though I
had seen the "Array" example previously there are several other useful
examples there that I had not seen.

One of these seems to deal with handling run-time errors from the
Fortran DLL. This is definitely an area that I will have to
investigate, because at the moment any error that occurs in the DLL
causes the VB .Net application to crash quite silently with no
indication as to why it is disappearing.

Except for a common interface, the DLLs will all be provided by other
parties, so it is clearly going to be a problem if the application
crashes silently without even indicating which DLL was responsible.

So I will take a closer look at this GETEPTRS example. Thanks again.

Cheers,
Randy


Steve Lionel wrote:
...

Steve Lionel

unread,
Dec 8, 2005, 11:55:56 AM12/8/05
to
On Thu, 08 Dec 2005 10:37:34 +0100, "R. MacDonald" <sci...@NO-SP-AMcips.ca>
wrote:

>Thanks for the reference, Steve. Actually I had already found your

>"Arrays" example, and it was very helpful in understanding how to
>approach the whole business of passing arrays back and forth.
>
>Unfortunately, the example is creating the arrays in .Net and then
>passing them to Fortran for use. It seems as if going in the opposite
>direction is a bigger problem. I.e. .Net won't "import" anything other
>than a 1-D zero based array.

I haven't tried this myself, but I don't see why you can't build the SAFEARRAY
structure, fill it in as needed, and return it to the VB routine. It's all in
the declarations on the VB side, I'd imagine. This is certainly NOT my area
of expertise, though I've learned a lot by developing/porting the examples.

Anyway, this is a good issue to bring up in our user forum (link below). I
encourage you to do so.

0 new messages