Fortran Coder

标题: 求助:关于iso_c_binding中C端多重指针参数接口的写法 [打印本页]

作者: andy8496    时间: 2021-7-29 16:01
标题: 求助:关于iso_c_binding中C端多重指针参数接口的写法

求教一个混编问题:

我要调用一个C语言函数:
[C] 纯文本查看 复制代码
int get_table(char ***Results );

这个函数位于一个已经封装好的动态链接库中,作用是读取一个文件,将内容存储到二维字符串数组Results中。

按照如下写法,我得到的Results是一个乱码:

接口:
[Fortran] 纯文本查看 复制代码
interface
  function get_table(Results) bind(c)
    use iso_c_binding, only: c_int, c_char
    character(kind=c_char),dimension(*)::Results
    integer(c_int):: get_table
  end function
end interface


调用:

[Fortran] 纯文本查看 复制代码
character(8) :: Results(3,10)
i =  get_table(Results)


结果:
                Results(1,1)        '鴴+     '        CHARACTER(8)
                Results(2,1)        '        '        CHARACTER(8)
                Results(3,1)        '        '        CHARACTER(8)
                Results(1,2)        '        '        CHARACTER(8)
                Results(2,2)        '        '        CHARACTER(8)
                Results(3,2)        '        '        CHARACTER(8)
                Results(1,3)        '        '        CHARACTER(8)
                Results(2,3)        '        '        CHARACTER(8)
                Results(3,3)        '        '        CHARACTER(8)
                Results(1,4)        '        '        CHARACTER(8)

……
……
这个结果并不是我预期的。

请教大神,这个interface应该如何写,Fortran中的Results参数应该如何声明?
先谢过!



作者: 布衣龙共    时间: 2021-7-30 08:24
本帖最后由 布衣龙共 于 2021-7-30 08:27 编辑

[buyi@OLCC ~]$ gfortran a.f90 a.c -o x
[buyi@OLCC ~]$ ./x
fcode
[buyi@OLCC ~]$

[Fortran] 纯文本查看 复制代码
use ISO_C_Binding
implicit none
interface
  integer(c_int) function get_table(val) bind(c)
    import
    type(c_ptr) :: val
  end function
end interface
character(8) ,target :: Results(3,10)
type(c_ptr) , target :: val
integer :: i
val = c_loc(Results)
i =  get_table(c_loc(val))
write(*,*) Results(1,1)
end

[C] 纯文本查看 复制代码
#include <stdio.h>

int get_table(char ***Results){
  (**Results)[0] = 'f';
  (**Results)[1] = 'c';
  (**Results)[2] = 'o';
  (**Results)[3] = 'd';
  (**Results)[4] = 'e';
}

作者: andy8496    时间: 2021-7-30 09:47
本帖最后由 andy8496 于 2021-7-30 11:59 编辑

多谢!但是我这儿好像没成功
[Fortran] 纯文本查看 复制代码
use ISO_C_Binding
implicit none
interface
  integer(c_int) function get_table(val) bind(c)
    import
    type(c_ptr) :: val
  end function
end interface
character(8) ,target :: Results(3,10)
type(c_ptr) , target :: val
integer :: i
val = c_loc(Results)
i =  get_table(c_loc(val))
write(*,*) Results(1,1)
end

第13行中,val是不是已经是地址了,还需要套一个c_loc吗?我这么改了但是依然没成功。跟我的环境(VS+IVF)有关系吗?我再接着试试别的写法。


再接着问一个问题:
在C中,Results似乎不需要指定“大小”,这样我们不需要预先知道要读取的数据多少,就能完成读取
在Fortran中,我们一定要预先申明,character(8) ,target :: Results(3,10),也就是先知道8,3,10这些表示数据的多少的量才行吗?有没有“自动分配”的方法?
或者退而求其次,我想办法预先知道了8,3,10这些表示数据, Results能不能申明成可分配的?
如若不然,要完整的读取数据,声明Results时,大小真是不好办!





作者: 布衣龙共    时间: 2021-7-30 17:04
我用 windows , VS + Ifort 试了,也是OK的啊



作者: 布衣龙共    时间: 2021-7-30 17:07
第13行,是的, val 已经是地址了,还必须套一个 c_loc
因为 C 语言的接口是 ***Results,而不是 **Results

C 语言不是“不需要”指定大小,而是根本不知道大小(除非你额外传递给他)
所以,C 语言是很容易越界的。

int get_table(char ***Results );
这个函数,必须要求调用者分配好足够的大小。

没有自动分配的方法。

Results 可以是可分配的,但也必须分配好大小,再 get_table
作者: fcode    时间: 2021-7-31 09:05
C函数应该是一个三维数组,你可能需要构建一个指针链给他用。

[C] 纯文本查看 复制代码
#include <stdio.h>

