Skip to content

Commit 9f450ad

Browse files
committed
poly_function: correct and return status to C++
1 parent 1085779 commit 9f450ad

4 files changed

Lines changed: 226 additions & 38 deletions

File tree

src/poly_function/c_interface_poly.f90

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,19 @@ module c_interface_poly
33
!! from https://github.com/mattzett/fortran_tests
44

55
use datamod_poly, only: dataobj_poly,data1,data2
6-
use, intrinsic :: iso_c_binding, only: c_loc,c_ptr,c_f_pointer,c_int, c_float, c_size_t
6+
use, intrinsic :: iso_c_binding, only: c_loc,c_ptr,c_f_pointer,c_int, c_float, c_size_t, c_null_ptr, c_associated
77
implicit none
88

9+
integer(c_int), parameter :: STATUS_SUCCESS = 0_c_int
10+
integer(c_int), parameter :: STATUS_INVALID_TYPE = 1_c_int
11+
integer(c_int), parameter :: STATUS_NULL_PTR = 2_c_int
12+
integer(c_int), parameter :: STATUS_ZERO_DIMS = 3_c_int
13+
integer(c_int), parameter :: STATUS_ALLOC_FAIL = 4_c_int
14+
integer(c_int), parameter :: STATUS_BAD_OBJECT_PTR = 5_c_int
15+
916
contains
1017

