Community
cancel
Showing results for 
Search instead for 
Did you mean: 
Highlighted
Beginner
380 Views

fortran calling C, stdcall and cdecl

The following interface works when the function I am calling is in a C DLL built with a cdecl calling convention.  What do I change if the DLL is replaced with one having a stdcall convention?

        function TA_GetFeatureValue(handle, featureName, lpValueStr, cchValue) bind(c, name = 'TA_GetFeatureValue') result(retval) 
            import 
            integer(c_int32_t), value :: handle ! Note that Fortran doesn't have unsigned integer types 
            integer(c_short), dimension(*) :: featureName  
            integer(c_short), dimension(*) :: lpValueStr
            integer(c_int), value :: cchValue ! Note the value attribute here 
            integer(c_long) :: retval
        end function 

 

0 Kudos
20 Replies
Highlighted
Valued Contributor III
343 Views

If you're on Windows OS and the DLL and the application are built for 64-bit platform (e.g., x64 configuration in Visual Studio), then 'stdcall' should make no difference and you should not require any changes.

However, if the target platform is 32-bit (e.g., x86 configuration in Visual Studio for the C DLL, etc.), then you can try including the !DIR$ ATTRIBUTES for STDCALL - see this link.

function TA_GetFeatureValue(handle, featureName, lpValueStr, cchValue) bind(c, name = 'TA_GetFeatureValue') result(retval)
   !dir$ attributes stdcall :: TA_GetFeatureValue
..

 

0 Kudos
Highlighted
Black Belt Retired Employee
330 Views

FortranFan's suggestion of adding an ATTRIBUTES STDCALL directive is indeed the correct approach. Earlier versions of Intel Fortran did not allow this with BIND(C), but that was fixed several versions ago.

--
Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
Beginner
325 Views

I'm close, but not quite there.  I replaced the cdecl DLL with the stdcall, and tried running the program (a console program), and it worked!  But at END PROGRAM throws an exception about the stack.  No surprise there.

I added the ATTRIBUTES STDCALL statements and the program would not link, with errors like the following.

unresolved external symbol _TA_GetFeatureValue@16 referenced in function _MAIN__

I tried adding an underscore in front of the routine name like this:

!dir$ attributes stdcall :: _TA_GetFeatureValue

The program would build and run, but at exit throws the same exception about the stack.

I tried adding an alias statement such as the following, but the compiler said The BIND(C) attribute for this symbol conflicts with a DEC$ ATTRIBUTES ALIAS

!DEC$ ATTRIBUTES ALIAS:"TA_GetFeatureValue" :: TA_GetFeatureValue

What am I missing?

 

0 Kudos
Highlighted
Black Belt Retired Employee
319 Views

First thing to do here is determine the actual global name of the routine. Take the .lib from the DLL build and do:

 

dumpbin -exports mydll.lib

 

You should not need to add the leading underscore - BIND(C) will do that for you.

When you added the underscore to the ATTRIBUTES directive, you effectively removed the directive as you're now changing the attribute of a different routine.

It's possible that the new DLL isn't following the C compiler's conventions for routine naming - evidence being that you can link without it. If that is the case, you can't use BIND(C) and must revert to the older way of:

!DIR$ ATTRIBUTES STDCALL,REFERENCE,ALIAS:"_TA_GetFeatureValue" :: TA_GetFeatureValue

You will also need to add a !DIR$ ATTRIBUTES VALUE :: cchValue to the interface block.

--
Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
Beginner
313 Views

Thanks, Steve.  That did the trick.  There are of course other routines, and once I gave all necessary variables the VALUE attribute, it works great.

I've attached a dumpbin.  It shows only two lines containing GetFeatureValue.

         15    E 00023464 GetFeatureValue
         36   23 00023272 TA_GetFeatureValue

In Dependency Walker I see these two along with a third; _TAGetFeatureValue@16.  Does this mean the DLL isn't following the C compiler's conventions for routine naming?

0 Kudos
Highlighted
Black Belt Retired Employee
302 Views

