- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
One of our apps has a C++ layer on top of FORTRAN. Lots of our C++ functions call the FORTRAN and have string arrays returned (in arguments). Currently this is done by allocating one large char variable in C++ and calling the FORTRAN, which actually returns an array of strings. We then have to take the C++ char variable and chop it back up into a 'proper' array. This takes an inordinate amount of string manipulation. Can you return an array of strings from FORTRAN as an array of strings in C++? (Can you even have arrays of character data in C++?). It would be nice, and a lot quicker and less 'dangerous' if I could avoid lots of space stripping and memcpy's....?
Link Copied
11 Replies
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Sorry for late reply, been quite busy last week; hope it would be helpful even now.
From C++'s viewpoint, CHARACTER(n):: string, CHARACTER:: string(n) and CHARACTER(n/6):: string(6) are the same thing; OTOH, from Fortran viewpoint, char string, char string[6][n/6] are the same thing; these all represent the same contiguous chunk of memory. So, it's possible to declare a (array of) string in C++ in one way and declare it in Fortran in another way; as long as they map to the same piece of memory you're OK. It's up to you to determine which treatment is easier for you in both languages. In both languages, (array of) string is treated as a starting address; length of string is relevant for fortran only (and array dimensions, if any, are not relevant). Of course, take care how to handle hidden length argument to Fortran routine and not to exceed buffer dimenstions. For example:
Now, it's not clearcut to me what exactly you're trying to achieve. Usually, it's better that the caller allocates the memory (see my recent post about programming precautions in this sense).
HTH
Jugoslav
From C++'s viewpoint, CHARACTER(n):: string, CHARACTER:: string(n) and CHARACTER(n/6):: string(6) are the same thing; OTOH, from Fortran viewpoint, char string
extern "C" void __stdcall FSUB(char* string, int nLen); ... char string[7][20]; // Or maybe [20][7] -- I always mix these FSUB(string, 7); --------------- SUBROUTINE FSUB(string) CHARACTER(*):: string(*) !Array of 20 strings 7 chars long !Or CHARACTER(140) string - one long string !Or CHARACTER(20) string(7) - aray of 7 strings 20 chars long DO i=1,20 WRITE(string(i), "(f7.3)") something END DO
Now, it's not clearcut to me what exactly you're trying to achieve. Usually, it's better that the caller allocates the memory (see my recent post about programming precautions in this sense).
HTH
Jugoslav
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Thanks for the reply Jugoslav. I don't mind doing the allocation and deletion in C++ (that is how its done currently), its the 'shape' of the string I get back that is the 'problem'. The C++ is an OLE automation layer that is typically used to return a VARIANT array of data to the outside world. So what generally happens is the OLE Automation method calls the FORTRAN which retrieves say, an array of 1500 item names of length 10 characters. Since this 'appears' in C++ as a long string, the code then memcpy's 10 char chunks from the string and puts them into a COleSafeArray using PutElement. The memory pointer is then stepped by 10 bytes. This would be repeated 1500 times to construct the variant array, which would be returned using a SafeArray.Detach(). Instead of all that messing around it would be nice to be able to do something like:
for (long i = 1; i <= 1500; i++)
{
SafeArray.PutElement(i, MyCPPStringArrayFromFORTRAN(i));
}
I try a little test app but couldn't seem to get it to work. I returned an array of 10 strings to C++, but the first element appeared to 'contain' all 10, the second 2-10, the third 3-10 etc.
Strings are far too complicated in C++!!!
for (long i = 1; i <= 1500; i++)
{
SafeArray.PutElement(i, MyCPPStringArrayFromFORTRAN(i));
}
I try a little test app but couldn't seem to get it to work. I returned an array of 10 strings to C++, but the first element appeared to 'contain' all 10, the second 2-10, the third 3-10 etc.
Strings are far too complicated in C++!!!
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
You almost get it right -- should be
i.e. C++ has a concept of "matrix of characters" rather than "array of strings".
Btw, did you know you could avoid all that C++/copy string mess -- SafeArrays are accessible from Fortran too. You should USE DFCOM, pass CSafeArray by reference, and put elements directly from Fortran using kinda:
Frankly, I've never worked with this but having taken a glance to docs plus DFCOM.f90 I think it should be simple enough.
Jugoslav
These are the right HTML tags
for (long i = 1; i <= 1500; i+=10)
{ SafeArray.PutElement[i, MyCPPStringArrayFromFORTRAN(i)); }
i.e. C++ has a concept of "matrix of characters" rather than "array of strings".
Btw, did you know you could avoid all that C++/copy string mess -- SafeArrays are accessible from Fortran too. You should USE DFCOM, pass CSafeArray by reference, and put elements directly from Fortran using kinda:
SUBROUTINE FSUB(lpSafeArray)
!DEC$ATTRIBUTES VALUE:: lpSafeArray !Not positive if this is required
INTEGER(SELECTED_PTR_KIND):: lpSafeArray
CHARACTER(10):: sArray(1500)
DO i=1,1500
j = SafeArrayPutElement(lpSafeArray, i, LOC(sArray(i)))
END DO
Frankly, I've never worked with this but having taken a glance to docs plus DFCOM.f90 I think it should be simple enough.
Jugoslav
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
D'oh! I never thought of that, but now you come to mention it - it's obvious isn't it! Thanks!!!
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
What is wrong with this? the HRESULT from the SafeArrayPutElement comes back with 'Catastrophic failure'
[C++]
void CSATESTDlg::OnOK()
{
CDialog::OnOK();
LPSTR cData;
SAFEARRAY * saResult;
SAFEARRAYBOUND rgsabounds[] = {{10, 0}};
saResult = SafeArrayCreate(VT_VARIANT, 1, rgsabounds);
cData = new char[128];
memset(&cData, ' ', 128);
//----------------
// Get the strings
//----------------
long lCount = GET_STRINGS (&saResult);
for (long i = 0; i < 10; i++)
{
HRESULT hr = SafeArrayGetElement(saResult, &i, &cData);
OutputDebugString(cData);
memset(&cData, ' ', 128);
}
}
[FORTRAN] *------------------------------------------------------------------------------ INTEGER*4 FUNCTION GET_STRINGS (lpSafeArray) *------------------------------------------------------------------------------ !DEC$ATTRIBUTES VALUE:: lpSafeArray USE DFCOM IMPLICIT NONE CHARACTER(10):: sArray(0:9) INTEGER*4 i, j INTEGER*4 lpSafeArray INTEGER*4 location *------------------------------------------------------------------------------ sArray(0) = "STRING_001" sArray(1) = "STRING_002" sArray(2) = "STRING_003" sArray(3) = "STRING_004" sArray(4) = "STRING_005" sArray(5) = "STRING_006" sArray(6) = "STRING_007" sArray(7) = "STRING_008" sArray(8) = "STRING_009" sArray(9) = "STRING_010" DO i = 0, 9 location = LOC(sArray(i)) j = SafeArrayPutElement(lpSafeArray, i, location) END DO GET_STRINGS = 10 RETURN END
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Re-looking at the docs, it seems that 2nd argument should be LOC(i); I thought that it's an ordinary index, but docs say it's a pointer to an array of indices. IIRC, SafeArrays are indexed from zero so (maybe) you should pay attention to that also.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
...also, not being familiar with OLE stuff, I don't know what kind of string should be put into a Variant; if it's C-like, you should terminate your strings with CHAR(0). (And make sure their length fits).
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
I've progressed a little - I'm now getting 'Invalid index' as my error. vis a vis the kind of string I would expect it would have to be a BSTR - so I might need to use ConvertStringtoBSTR. But it seems it's the index giving me problems...
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
I've now got my test app to report 'Bad variable type', which leads me to believe I need to use BSTRs, but if I try to use ConvertStringToBSTR I get link errors because the things I'm USEing are already defined (this being a mixed language app) - what do I do about that?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
[sigh] I recall problems with doubly defined symbols with DFNLS and DFCOM... Which ones, btw? I workarounded them by manually adding needed interfaces in my own module.
Try copying only necessary stuff from DFCOM.f90 to your file. I guess you'd need at least MultiByteToWideChar (ANSI to UNICODE conversion), (which is what MBConvertMBToUnicode) does and SysAllocString:
(The above is untested). That could be a thorny road though; I'm on a slippery ground about OLE, Variants, BSTRs & co. Anyone has a better idea? Leo?
Try copying only necessary stuff from DFCOM.f90 to your file. I guess you'd need at least MultiByteToWideChar (ANSI to UNICODE conversion), (which is what MBConvertMBToUnicode) does and SysAllocString:
INTERFACE
INTEGER FUNCTION MultiByteToWideChar(CodePage, dwFlags, szPath, cchPath, widePath, cchWide)
!DEC$ATTRIBUTES STDCALL, ALIAS: "_MultiByteToWideChar@24":: MultiByteToWideChar
INTEGER Codepage, dwFlags, cchPath, cchWide
!DEC$ATTRIBUTES REFERENCE:: szPath
CHARACTER(*) szPath
INTEGER(2) widePath(*)
END FUNCTION
END INTERFACE
INTERFACE
INTEGER*4 FUNCTION SysAllocString(unistr)
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_SysAllocString@' :: SysAllocString
INTEGER*2, INTENT(IN):: unistr(*)
END FUNCTION SysAllocString
END INTERFACE
!Convert szPath to UNICODE
CHARACTER(30):: szPath="ANSI string to convert"C
INTEGER(2):: olePath(30)
iSt = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szPath, -1, olePath, 2*SIZE(olePath))
bstr = SysAllocString(olePath)
(The above is untested). That could be a thorny road though; I'm on a slippery ground about OLE, Variants, BSTRs & co. Anyone has a better idea? Leo?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
I'm actually a chemical engineer - so I don't know much more about OLE, varaints and BSTRs than you! Thanks for your help so far anyway. I'm now wondering if I'm trying to be too complicated. The current code uses COleSafeArrays for construction purposes, but detaches the underlying VARIANT for return. Can I construct that VARIANT directly in FORTRAN if I manually null-terminate my strings and set the pointer element of the variant stucture to the (address of?) the string array?
Reply
Topic Options
- Subscribe to RSS Feed
- Mark Topic as New
- Mark Topic as Read
- Float this Topic for Current User
- Bookmark
- Subscribe
- Printer Friendly Page