11-
subroutine objconstruct_C(objtype,cptr_f90obj,cptr_indata,lx,ly) bind(C, name='objconstruct_C')
18+
integer(c_int) function objconstruct_C(objtype,cptr_f90obj,cptr_indata,lx,ly) bind(C, name='objconstruct_C')
1219
!! return a c pointer to a fortran polymorphic object that is created by this routine; in this case we
1320
!! are passing the object type to be created from the C main program
1421
integer(c_int), intent(in) :: objtype
@@ -19,24 +26,44 @@ subroutine objconstruct_C(objtype,cptr_f90obj,cptr_indata,lx,ly) bind(C, name='o
1926
type(data1), pointer :: tmpobj1 !< declared as pointers so they don't auto-allocate when we return
2027
type(data2), pointer :: tmpobj2 !< ditto
2128
real(c_float), dimension(:,:), pointer :: fortdata
29+
integer :: alloc_status
2230

2331
!> nullify for sake of clarity and good practice
2432
nullify(tmpobj1, tmpobj2)
33+
objconstruct_C = STATUS_SUCCESS
34+
cptr_f90obj = c_null_ptr
35+
36+
if (.not. c_associated(cptr_indata)) then
37+
objconstruct_C = STATUS_NULL_PTR
38+
return
39+
end if
40+
41+
if (lx == 0_c_size_t .or. ly == 0_c_size_t) then
42+
objconstruct_C = STATUS_ZERO_DIMS
43+
return
44+
end if
2545

2646
!> allocate derived type, note that c_loc only works on a static type (i.e. not polymorphic class), hence the tmpobj's
2747
select case (objtype)
2848
case (1)
29-
allocate(data1::obj)
30-
allocate(tmpobj1)
49+
allocate(tmpobj1, stat=alloc_status)
50+
if (alloc_status /= 0) then
51+
objconstruct_C = STATUS_ALLOC_FAIL
52+
return
53+
end if
3154
cptr_f90obj=c_loc(tmpobj1)
3255
obj=>tmpobj1
3356
case (2)
34-
allocate(data2::obj)
35-
allocate(tmpobj2)
57+
allocate(tmpobj2, stat=alloc_status)
58+
if (alloc_status /= 0) then
59+
objconstruct_C = STATUS_ALLOC_FAIL
60+
return
61+
end if
3662
cptr_f90obj=c_loc(tmpobj2)
3763
obj=>tmpobj2
3864
case default
39-
error stop 'unable to identify object type during construction'
65+
objconstruct_C = STATUS_INVALID_TYPE
66+
return
4067
end select
4168

4269
!> initialize some test data, and call methods to print
@@ -45,53 +72,87 @@ subroutine objconstruct_C(objtype,cptr_f90obj,cptr_indata,lx,ly) bind(C, name='o
4572
call obj%set_data(fortdata)
4673

4774
!! note lack of deallocate and nullify here; we need memory allocated to persist past the return.
48-
end subroutine objconstruct_C
75+
end function objconstruct_C
4976

5077

5178
!> Use some of the polymorphic object methods and data
52-
subroutine objuse_C(objtype,objC) bind(C, name='objuse_C')
79+
integer(c_int) function objuse_C(objtype,objC) bind(C, name='objuse_C')
5380
type(c_ptr), intent(in) :: objC
5481
integer(c_int), intent(in) :: objtype
5582
class(dataobj_poly),pointer :: obj
83+
integer(c_int) :: status
84+
85+
objuse_C = STATUS_SUCCESS
86+
if (.not. c_associated(objC)) then
87+
objuse_C = STATUS_NULL_PTR
88+
return
89+
end if
90+
91+
call set_pointer_dyntype(objtype,objC,obj,status)
92+
if (status /= 0) then
93+
objuse_C = status
94+
return
95+
end if
5696

57-
obj=>set_pointer_dyntype(objtype,objC)
5897
call obj%print_data()
59-
end subroutine objuse_C
98+
end function objuse_C
6099

61100

62101
!> set fortran object pointer dynamic type to what is indicated in objtype. Convert C pointer using
63102
! declared static types (c_f_pointer will not work on a polymorphic object).
64-
function set_pointer_dyntype(objtype, objC) result(obj)
103+
subroutine set_pointer_dyntype(objtype, objC, obj, status)
65104
type(c_ptr), intent(in) :: objC
66105
integer(c_int), intent(in) :: objtype
67-
class(dataobj_poly), pointer :: obj
106+
class(dataobj_poly), pointer, intent(out) :: obj
107+
integer(c_int), intent(out) :: status
68108
type(data1), pointer :: obj1
69109
type(data2), pointer :: obj2
70110

111+
status = STATUS_SUCCESS
112+
nullify(obj)
113+
nullify(obj1, obj2)
114+
71115
select case (objtype)
72116
case (1)
73117
call c_f_pointer(objC,obj1)
118+
if (.not. associated(obj1)) then
119+
status = STATUS_BAD_OBJECT_PTR
120+
return
121+
end if
74122
obj=>obj1
75123
case (2)
76124
call c_f_pointer(objC,obj2)
125+
if (.not. associated(obj2)) then
126+
status = STATUS_BAD_OBJECT_PTR
127+
return
128+
end if
77129
obj=>obj2
78130
case default
79-
error stop 'ERROR: set_pointer_dyntype: unable to identify object type during conversion from C to fortran class pointer'
131+
status = STATUS_INVALID_TYPE
80132
end select
81-
end function set_pointer_dyntype
133+
end subroutine set_pointer_dyntype
82134

83135

84-
subroutine destruct_C(objtype, objC) bind(C, name='destruct_C')
136+
integer(c_int) function destruct_C(objtype, objC) bind(C, name='destruct_C')
85137
type(c_ptr), intent(inout) :: objC
86138
integer(c_int), intent(in) :: objtype
87139

88140
class(dataobj_poly),pointer :: obj
141+
integer(c_int) :: status
142+
143+
destruct_C = STATUS_SUCCESS
144+
if (.not. c_associated(objC)) return
89145

90-
obj=>set_pointer_dyntype(objtype,objC)
146+
call set_pointer_dyntype(objtype,objC,obj,status)
147+
if (status /= 0) then
148+
destruct_C = status
149+
return
150+
end if
91151

92-
deallocate(obj%dataval)
152+
if (associated(obj%dataval)) deallocate(obj%dataval)
93153
deallocate(obj)
154+
objC = c_null_ptr
94155

95-
end subroutine
156+
end function destruct_C
96157

97158
end module c_interface_poly

src/poly_function/datamod_poly.f90

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,29 @@ module datamod_poly
3636
subroutine set_data(self,array)
3737
class(dataobj_poly), intent(inout) :: self
3838
real(c_float), dimension(:,:), intent(in) :: array
39-
40-
self%lx=size(array,1)
41-
self%ly=size(array,2)
42-
allocate(self%dataval(self%lx,self%ly))
39+
integer(c_int) :: nx, ny
40+
integer(c_int) :: dealloc_status, alloc_status
41+
42+
nx = size(array,1)
43+
ny = size(array,2)
44+
45+
! Reuse existing allocation when shape matches to avoid churn.
46+
if (associated(self%dataval)) then
47+
if (size(self%dataval,1) == nx .and. size(self%dataval,2) == ny) then
48+
self%lx = nx
49+
self%ly = ny
50+
self%dataval(:,:) = array(:,:)
51+
return
52+
end if
53+
deallocate(self%dataval, stat=dealloc_status)
54+
if (dealloc_status /= 0) return
55+
end if
56+
57+
allocate(self%dataval(nx,ny), stat=alloc_status)
58+
if (alloc_status /= 0) return
59+
60+
self%lx = nx
61+
self%ly = ny
4362
self%dataval(:,:)=array(:,:)
4463
end subroutine set_data
4564

src/poly_function/my_polyfcn.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
#include <stddef.h>
2+
13
#ifdef __cplusplus
24
extern "C" {
35
#endif
46

5-
void objconstruct_C(int*, void**, float**, const size_t*, const size_t*);
6-
void objuse_C(int*, void*);
7-
void destruct_C(int*, void**);
7+
int objconstruct_C(int*, void**, float**, const size_t*, const size_t*);
8+
int objuse_C(int*, void**);
9+
int destruct_C(int*, void**);
810

911
#ifdef __cplusplus
1012
}

test/poly_function/main.cpp

Lines changed: 118 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,46 @@
11
// C++ calling polymorphic Fortran object
22
// based on https://github.com/mattzett/fortran_tests
3+
// objconstruct_C returns status code:
4+
// 0: success
5+
// 1: invalid object type
6+
// 2: null C pointer input
7+
// 3: zero dimensions in constructor
8+
// 4: allocation failure
9+
// 5: invalid/stale object pointer
310
#include <array>
411
#include <iostream>
512
#include <cstdlib>
613

714
#include "my_polyfcn.h"
815

9-
1016
int main() {
1117

1218
// pointers for various fortran data
13-
void* objptr1;
14-
void* objptr2;
19+
void* objptr1 = nullptr;
20+
void* objptr2 = nullptr;
1521
int objtype;
1622
float* arrptr;
23+
int status;
1724

1825
const size_t lx1=2;
1926
const size_t ly1=2;
2027

21-
// Object 1`
28+
// Object 1
2229
objtype=1;
2330
std::array<float, lx1*ly1> x1;
2431
x1 = {1, 2, 3, 4};
2532
arrptr = &x1.front();
26-
objconstruct_C(&objtype, &objptr1, &arrptr, &lx1, &ly1);
33+
status = objconstruct_C(&objtype, &objptr1, &arrptr, &lx1, &ly1);
34+
if (status != 0) {
35+
std::cerr << "objconstruct_C failed for object 1 with status " << status << "\n";
36+
return EXIT_FAILURE;
37+
}
2738
std::cout << "Use object 1\n";
28-
objuse_C(&objtype, &objptr1);
39+
status = objuse_C(&objtype, &objptr1);
40+
if (status != 0) {
41+
std::cerr << "objuse_C failed for object 1 with status " << status << "\n";
42+
return EXIT_FAILURE;
43+
}
2944

3045
// Object 2
3146
objtype=2;
@@ -34,21 +49,112 @@ const size_t ly2=3;
3449
std::array<float, lx2*ly2> x2;
3550
x2 = {6,5,4,3,2,1};
3651
arrptr = &x2.front();
37-
objconstruct_C(&objtype, &objptr2, &arrptr, &lx2, &ly2);
52+
status = objconstruct_C(&objtype, &objptr2, &arrptr, &lx2, &ly2);
53+
if (status != 0) {
54+
std::cerr << "objconstruct_C failed for object 2 with status " << status << "\n";
55+
return EXIT_FAILURE;
56+
}
3857
std::cout << "Use object 2\n";
39-
objuse_C(&objtype,&objptr2);
58+
status = objuse_C(&objtype, &objptr2);
59+
if (status != 0) {
60+
std::cerr << "objuse_C failed for object 2 with status " << status << "\n";
61+
return EXIT_FAILURE;
62+
}
4063

4164
// show that objects persist
4265
std::cout << "Use object 1 again\n";
4366
objtype=1;
44-
objuse_C(&objtype, &objptr1);
67+
status = objuse_C(&objtype, &objptr1);
68+
if (status != 0) {
69+
std::cerr << "objuse_C failed when reusing object 1 with status " << status << "\n";
70+
return EXIT_FAILURE;
71+
}
4572

4673
std::cout << "Use object 2 again\n";
4774
objtype=2;
48-
objuse_C(&objtype, &objptr2);
75+
status = objuse_C(&objtype, &objptr2);
76+
if (status != 0) {
77+
std::cerr << "objuse_C failed when reusing object 2 with status " << status << "\n";
78+
return EXIT_FAILURE;
79+
}
80+
81+
// Negative-path checks for C ABI status behavior.
82+
int invalid_objtype = 999;
83+
status = objuse_C(&invalid_objtype, &objptr1);
84+
if (status != 1) {
85+
std::cerr << "Expected invalid objtype status 1 from objuse_C, got " << status << "\n";
86+
return EXIT_FAILURE;
87+
}
88+
89+
void* null_obj = nullptr;
90+
status = objuse_C(&objtype, &null_obj);
91+
if (status != 2) {
92+
std::cerr << "Expected null pointer status 2 from objuse_C, got " << status << "\n";
93+
return EXIT_FAILURE;
94+
}
95+
96+
void* bad_construct_ptr = nullptr;
97+
size_t zero_dim = 0;
98+
objtype = 1;
99+
status = objconstruct_C(&objtype, &bad_construct_ptr, &arrptr, &zero_dim, &ly1);
100+
if (status != 3) {
101+
std::cerr << "Expected zero-dimension status 3 from objconstruct_C, got " << status << "\n";
102+
return EXIT_FAILURE;
103+
}
104+
105+
objtype = 999;
106+
status = objconstruct_C(&objtype, &bad_construct_ptr, &arrptr, &lx1, &ly1);
107+
if (status != 1) {
108+
std::cerr << "Expected invalid objtype status 1 from objconstruct_C, got " << status << "\n";
109+
return EXIT_FAILURE;
110+
}
111+
112+
// Stress the object lifecycle repeatedly.
113+
for (int iter = 0; iter < 100; ++iter) {
114+
void* loop_obj = nullptr;
115+
objtype = (iter % 2) + 1;
116+
size_t loop_lx = (objtype == 1) ? lx1 : lx2;
117+
size_t loop_ly = (objtype == 1) ? ly1 : ly2;
118+
arrptr = (objtype == 1) ? &x1.front() : &x2.front();
119+
120+
status = objconstruct_C(&objtype, &loop_obj, &arrptr, &loop_lx, &loop_ly);
121+
if (status != 0) {
122+
std::cerr << "Lifecycle loop construct failed at iteration " << iter << " with status " << status << "\n";
123+
return EXIT_FAILURE;
124+
}
125+
126+
status = objuse_C(&objtype, &loop_obj);
127+
if (status != 0) {
128+
std::cerr << "Lifecycle loop use failed at iteration " << iter << " with status " << status << "\n";
129+
return EXIT_FAILURE;
130+
}
131+
132+
status = destruct_C(&objtype, &loop_obj);
133+
if (status != 0) {
134+
std::cerr << "Lifecycle loop destruct failed at iteration " << iter << " with status " << status << "\n";
135+
return EXIT_FAILURE;
136+
}
137+
}
138+
139+
objtype=1;
140+
status = destruct_C(&objtype, &objptr1);
141+
if (status != 0) {
142+
std::cerr << "destruct_C failed for object 1 with status " << status << "\n";
143+
return EXIT_FAILURE;
144+
}
145+
objtype=2;
146+
status = destruct_C(&objtype, &objptr2);
147+
if (status != 0) {
148+
std::cerr << "destruct_C failed for object 2 with status " << status << "\n";
149+
return EXIT_FAILURE;
150+
}
49151

50-
destruct_C(&objtype, &objptr1);
51-
destruct_C(&objtype, &objptr2);
152+
// Double-destroy should be harmless because destruct_C ignores null pointers.
153+
status = destruct_C(&objtype, &objptr2);
154+
if (status != 0) {
155+
std::cerr << "Expected success from double destruct, got status " << status << "\n";
156+
return EXIT_FAILURE;
157+
}
52158

53159
return EXIT_SUCCESS;
54160
}

0 commit comments

Comments
 (0)