Now I'm confused. You earlier reported an error:

unresolved external symbol _TA_GetFeatureValue@16 referenced in function _MAIN__

yet that exact symbol is in the export library:

87   56 00023272 _TA_GetFeatureValue@16
--
Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
Beginner
292 Views

It's a mystery to me, too.  

It may not be important, but I mis-stated what was in the dumpbin.  I should have said:

         15    E 00023464 GetFeatureValue
         36   23 00023272 TA_GetFeatureValue
         66   41 00023464 _GetFeatureValue@12
         87   56 00023272 _TA_GetFeatureValue@16

What I now have for a typical interface block is as follows.  

        function TA_GetFeatureValue(handle, featureName, lpValueStr, cchValue) result(retval)
            !DIR$ ATTRIBUTES STDCALL,REFERENCE,ALIAS:"_TA_GetFeatureValue" :: TA_GetFeatureValue
			!DIR$ ATTRIBUTES VALUE :: handle, cchValue
            import 
            integer(c_int32_t), value :: handle 
            integer(c_short), dimension(*) :: featureName   
            integer(c_short), dimension(*) :: lpValueStr   
            integer(c_int), value :: cchValue ! Note the value attribute here 
            integer(c_long) :: retval ! HRESULT is typedef long 
        end function 

 So far it works.  Is this the right way?

0 Kudos
Highlighted
Black Belt Retired Employee
282 Views

I would be more comfortable if you tacked on the @16 to the ALIAS, but then it should have worked before with just the STDCALL directive and BIND(C). 

--
Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
New Contributor I
273 Views

I have been using BIND(C) to access COMMON blocks in C++ in an x86 program. I had never needed to include the STDCALL stuff. Was I lucky? or this is a different situation?

I. Konuk

0 Kudos
Highlighted
Black Belt Retired Employee
267 Views

Different. STDCALL affects procedure calls on 32-bit only. It has to do with who pops the argument list off the stack. With STDCALL, the called routine does it in the RET instruction; with C, the caller does it. This is why you get stack corruption if you mix them.

--
Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
Beginner
227 Views

Steve - do you have any ideas why the STDCALL directive was not adequate?  Could it have something to do with the 4 different flavors of the routine identified by the dumpbin?  Should I report back to the developer of this DLL that there is something odd with it?

0 Kudos
Highlighted
Black Belt Retired Employee
224 Views

If I may be brutally honest, I don't believe you tested the STDCALL directive correctly. The evidence from the dump of the exports shows that the symbol you reported as missing is actually there. Perhaps you linked to the wrong library - I don't know. That there is something "odd with the DLL" would be far down my list.