int get_table(char ***Results){
  Results[0][0][0] = 'f';
  Results[0][0][1] = 'c';
  Results[0][0][2] = 'o';
  Results[0][0][3] = 'd';
  Results[0][0][4] = 'e';
  Results[3][5][0] = 'f';
  Results[3][5][1] = 'c';
  Results[3][5][2] = 'o';
  Results[3][5][3] = 'd';
  Results[3][5][4] = 'e';
  return 1;
}

[Fortran] 纯文本查看 复制代码
Program Main
  use ISO_C_Binding
  use CLangMultiDimensionArrayLink2D
  implicit none

  interface
    integer(c_int) function get_table(val) bind(c)
      import
      type(c_ptr) , value :: val
    end function
  end interface

  Integer , parameter :: M = 6 , N = 4
  character(8) ,target :: Results(M,N)
  integer :: i
  type(CLang2DArrayLink) :: linkResults
  i =  get_table( linkResults%Set(Results) )
  write(*,"(2(a8,1x))") Results(1,1),Results(M,N)
End Program Main


以下模块可以复制粘贴,不做修改。
[Fortran] 纯文本查看 复制代码
Module CLangMultiDimensionArrayLink2D

  use , Intrinsic :: ISO_C_Binding
  Implicit None
  private

  Type , public :: CLang2DArrayLink
    type(c_ptr) , pointer :: p1(:) , p2(:,:)
  contains
    Procedure :: Set
    Final :: Release
  End Type CLang2DArrayLink

contains

  Type(c_ptr) Function Set( this , datas ) result ( cptr )
    class(CLang2DArrayLink) :: this
    class(*) , intent(IN) :: datas(1:,1:)
    integer :: sizes(2) , i , j
    sizes = shape(datas)
    if( associated(this%p1) ) deallocate(this%p1,this%p2)
    Allocate(this%p1(sizes(2)),this%p2(sizes(1),sizes(2)))
    Do i = 1 , sizes(2)
      Do j = 1 , sizes(1)
        this%p2(j,i) = c_loc(datas(j,i))
      End Do
      this%p1(i) = c_loc(this%p2(1,i))
    End Do
    cptr = c_loc(this%p1(1))
  End Function Set

  Subroutine Release( this )
    type(CLang2DArrayLink) :: this
    if( associated(this%p1) ) deallocate(this%p1,this%p2)
  End Subroutine Release  

End Module CLangMultiDimensionArrayLink2D





作者: andy8496    时间: 2021-8-16 15:04
本帖最后由 andy8496 于 2021-8-16 15:09 编辑
fcode 发表于 2021-7-31 09:05
C函数应该是一个三维数组,你可能需要构建一个指针链给他用。

[mw_shl_code=c,true]#include

非常感谢二位的耐心解答。我运行了两个解决方案,都能得到正确的结果。但是,可能是我关于问题的描述/简化不正确,这段时间又在我自己的工程中反复尝试了这两种方法,都没有得到预期的结果。无奈之下,只能在此再次求助!附件是我这个问题的实际工程(VS2012+Intel Parallel Studio XE 2015)。

Project.jpg (52.71 KB, 下载次数: 439)

Project.jpg

DB_Test.rar

1.73 MB, 下载次数: 27


作者: 布衣龙共    时间: 2021-8-16 16:14
本帖最后由 布衣龙共 于 2021-8-16 16:17 编辑

原来是 sqlite。sqlite3_get_table 的参数,你给他一个指针,由它内部分配空间,最后把它分配的空间的地址告诉 Fortran。
我们之前的思路,都是由 Fortran 分配空间,构建好,然后交给 C 去填充。

这两者的思路是不同的。

[Fortran] 纯文本查看 复制代码

type(c_ptr),target :: val
type(c_ptr),pointer:: p1(:)
istat = sqlite3_get_table(db,"select * from MY_TABLE",val,nRow,nColumn,ErrMsg)
  call c_f_pointer(val,p1,[nRow*nColumn])
  Do i = 1 , nRow
    Do j = 1 , nColumn
      call c_f_pointer( p1( (i-1)*nColumn + j ),cRes)
      k = index(cRes,c_null_char)
      Results(j,i) = cRes(:k-1)
    End Do
  End Do

作者: andy8496    时间: 2021-8-16 17:30
布衣龙共 发表于 2021-8-16 16:14
原来是 sqlite。sqlite3_get_table 的参数,你给他一个指针,由它内部分配空间,最后把它分配的空间的地址 ...

cRes我这么声明的

[Fortran] 纯文本查看 复制代码
character(kind=c_char,len=256),pointer :: cRes


现在可以了。感激不尽!
另外还有一个问题,这里用到的这些pointer需要释放吗?
作者: 布衣龙共    时间: 2021-8-16 17:44
可以,没关系的。

Fortran 的指针不需要释放,C那边你找找,可能有别的函数来释放。
作者: andy8496    时间: 2021-8-17 09:00
布衣龙共 发表于 2021-8-16 17:44
可以,没关系的。

Fortran 的指针不需要释放,C那边你找找,可能有别的函数来释放。 ...

Perfect!再次感谢!!!

