과학 프로그램에서 Fortran의 함수 포인터로 작업하는 방법


11

다음은 C에서 함수 포인터의 일반적인 사용법입니다. Fortran에서 비슷한 작업을하고 싶습니다. 몇 가지 아이디어가 있지만 정식 방법이 있는지 알고 싶습니다.

사용자가 전달한 함수 포인터와 컨텍스트는 저장되고 나중에 호출됩니다.

typedef PetscErrorCode (*TSIFunction)(TS,PetscReal,Vec,Vec,Vec,void*);
PetscErrorCode TSSetIFunction(TS ts,Vec res,TSIFunction f,void *ctx);

사용자의 기능은 다양한 상황에서 컨텍스트를 사용하여 다시 호출됩니다.

PETSc에서는 문자열-> 함수 포인터 테이블을 많이 사용합니다. 모든 것이 플러그인이므로 사용자는 자신의 구현을 등록 할 수 있으며 일류입니다.

#define PCGAMG "gamg"
  PCRegisterDynamic(PCGAMG         ,path,"PCCreate_GAMG",PCCreate_GAMG);

이것은 생성 루틴을 "FList"에 등록한 다음 PCSetFromOptions ()는 다른 방법과 비교하여이 방법을 선택할 수있는 기능을 제공합니다. 시스템이 동적 로딩을 지원하는 경우 PCCreate_GAMG 심볼에 대한 컴파일 타임 종속성을 건너 뛰고 NULL 만 전달하면 심볼이 런타임에 공유 라이브러리에서 조회됩니다.

"공장"을 넘어서는이 단계는 Martin Fowler가 "서비스 로케이터"라고 부르는 것과 유사한 제어 장치의 역전입니다.

참고 : 이것은 Jed Brown과의 개인적인 서신에서 나 에게이 질문을했습니다. 나는 그것을 아웃소싱하고 사람들이 어떤 답변을 얻을 수 있는지 확인하기로 결정했습니다.

답변:


5

void *최신 Fortran 코드에서 전송을 사용하여 에뮬레이션 할 필요가 없습니다 . 대신, 모든 주요 포트란 컴파일러에서 지원 하는 ISO_C_BINDING 내장 모듈을 사용하십시오 . 이 모듈을 사용하면 Fortran과 C 사이의 인터페이스를 매우 간단하게 만들 수 있습니다. C_LOCand C_FUNLOC함수를 사용하여 Fortran 데이터 및 프로 시저에 대한 C 포인터를 각각 얻을 수 있습니다.

위의 PETSC 예와 관련하여 컨텍스트는 일반적으로 사용자 정의 구조에 대한 포인터라고 가정합니다. 이는 Fortran의 파생 데이터 형식과 같습니다. 을 사용하는 데 문제가되어서는 안됩니다 C_LOC. 불투명 한 TSIFunction 핸들도 처리하기가 매우 간단합니다. ISO c_ptr와 동일한 ISO_C_BINDING 데이터 유형을 사용하십시오 . 이는 void *C 와 동일합니다 . Fortran으로 작성된 라이브러리 c_ptr는 최신 Fortran의 엄격한 유형 검사를 해결해야하는 경우 사용할 수 있습니다 .


Brian, 네, 그동안 Jed와 저는 콜백에 대한 몇 가지 해결책을 알아 냈습니다. fortran90.org/src/best-practices.html#type-casting-in-callbacks , type (c_ptr) 섹션 번호 V입니다.
Ondřej Čertík

9

내가 추측하고있는 것은 귀하의 질문에 PETSc 관련 언어 (내가 익숙하지 않은)입니다. 여기에 주름이있을 수는 있지만 이해할 수는 없지만 여전히 도움이 될 것입니다. 시작했다.

기본적으로 프로 시저에 대한 인터페이스를 정의해야하며이 인터페이스 다음에 오는 함수에 포인터를 전달할 수 있습니다. 다음 코드는 예를 보여줍니다. 먼저 인터페이스를 정의하고 해당 인터페이스를 따르는 사용자가 제공 한 루틴을 실행하는 코드 청크의 간단한 예를 보여주는 모듈이 있습니다. 다음은 사용자가이 모듈을 사용하고 실행할 기능을 정의하는 방법을 보여주는 프로그램입니다.

MODULE xmod

  ABSTRACT INTERFACE
  FUNCTION function_template(n,x) RESULT(y)
      INTEGER, INTENT(in) :: n
      REAL, INTENT(in) :: x(n)
      REAL :: y
  END FUNCTION function_template
  END INTERFACE

CONTAINS

  SUBROUTINE execute_function(n,x,func,y)
    INTEGER, INTENT(in) :: n
    REAL, INTENT(in) :: x(n)
    PROCEDURE(function_template), POINTER :: func
    REAL, INTENT(out) :: y
    y = func(n,x)
  END SUBROUTINE execute_function

END MODULE xmod


PROGRAM xprog

  USE xmod

  REAL :: x(4), y
  PROCEDURE(function_template), POINTER :: func

  x = [1.0, 2.0, 3.0, 4.0]
  func => summation

  CALL execute_function(4,x,func,y)

  PRINT*, y  ! should give 10.0

CONTAINS

  FUNCTION summation(n,x) RESULT(y)
    INTEGER, INTENT(in) :: n
    REAL, INTENT(in) :: x(n)
    REAL :: y
    y = SUM(x)
  END FUNCTION summation

END PROGRAM xprog

답변 해주셔서 감사합니다. 위의 PETSc 예제는 일부 내부 데이터 구조에 함수 포인터를 저장하지만 PROCEDURE(function_template), POINTER :: func내부적 으로 저장하는 것이 매우 간단하다고 생각합니다 .
Ondřej Čertík

포인터는 해당 코드의 주소가 아닌 불투명 한 객체이므로 AFAIK는 C와 상호 운용성이 없습니다. PETSc에서는 이러한 것들에 대해 C 래퍼에 대한 함수 포인터 테이블을 유지해야합니다.
Matt Knepley

PETSc 예제는 함수 포인터와 컨텍스트 (함수가 호출 될 때 다시 전달되는 개인 사용자 데이터)를 모두 저장합니다. 컨텍스트는 정말 중요합니다. 그렇지 않으면 사용자가 전역 참조와 같은 끔찍한 일을하게됩니다. 에 해당하는 것이 없으므로 void*사용자는 라이브러리 함수 자체에 대한 인터페이스를 작성해야합니다. C로 라이브러리를 구현하면 이것으로 충분하지만 Fortran에서 구현하면 컴파일러가 사용자의 INTERFACE와 동시에 라이브러리의 "더미"INTERFACE를 보지 못하게해야합니다.
Jed Brown

2
void*Fortran 과 동등한 transfer방법입니다. 사용법 예는 여기 를 참조 하십시오 . 이 방법 외에 다른 세 가지 접근 transfer방식은 "작업 배열", "대신 특정 파생 유형 void *"이며 모듈에 로컬 인 모듈 변수를 사용합니다.
Ondřej Čertík

함수를 호출 하기 위해 사용자가 transfer말도 안되는 유형 ( character (len=1), allocatable) 을 사용해야하는 것은 부끄러운 일 입니다.
Jed Brown
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.