If you can attach a ZIP of a reproducible test case (Fortran source and export LIB - don't need the DLL), I'd be happy to take a look.

--
Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
Beginner
210 Views

Being honest is not brutal. 

As I reported earlier, the following gives me unresolved external linker error.

            !DIR$ ATTRIBUTES STDCALL :: TA_GetFeatureValue

Whereas the following gets rid of the linker error, but when the program finishes running I got an error about corrupting the stack.  But Steve said earlier an underscore should not be required because BIND C should do that.

            !DIR$ ATTRIBUTES STDCALL :: _TA_GetFeatureValue

Anyhow, today I'm not getting the error about the stack, but I still need to add the underscore. 

In the attached zip, open LimeMod stdcall.f90 and go to line 46.  If the underscore is removed, I get unresolved external.  With the underscore, the code builds and runs, but strangely doesn't corrupt the stack like it did for me a few days ago.

 

0 Kudos
Highlighted
Black Belt Retired Employee
200 Views

Ok, I see we have a misunderstanding here. It doesn't help that the compiler isn't alerting you about it.

You have:

        function TA_GetFeatureValue(handle, featureName, lpValueStr, cchValue) result(retval) bind(c, name = 'TA_GetFeatureValue')
            !DIR$ ATTRIBUTES STDCALL :: _TA_GetFeatureValue

What you did here was tell the compiler that a routine named _TA_GetFeatureValue is STDCALL. Great, except that's not the name of the routine you're declaring an interface to. The result is that this directive is silently ignored.

I constructed a minimal example to demonstrate this. Here's the result of building with the directive the way you had it:

test.obj : error LNK2019: unresolved external symbol _TA_GetFeatureValue referenced in function _MAIN__
test.exe : fatal error LNK1120: 1 unresolved externals

(not linking to a library)

and here's with the underscore in the directive removed:

test.obj : error LNK2019: unresolved external symbol _TA_GetFeatureValue@16 referenced in function _MAIN__
test.exe : fatal error LNK1120: 1 unresolved externals

Note that in both cases the compiler added the leading underscore, and with the correct name in the directive also added the @16 for STDCALL.

Another thing I see in the dumpbin output is that the C++ compiler (I think that's what built the library) has both _TA_GetFeatureValue and _TA_GetFeatureValue@16 aliased to the same entry point. So it should not matter which of these you call - as long as the caller knows to apply the STDCALL mechanism. When you incorrectly add the leading underscore to the procedure name in the directive, the compiler thinks it is using the C mechanism and the stack gets corrupted by popping an extra 16 bytes from the stack.

The insidious thing about stack corruption is that it isn't always immediately obvious - it all depends on what happens after that.

Now I am going to link my test program to your library (with the errant underscore removed.) Let's see what I get:

test.obj : error LNK2019: unresolved external symbol _TA_GetFeatureValue@16 referenced in function _MAIN__

So that's what you see. Fine. Now let's look at the dumpbin.txt you included:

Dump of file turboactivate.DLL

File Type: DLL

  Section contains the following exports for TurboActivate.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
         106 number of functions
         106 number of names
...
         87   56 00023272 _TA_GetFeatureValue@16

Looks reasonable. But wait.. it's a dump of the DLL, not the .LIB. What do we get if we dump the .LIB? It's short so I'll include the whole thing:

Dump of file turboactivate.lib

File Type: LIBRARY

     Exports

       ordinal    name

                  ?DeleteProductKey@@YAXI@Z (void __cdecl DeleteProductKey(unsigned int))
                  ?GetPPDetails@@YAJPAUPartialProductDetails@@@Z (long __cdecl GetPPDetails(struct PartialProductDetails *))
                  ?SetProgressDelegate@@YAXP6AXH@Z@Z (void __cdecl SetProgressDelegate(void (__cdecl*)(int)))
                  _Activate
                  _ActivateEx
                  _ActivateFromFile
                  _ActivationRequestToFile
                  _ActivationRequestToFileEx
                  _CheckAndSavePKey
                  _Deactivate
                  _DeactivationRequestToFile
                  _ExtendTrial
                  _GetCurrentProduct
                  _GetExtraData
                  _GetFeatureValue
                  _GetPKey
                  _IsActivated
                  _IsDateValid
                  _IsGenuine
                  _IsGenuineEx
                  _IsProductKeyValid
                  _PDetsFromPath
                  _SetCurrentProduct
                  _SetCustomActDataPath
                  _SetCustomProxy
                  _TA_Activate
                  _TA_ActivateFromFile
                  _TA_ActivationRequestToFile
                  _TA_CheckAndSavePKey
                  _TA_Cleanup
                  _TA_Deactivate
                  _TA_DeactivationRequestToFile
                  _TA_ExtendTrial
                  _TA_GenuineDays
                  _TA_GetExtraData
                  _TA_GetFeatureValue
                  _TA_GetHandle
                  _TA_GetPKey
                  _TA_GetVersion
                  _TA_IsActivated
                  _TA_IsDateValid
                  _TA_IsGenuine
                  _TA_IsGenuineEx
                  _TA_IsProductKeyValid
                  _TA_PDetsFromByteArray
                  _TA_PDetsFromPath
                  _TA_SetCustomActDataPath
                  _TA_SetCustomProxy
                  _TA_SetTrialCallback
                  _TA_TrialDaysRemaining
                  _TA_UseTrial
                  _TA_UseTrialVerifiedFromFile
                  _TA_UseTrialVerifiedRequest
                  _TrialDaysRemaining
                  _UseTrial

What's missing? My guess is that this is a 64-bit .LIB, naturally missing all of the STDCALL entry points. Because the DLL itself is 32-bit, you would not notice the mismatch on running.

The solution is to 1) not put a leading underscore on the procedure name in the directive and 2) link against the correct export library.