作者: andy8496    时间: 2021-8-18 09:56
本帖最后由 andy8496 于 2021-8-18 10:20 编辑
布衣龙共 发表于 2021-8-16 16:14
原来是 sqlite。sqlite3_get_table 的参数,你给他一个指针,由它内部分配空间,最后把它分配的空间的地址 ...

非常不好意思,又遇到一个问题。


在7#楼附件工程DB_Test/FortranCallC/Source Files/FortranCallC.f90中,生成数据文件的部分:

[Fortran] 纯文本查看 复制代码
  istat = sqlite3_open(trim(filename)//""C, db)
  sql = "CREATE TABLE MY_TABLE (ID INTEGER PRIMARY KEY  AUTOINCREMENT, "// &
            "DATA1           TEXT    NOT NULL, " // &
            "DATA2           TEXT )"
  istat = sqlite3_exec(db, sql, c_null_funptr, c_null_ptr, ErrMsg)
  istat = sqlite3_exec(db,"begin;",c_null_funptr, c_null_ptr, ErrMsg)
  sql = "INSERT INTO MY_TABLE (DATA1,DATA2) VALUES (?,?);"C
  istat = sqlite3_prepare_v2(db,sql,len_trim(sql),stmt,c_null_ptr)
  do i=1,50
   istat = sqlite3_bind_text(stmt, 1, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 32, c_null_ptr);
   istat = sqlite3_bind_text(stmt, 2, "0123456789", 16, c_null_ptr);
    istat = sqlite3_step(stmt)
  enddo
istat = sqlite3_finalize(stmt)
istat = sqlite3_exec(db,"commit;",c_null_funptr, c_null_ptr, ErrMsg)
  istat = sqlite3_close(db)


如果在sqlite3_bind_text中像上文那样直接用字符串常量就没问题:
[Fortran] 纯文本查看 复制代码
istat = sqlite3_bind_text(stmt, 1, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 32, c_null_ptr);

但是改成变量写进去的就是乱码,读出来的也是乱码了
[Fortran] 纯文本查看 复制代码
 sss = “ABCDEFGHIJKLMNOPQRSTUVWXYZ”  ! character(256) :: sss
istat = sqlite3_bind_text(stmt, 1, trim(sss)//""C 32, c_null_ptr);



能想到的原因有两个:
①字符串声明的有问题,或者类型不合适
②可能与编码有关
做了一些尝试,问题依旧。无奈再次求助!


作者: fcode    时间: 2021-8-18 11:06
sss = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"c
istat = sqlite3_bind_text(stmt, 1, sss, 32, c_null_ptr);

这样就可以了。此处sss只能是引用,不能是表达式。
因为给 C 语言的是一个指针,而表达式结果是临时存放的,一旦整个语句结束,表达式的结果就清空了。
你试试,这样都不行:
istat = sqlite3_bind_text(stmt, 1, (sss), 32, c_null_ptr);
因为 (sss) 也是一个表达式。

作者: andy8496    时间: 2021-8-19 11:33
fcode 发表于 2021-8-18 11:06
sss = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"c
istat = sqlite3_bind_text(stmt, 1, sss, 32, c_null_ptr);

谢谢老群主!但是,奇怪的现象又来了……明明赋了不同的值,居然写进去两个同样的值……

20210819105733.png (55.71 KB, 下载次数: 384)

20210819105733.png

作者: fcode    时间: 2021-8-19 13:07
sqlite3_bind_text 的时候,并没有写入,只是存入地址。
等到 sqlite3_step 的时候,才会真正写入。
因为你都是用的 s_data 这个变量,所以地址相同。
最后,所有的值,都是最后一次的值
作者: andy8496    时间: 2021-8-20 10:09
fcode 发表于 2021-8-19 13:07
sqlite3_bind_text 的时候,并没有写入,只是存入地址。
等到 sqlite3_step 的时候,才会真正写入。
因为你 ...

谢谢!这个点明白了!但是运行之后还是有些问题,是不是sqlite3_bind_text的接口/参数还是存在问题呢?见图片:

20210820100251.png (68.21 KB, 下载次数: 358)

20210820100251.png

FortranCallC.rar

805 Bytes, 下载次数: 3


作者: fcode    时间: 2021-8-20 19:29
     function sqlite3_reset(stmt) bind(c)
       import
       type(c_ptr), value :: stmt
       integer(c_int) sqlite3_reset
     end function sqlite3_reset


    istat = sqlite3_step(stmt)
    write(*,"(A,I0,A,I0)") "第",i,"次sqlite3_step返回",istat
    istat = sqlite3_reset(stmt)


作者: andy8496    时间: 2021-8-23 09:58
fcode 发表于 2021-8-20 19:29
function sqlite3_reset(stmt) bind(c)
       import
       type(c_ptr), value :: stmt

之前百度了一个答案说是新版中sqlite3_step已经包含了sqlite3_reset,所以我给去掉了……
现在加上就好了,非常感谢!

再次感谢二位这段时间不厌其烦的帮助!




欢迎光临 Fortran Coder (http://bbs.fcode.cn/) Powered by Discuz! X3.2