在Delphi程序中调用带有CUDA调用的C函数

3

我的目标是拥有一个Delphi(或FreePascal)代码,可以像这样调用C函数func

C/Cuda文件:

/* this is the "progcuda.cu" file */
#include <stdio.h>

__global__ void foo(int *a, int *b, int *c, int n){
    /*
    add all the vector's element
    */
}


void func(int *a, int *b, int *c,int n){
    int *da,*db,*dc;
    cudaMalloc(&da, n*sizeof(int));
    cudaMalloc(&db, n*sizeof(int));
    cudaMalloc(&dc, n*sizeof(int));

    cudaMemcpy(da,a,sizeof(int)*n,cudaMemcpyHostToDevice);
    cudaMemcpy(db,b,sizeof(int)*n,cudaMemcpyHostToDevice);
    cudaMemcpy(dc,c,sizeof(int)*n,cudaMemcpyHostToDevice);

    foo<<<1,256>>>(da,db,dc);
    cudaMemcpy(c,dc,sizeof(int),cudaMemcpyDeviceToHost);

    /* do other stuff and call another Host and Device functions*/

    return;
}

帕斯卡主文件:

// this is the "progpas.pas" file
program progpas;
{$mode objfpc}{$H+}
uses unitpas;

var
    ...


begin
    ...
    func(a, b, c, len);
    ...
end.

Pascal单元文件:

// this is the "unitpas.pas" file
unit unitpas;
{$link progcuda.o}
interface

uses ctypes;
procedure func(a, b, c : cpint32 , n:cint32); cdecl; external;
procedure foo(a, b, c : cpint32 , n:cint32);cdecl; external;

implementation

end.

我发现了这篇文章Programming CUDA using Delphi or FreePascal,但它更多地展示了如何在Delphi中编写CUDA程序。
我不想在Delphi中编写CUDA程序,我想在纯C/C++代码中编写CUDA程序,然后只在Delphi中调用该C函数。
问题是什么? 我该如何把.cu代码链接到Delphi的代码中?
我正在使用linux ubuntu 16.04 LTS,但如果需要,我也可以在Windows上使用CUDA和VS。
注:如果您能详细解释如何操作,会很有帮助(对Pascal和文件链接不熟悉)。
我已经尝试过生成.o对象文件并将其链接到free pascal中,方法如下:
$ nvcc progcuda.cu -c -o progcuda.o 然后 $fpc progpas.pas
但是链接失败了。
注:我曾经尝试过将由C代码生成的普通.o文件链接到Pascal代码中,使用gcc和freepascal编译器,它可以工作,但是如果我使用nvcc而不是gcc,并将扩展名改为.cu(代码仍然相同),则链接失败。
注:我是Stack Overflow上的新用户,还不能回答问题。

如果您能提供确切的链接错误信息,我们将能够更好地帮助您。 - gflegar
2个回答

5
我对Delphi和FreePascal一无所知,但我了解CUDA、C和C++,因此也许我的解决方案适用于您。我将用一个简单的问题来演示它:f.cu的内容为:
int f() { return 42; }

main.c的内容:

extern int f();

int main() {
    return f();
}

以下作品:
$ gcc -c -xc f.cu # need -xc to tell gcc it's a C file
$ gcc main.c f.o
(no errors emitted)

现在,当我们尝试用 nvcc 替换 gcc 时:
$ nvcc -c f.cu
$ gcc main.c f.o
/tmp/ccI3tBM1.o: In function `main':
main.c:(.text+0xa): undefined reference to `f'
f.o: In function `__cudaUnregisterBinaryUtil()':
tmpxft_0000704e_00000000-5_f.cudafe1.cpp:(.text+0x52): undefined reference to `__cudaUnregisterFatBinary'
f.o: In function `__nv_init_managed_rt_with_module(void**)':
tmpxft_0000704e_00000000-5_f.cudafe1.cpp:(.text+0x6d): undefined reference to `__cudaInitModule'
f.o: In function `__sti____cudaRegisterAll()':
tmpxft_0000704e_00000000-5_f.cudafe1.cpp:(.text+0xa9): undefined reference to `__cudaRegisterFatBinary'
collect2: error: ld returned 1 exit status