--
Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
Valued Contributor III
196 Views

What you did here was tell the compiler that a routine named _TA_GetFeatureValue is STDCALL. Great, except that's not the name of the outine you're declaring an interface to. The result is that this directive is silently ignored.

Hmmm not very user friendly. It begs the question  why the STDCALL needs a name at all when in a bind(c name= is specified. In gfortran the stdcall decl is specified for the actual Fortran name rather the the alias which also makes more sense.

0 Kudos
Highlighted
Black Belt Retired Employee
191 Views

I'm not being clear here, it seems.

The STDCALL directive applies to the Fortran procedure name, NOT the alias. You are changing an attribute of the name as referenced in your Fortran code. This should be the same as gfortran.

When you use BIND(C, NAME=), the standard says that any "decoration" of the name should match what the "companion C processor" would do. And it does - MSVC adds the leading underscore and the @16 suffix when you tell it that a function is __stdcall.

You need the STDCALL in Fortran because this affects the generated code. The default (for both ifort and MSVC) is the "C" mechanism, where the caller pops the stack after return. STDCALL has the called procedure do the popping, but this then means that the called procedure has to know exactly how many bytes to pop. To help avoid mismatches, the convention is to append @n to the procedure name, where n is the number of bytes.

What you are seeing is that MSVC is ALSO emitting a name without the suffix - and Intel Fortran does this too. Why? Fortran does it because that allows you to declare a name simply EXTERNAL and pass it as an argument. I am not sure why MSVC is doing it as well.

I forgot to note that you made the same error with the directive for several other procedures you declared in this source.

As for why the compiler doesn't warn you that you gave a STDCALL attribute to a name you never used - doing so would probably annoy other users who include a lot of declarations for routines not currently used.

I also retract my guess that the .LIB is 64-bit. Rather, I think it is from a CDECL (C mechanism) version of the DLL, as the names all have leading underscores, and the convention on x64 is that this is omitted.

--
Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
Beginner
182 Views

Why do forum posts not have identifying numbers on them anymore?

Steve, I believe you said in your minimal example, linking failed both with & without the underscore.  I only get a linker error without the underscore, and with it links ok.

I can confirm that the .lib file is indeed 32 bit. I have another that is 64 bit.  A dumpbin from the 32 has underscores, and the 64 doesn't.

The creator of these files gives me DLL files in cdecl and stdcall flavors.  But only one .lib file.  Not knowing any better, I took it for granted that one .lib file works for both.  If stdcall requires a different .lib file, let me know and I'll ask them for it.

If you are wondering why I want to use the stdcall flavor.  I call this DLL from Excel visual basic as well as fortran.  So I want to use the same DLL for both.

0 Kudos
Highlighted
Black Belt Retired Employee
176 Views

My first two examples didn't link to any library - I was just showing the names referenced.

You must use the .LIB that corresponds to the DLL you are using. If you do that, and correct the directives in your source, it should work fine.

--
Steve (aka "Doctor Fortran") - https://stevelionel.com/drfortran
0 Kudos
Highlighted
Beginner
168 Views

I found a google page that shows Intel's MKL has DLL's on both flavors, and the LIB files are different.  So I have asked my supplier for a proper stdcall-LIB file. 

If I got it right (always doubtful), a "correct" directive will not have an underscore at the front of the name, and BIND(C, name='xyz') will be in the declaration, and I won't need any !DIR$ ATTRIBUTES VALUE statements.

0 Kudos