Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.
28988 Discussions

Best/Correct way to Dereferencing C-pointers

anthonyrichards
New Contributor III
2,161 Views
Frequently in windows programming you have a procedure which supplies a pointer to windows structure. e.g.

function MyDlgProc(hDlg, message, wParam, lParam)

where lParam may be a INTEGER(HANDLE) pointer.

In order to access the structure to which it is an address, what I find works at present is to do the following:

type (T_DRAWITEMSTRUCT) DIS ! Window DrawItem Structure...
POINTER(pDIS,DIS)
TYPE (T_RECT) RC ! Window rectangle structure...
...
pDIS=lparam ! Dereference the pointer
RC=DIS%rcItem

What is the correct, portable way to do this?

TIA

P.S.

I have seen TRANSFER and C_F_POINTER used, but am at a loss to know exactly what they do that is different.



0 Kudos
14 Replies
TimP
Honored Contributor III
2,161 Views
You ought to be able to
USE iso_c_binding

and make a Fortran standard pointer from the C pointer by c_f_pointer.
I don't expect a consensus on the answer to your question; most of the important compilers support Cray pointer well enough to consider your method portable, at least among the widespread 64-bit operating systems.
0 Kudos
Steven_L_Intel1
Employee
2,161 Views
The way you're doing it is ok - that code is inherently non-portable. But if you wanted to avoid use of language extensions, you could do something like this:

use, intrinsic :: iso_c_binding
use ifwin
type(T_DRAWITEMSTRUCT), POINTER :: DIS
...
call C_F_POINTER(TRANSFER(lParam, C_NULL_PTR), DIS)
RC=DIS%rcItem

The TRANSFER converts the address-sized lParam to an entity of type C_PTR (C_NULL_PTR is a convenient predeclared item of that type) and then C_F_POINTER makes DIS a pointer to the target.
0 Kudos
anthonyrichards
New Contributor III
2,161 Views
Thanks, Steve.

I must admit to not really understanding pointers and how they are transformed to 'normal' declared objects.
Take for example my code:

type (T_DRAWITEMSTRUCT) DIS ! Window DrawItem Structure...
POINTER(pDIS,DIS)

Now, normally when I declare a variable (in this case DIS), I expect the compiler to alot a place in memory for it and whenever 'DIS' appears in subsequent lines of code to use that address (inserted into a placeholder when linking) when its value (or address + an offset when the value of one of its components) is referenced.

So DIS must have an address in memory and space alotted to it. What I believe the POINTER(pDIS,DIS) does is to tell the compiler
  • EITHER that the initial address alotted to DIS in memory is actually going to be changed to the value subsequently given to pDIS
  • OR not to alot a place (address) in memory for DIS, but to substitute the value that subsequently appears in the memory address pointed to by pDIS (pDIS must be alotted a place in memory by the compiler, no?).
In the first case, there remains a memory area set aside for DIS which is subsequently not used (because subsequent references to DIS point to a new location) whereas in the second case, no initial memory area is alotted but the memory area used is that subsequently pointed to by pDIS (and alotted by code elsewhere).

Which is the case? Ot is neither the complete story?

Then we come to the declaration

type(T_DRAWITEMSTRUCT), POINTER :: DIS

which leaves me wondering 'what space is initially alotted to DIS?'. Is it just a 4-byte area to store an integer address? In which case, how does the compiler take care of the subsequent SIZE required for the structure pointed to? Does it dynamically allocate it after a value is supplied for its address and how is this address supplied in place of the value stored in the address initially pointed to by 'DIS'?

TIA.
0 Kudos
Steven_L_Intel1
Employee
2,161 Views
When you use the "Integer POINTER extension" (commonly referred to as "Cray pointers", though Cray itself doesn't like the term), the "OR" case you list is what happens. The variable is not allocated any memory. When you access the variable, the code goes through the pointer variable, whatever its contents, to get to the target.

When using the Fortran 90 style POINTER, it's much the same. No space is allocated to DIS, but you can use ALLOCATE to do so. Alternatively, as I suggested here, you can use C_F_POINTER to "fill in" the pointer with the address from the C pointer. Note that such a pointer can also be an assumed-shape array, in which case you need to supply the array bounds in the call to C_F_POINTER.
0 Kudos
mecej4
Honored Contributor III
2,161 Views
Tony,

Here is a point-of-view regarding Fortran-90 pointers that I found helpful during my own struggles with the concept, given a mind already corrupted by the C-language notion of pointer. The facts of the point-of-view are debatable, and it will be useless and unnecessary if you do not know C pointers.

In C, pointers are never confused with targets. The operator/prefixes '*' and '&' are used to dereference pointers and to create pointers to existing objects. At least in my C programs, the frequency of occurrence of '*' is much more than that of '&'. The creators of Fortran 90 must have felt that '*' clutters up source code, so they decided to leave it out. Thus, dereferencing is always implied when a pointer type variable occurs in an expression. If, in C, you say

var = *ptr; OR *ptr = var;

in Fortran you simply write, respectively,

var = ptr OR ptr = var

For the few cases where, in C, you would say

ptr = &var;

in Fortran you say

ptr => var

I think that providing the TARGET attribute was made all but necessary by this choice in Fortran, given the emphasis on code speed in Fortran.
0 Kudos
Arjen_Markus
Honored Contributor I
2,161 Views

The term pointer is a bit unfortunate -"reference" would have been a better one, IMHO.
And in my equally humble opinion, C (and especially C++ with its references) makes it
unnecessarily complicated. You have * to dereference a pointer and -> to dereference
a component of a pointer to a structure. The compiler complains if you get it wrong -
so it actually knows what it shouldbe!Such a distinction is not needed in Fortran :).
(And there are other places where the Fortran syntax is simpler too - function pointers
for instance).