这里的问题在于,当编译 f.cu 时,nvcc 会添加对 CUDA 运行时 API 中某些符号的引用,这些符号必须链接到最终的可执行文件中。我的 CUDA 安装在 /opt/cuda 中,因此我将使用它,但您需要将其替换为系统上安装 CUDA 的位置。因此,如果我们在编译库时链接 libcudart.so,则会得到如下结果:
$ nvcc -c f.cu
$ gcc main.c f.o -L/opt/cuda/lib64 -lcudart
/tmp/ccUeDZcb.o: In function `main':
main.c:(.text+0xa): undefined reference to `f'
collect2: error: ld returned 1 exit status

这看起来更好,没有奇怪的错误,但它仍然找不到函数f。这是因为nvccf.cu视为C++文件,因此在创建对象文件时进行名称重整,我们必须指定我们希望f具有C链接,而不是C++链接(在此处查看更多信息:http://en.cppreference.com/w/cpp/language/language_linkage)。 为了做到这一点,我们必须修改f.cu,像这样:
extern "C" int f() { return 42; }

现在当我们做这个时:
$ nvcc -c f.cu
$ gcc main.c f.o -L/opt/cuda/lib64 -lcudart
(no errors emitted)

我希望你能修改这个程序以适应你的语言。
编辑:我尝试了一个更复杂的例子。
// f.cu
#include <stdio.h>

__global__ void kernel() {
    printf("Running kernel\n");
}

extern "C" void f() {
    kernel<<<1, 1>>>();
    // make sure the kernel completes before exiting
    cudaDeviceSynchronize();
}

// main.c
extern void f();

int main() {
    f();
    return 0;
}

编译时我遇到了以下问题:
    f.o:(.data.DW.ref.__gxx_personality_v0[DW.ref.__gxx_personality_v0]+0x0): undefined reference to `__gxx_personality_v0'
collect2: error: ld returned 1 exit status

为了修复它,您还需要将标准的C++库添加到链接器标志中:
$ nvcc -c f.cu
$ gcc main.c f.o -L/opt/cuda/lib64 -lcudart -lstdc++
$ ./a.out
Running kernel

也许我的回答和 https://www.freepascal.org/docs-html/prog/progsu147.html#x186-1890007.1.1 就足以解决问题了。 - gflegar

4
我按照@Goran Flegar的解释修复了文件: 在.cu文件中添加extern "C" int func(...);。然后尝试编译/链接.cu代码,但没有设备调用(但有设备代码),所有内容都正常工作。 但是当我添加设备调用(foo<<<Nb,Nt>>>(...))并使用以下方式进行编译时:
$nvcc progcuda.cu -c
$fpc progpas.pas -ofinal.exe -Fl/usr/local/cuda/lib64

i get:

Free Pascal Compiler version 3.0.4 [2017/12/13] for x86_64
Copyright (c) 1993-2017 by Florian Klaempfl and others
Target OS: Linux for x86-64
Compiling prog1.pas
Linking sum.exe
/usr/bin/ld: aviso: link.res contém seções de saída; você se esqueceu -T?
/usr/bin/ld: sum.o: undefined reference to symbol '_Unwind_Resume@@GCC_3.0'
//lib/x86_64-linux-gnu/libgcc_s.so.1: error adding symbols: DSO missing from command line
prog1.pas(16,1) Error: Error while linking
prog1.pas(16,1) Fatal: There were 1 errors compiling module, stopping
Fatal: Compilation aborted
Error: /usr/bin/ppcx64 returned an error exitcode

所以还有一些缺失的库。


解决方案:

发现将stdc++和gcc_s库链接到Pascal中可以解决编译问题。

unit unitpas;
// file "unitpas.pas"
{$LINK progcuda.o}
{$LINKLIB c}
{$LINKLIB cudart}
{$linklib stdc++}
{$linklib gcc_s}

interface

uses ctypes;
function func(x,y: cint32): cint32; cdecl; external;

implementation

end.

运行
$nvcc progcuda.cu -c
$fpc progpas.pas -ofinal.exe -Fl/usr/local/cuda/lib64

并且一切都正常工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接