Regards,

Arjen

0 Kudos
mecej4
Honored Contributor III
2,161 Views
> The term pointer is a bit unfortunate

I agree. The argument reminds me of being startled when I first came to the US and heard someone say, "you know, in the UK they drive on the wrong side of the road".
0 Kudos
Robert_van_Amerongen
New Contributor III
2,161 Views
Dear all,

I am a little confused on some of this thread. Steve gave as an example the command:

CALL_C_F_POINTER(TRANSFER(lParam,C_NULL_PTR), dis)

where I should use

CALL C_F_POINTER(C_LOC(lParam), dis)

I have tested both of these two versions and both seems to work. Is there any preference for one of them? Did I missed some important point? (Note: I am neither a C-expert nor a Fortran-pointer expert.)

Robert van Anerongen
0 Kudos
Steven_L_Intel1
Employee
2,161 Views
The latter, with C_LOC, would only work if lParam was passed in by value, which it is in this specific case. The first argument must be of type C_PTR and be a value containing the address.
0 Kudos
Robert_van_Amerongen
New Contributor III
2,161 Views
Steve,

when using window procecures I always have the dummy arguments declared with the VALUE attribute. So this answers my question. Thanks for that.

Best regards,
Robert van Amerongen
0 Kudos
anthonyrichards
New Contributor III
2,161 Views
Thanks for the guidance.
I have implemented Steve's suggestions and examined the sizes and values of the types produced and am now clearer about what happens. This is how I see it:

The ISO_C_BINDING module contains the type C_PTR which has a single private component C_PTR%PTR of size 4 bytes.
(The C_NULL_PTR is a C_PTR which has C_PTR%PTR=0).

Interoperable pointers must always be cast as this type for safety.

Since its component is private, the component(s) of C_PTR types can only be manipulated by public procedures such as C_F_POINTER.
If FORTRAN variable LPARAM contains an address (pointer), it is not interoperable and so must be cast to a C_PTR using the TRANSFER intrinsic.
Basically, all this does is put the value stored in LPARAM into C_PTR%PTR.

C_LOC(LPARAM) produces a C_PTR with C_PTR%PTR=LOC(LPARAM). This only produces what I want if LPARAM contains the actual address of the structure I want.

What if LPARAM contains a pointer to the address of the structure (i.e. it is supplied by reference)?

P.S. I note that the module is named as ISO_C_BINDING.MODINTR. Why the MODINTR and not MOD? How does that work?
0 Kudos
Steven_L_Intel1
Employee
2,161 Views
The .MODINTR is how we distinguish intrinsic modules from user modules. If you say:

USE, INTRINSIC :: ISO_C_BINDING

then we only look at the .MODINTR even if there is a "user" ISO_C_BINDING.MOD in the include path. If you write:

USE ISO_C_BINDING

then if ISO_C_BINDING.MOD exists it will be used. This satisfies the language rules so that introduction of intrinsic modules doesn't break existing applications.

C_LOC returns a C_PTR containing the address of the argument's value. If the argument is a normal Fortran variable, then it would be a pointer to that value. In the case of a variable LPARAM, the value itself is an address so you'd need an additional level of indirection, and that's what TRANSFER would do for you. The same would be needed if LPARAM was passed by reference (the address of the address).
0 Kudos
anthonyrichards
New Contributor III
2,161 Views
Do you mean to say that you would then have to do two TRANSFERs to get to the C_PTR from LPARAM in that case?
0 Kudos
Steven_L_Intel1
Employee
2,161 Views
No, just one. You're not using C_LOC then.
0 Kudos
Reply