C++ 多个类的DLL封装以及隐式链接和显式链接

将OpenCL和OpenCV一些简单的方法封装成DLL,具体OpenCL和OpenCV的配置方法可以参考本人的博客。

VS2015下安装与编译OpenCV源码并在VS2015下配置OpenCV环境

VS上运行CUDA,并在NVDIA显卡安装的CUDA中运行OpenCL


1.  DLL封装

(1)  创建DLL工程

新建一个Win32控制台应用程序,项目名称为buildopencldll,选择DLL和空项目。

(2)新建头文件

头文件:createopencldll.h

向动态链接库添加类的核心代码:
  1. #ifdef CREATEOPENCLDLL_EXPORTS  
  2. #define CREATEOPENCLDLL_API __declspec(dllexport)   
  3. #else  
  4. #define CREATEOPENCLDLL_API __declspec(dllimport)   
  5. #endif  
#ifdef CREATEOPENCLDLL_EXPORTS




#define CREATEOPENCLDLL_API __declspec(dllexport) #else #define CREATEOPENCLDLL_API __declspec(dllimport) #endif 为显式链接做准备的导出函数:
  1. extern “C”  
  2. {  
  3.     CREATEOPENCLDLL_API CreateOpenCLDLL* GetCreateOpenCLDLL(void);  
  4.     typedef CreateOpenCLDLL* (*PFNGetCreateOpenCLDLL)(void);  
  5. }  
extern "C"
{
    CREATEOPENCLDLL_API CreateOpenCLDLL* GetCreateOpenCLDLL(void);
    typedef CreateOpenCLDLL* (*PFNGetCreateOpenCLDLL)(void);
}

创建类时必须加上 CREATEOPENCLDLL_API,如写成class CREATEOPENCLDLL_APICreateOpenCLDLL 要不然生成不了lib文件
完整的头文件代码如下:
  1. #pragma once  
  2. #ifndef _CREATEOPENCLDLL_H_  
  3. #define _CREATEOPENCLDLL_H_  
  4.   
  5. #ifdef CREATEOPENCLDLL_EXPORTS  
  6. #define CREATEOPENCLDLL_API __declspec(dllexport)   
  7. #else  
  8. #define CREATEOPENCLDLL_API __declspec(dllimport)   
  9. #endif  
  10.   
  11. #ifdef __APPLE__  
  12. #include <OpenCL/cl.h>  
  13. #else  
  14. #include <CL/cl.h>  
  15. #endif  
  16.   
  17.   
  18. class CREATEOPENCLDLL_API CreateOpenCLDLL  
  19. {  
  20. public:  
  21.     CreateOpenCLDLL();  
  22.     ~CreateOpenCLDLL();  
  23.     //  选择平台并创建上下文    
  24.     cl_context CreateContext();  
  25.     cl_command_queue CreateCommandQueue(cl_context context, cl_device_id *device);  
  26.     cl_program CreateProgram(cl_context context, cl_device_id device, const char* fileName);  
  27.     bool CreateMemObjects(cl_context context, cl_mem memObjects[3], float *a, float *b);  
  28.     void Cleanup(cl_context context, cl_command_queue commandQueue,  
  29.         cl_program program, cl_kernel kernel, cl_mem memObjects[3]);  
  30.     void Calcaute();  
  31.   
  32. private:  
  33.     cl_context m_context;  
  34.     cl_command_queue m_commandQueue;  
  35.     cl_program m_program;  
  36.     cl_device_id m_device;  
  37.     cl_kernel m_kernel;  
  38.     cl_mem m_memObjects[3];  
  39.     cl_int m_errNum;  
  40. };  
  41. /* 导出函数声明 */  
  42. extern “C”  
  43. {  
  44.     CREATEOPENCLDLL_API CreateOpenCLDLL* GetCreateOpenCLDLL(void);  
  45.     typedef CreateOpenCLDLL* (*PFNGetCreateOpenCLDLL)(void);  
  46. }  
  47.   
  48. #endif    
#pragma once




#ifndef _CREATEOPENCLDLL_H_ #define _CREATEOPENCLDLL_H_ #ifdef CREATEOPENCLDLL_EXPORTS #define CREATEOPENCLDLL_API __declspec(dllexport) #else #define CREATEOPENCLDLL_API __declspec(dllimport) #endif #ifdef __APPLE__ #include <OpenCL/cl.h> #else #include <CL/cl.h> #endif class CREATEOPENCLDLL_API CreateOpenCLDLL { public: CreateOpenCLDLL(); ~CreateOpenCLDLL(); // 选择平台并创建上下文 cl_context CreateContext(); cl_command_queue CreateCommandQueue(cl_context context, cl_device_id *device); cl_program CreateProgram(cl_context context, cl_device_id device, const char* fileName); bool CreateMemObjects(cl_context context, cl_mem memObjects[3], float *a, float *b); void Cleanup(cl_context context, cl_command_queue commandQueue, cl_program program, cl_kernel kernel, cl_mem memObjects[3]); void Calcaute(); private: cl_context m_context; cl_command_queue m_commandQueue; cl_program m_program; cl_device_id m_device; cl_kernel m_kernel; cl_mem m_memObjects[3]; cl_int m_errNum; }; /* 导出函数声明 */ extern "C" { CREATEOPENCLDLL_API CreateOpenCLDLL* GetCreateOpenCLDLL(void); typedef CreateOpenCLDLL* (*PFNGetCreateOpenCLDLL)(void); } #endif


(3)Cpp文件:createopencldll.cpp

需要添加:#define CREATEOPENCLDLL_EXPORTS

完整的cpp代码如下:

  1. #define CREATEOPENCLDLL_EXPORTS  
  2.   
  3. #include <iostream>  
  4. #include <fstream>  
  5. #include <string>  
  6. #include <vector>  
  7. #include <sstream>  
  8.   
  9. #include “createopencldll.h”  
  10.   
  11. using namespace std;  
  12.   
  13. const int KArraySize = 1000;  
  14.   
  15. CreateOpenCLDLL::CreateOpenCLDLL()  
  16. {  
  17.     m_context = 0;  
  18.     m_commandQueue = 0;  
  19.     m_program = 0;  
  20.     m_device = 0;  
  21.     m_kernel = 0;  
  22.     m_memObjects[0] = 0;  
  23.     m_memObjects[1] = 0;  
  24.     m_memObjects[2] = 0;  
  25.     m_errNum = 0;  
  26. }  
  27.   
  28. CreateOpenCLDLL::~CreateOpenCLDLL()  
  29. {  
  30.   
  31. }  
  32.   
  33. //  选择平台并创建上下文  
  34. cl_context CreateOpenCLDLL::CreateContext()  
  35. {  
  36.     cl_int errNum;  
  37.     cl_uint numPlatforms;  
  38.     cl_platform_id firstPlatformId;  
  39.     cl_context context = NULL;  
  40.   
  41.     //选择第一个可用的平台    
  42.     errNum = clGetPlatformIDs(1, &firstPlatformId, &numPlatforms);  
  43.     if (errNum != CL_SUCCESS || numPlatforms <= 0)  
  44.     {  
  45.         std::cerr << ”Failed to find any OpenCL platforms.” << std::endl;  
  46.         return NULL;  
  47.     }  
  48.   
  49.     // 创建一个opencl上下文,成功则使用GUP上下文,否则使用cpu    
  50.     cl_context_properties contextProperties[] =  
  51.     {  
  52.         CL_CONTEXT_PLATFORM,  
  53.         (cl_context_properties)firstPlatformId,  
  54.         0  
  55.     };  
  56.     context = clCreateContextFromType(contextProperties, CL_DEVICE_TYPE_GPU,  
  57.         NULL, NULL, &errNum);  
  58.     if (errNum != CL_SUCCESS)  
  59.     {  
  60.         std::cout << ”Could not create GPU context, trying CPU…” << std::endl;  
  61.         context = clCreateContextFromType(contextProperties, CL_DEVICE_TYPE_CPU,  
  62.             NULL, NULL, &errNum);  
  63.         if (errNum != CL_SUCCESS)  
  64.         {  
  65.             std::cerr << ”Failed to create an OpenCL GPU or CPU context.” << std::endl;  
  66.             return NULL;  
  67.         }  
  68.     }  
  69.   
  70.     return context;  
  71. }  
  72.   
  73. //选择第一个可用的设备并创建一个命令队列  
  74. cl_command_queue CreateOpenCLDLL::CreateCommandQueue(cl_context context, cl_device_id *device)  
  75. {  
  76.     cl_int errNum;  
  77.     cl_device_id *devices;  
  78.     cl_command_queue commandQueue = NULL;  
  79.     size_t deviceBufferSize = -1;  
  80.   
  81.     //这个clGetContextInfo获得设备缓冲区的大小    
  82.     errNum = clGetContextInfo(context, CL_CONTEXT_DEVICES, 0, NULL, &deviceBufferSize);  
  83.     if (errNum != CL_SUCCESS)  
  84.     {  
  85.         std::cerr << ”Failed call to clGetContextInfo(…,GL_CONTEXT_DEVICES,…)”;  
  86.         return NULL;  
  87.     }  
  88.   
  89.     if (deviceBufferSize <= 0)  
  90.     {  
  91.         std::cerr << ”No devices available.”;  
  92.         return NULL;  
  93.     }  
  94.   
  95.     //为设备缓冲区分配内存,这个clGetContextInfo用来获得上下文中所有可用的设备    
  96.     devices = new cl_device_id[deviceBufferSize / sizeof(cl_device_id)];  
  97.     errNum = clGetContextInfo(context, CL_CONTEXT_DEVICES, deviceBufferSize, devices, NULL);  
  98.     if (errNum != CL_SUCCESS)  
  99.     {  
  100.         delete[] devices;  
  101.         std::cerr << ”Failed to get device IDs”;  
  102.         return NULL;  
  103.     }  
  104.     char    deviceName[512];  
  105.     char    deviceVendor[512];  
  106.     char    deviceVersion[512];  
  107.     errNum = clGetDeviceInfo(devices[0], CL_DEVICE_VENDOR, sizeof(deviceVendor),  
  108.         deviceVendor, NULL);  
  109.     errNum |= clGetDeviceInfo(devices[0], CL_DEVICE_NAME, sizeof(deviceName),  
  110.         deviceName, NULL);  
  111.     errNum |= clGetDeviceInfo(devices[0], CL_DEVICE_VERSION, sizeof(deviceVersion),  
  112.         deviceVersion, NULL);  
  113.   
  114.     printf(”OpenCL Device Vendor = %s,  OpenCL Device Name = %s,  OpenCL Device Version = %s\n”, deviceVendor, deviceName, deviceVersion);  
  115.     // 在这个例子中,我们只选择第一个可用的设备。在实际的程序,你可能会使用所有可用的设备或基于OpenCL设备查询选择性能最高的设备    
  116.     commandQueue = clCreateCommandQueue(context, devices[0], 0, NULL);  
  117.     if (commandQueue == NULL)  
  118.     {  
  119.         delete[] devices;  
  120.         std::cerr << ”Failed to create commandQueue for device 0”;  
  121.         return NULL;  
  122.     }  
  123.   
  124.     *device = devices[0];  
  125.     delete[] devices;  
  126.     return commandQueue;  
  127. }  
  128.   
  129. //从磁盘加载内核源文件并创建一个程序对象  
  130. cl_program CreateOpenCLDLL::CreateProgram(cl_context context, cl_device_id device, const char* fileName)  
  131. {  
  132.     cl_int errNum;  
  133.     cl_program program;  
  134.   
  135.     std::ifstream kernelFile(fileName, std::ios::in);  
  136.     if (!kernelFile.is_open())  
  137.     {  
  138.         std::cerr << ”Failed to open file for reading: ” << fileName << std::endl;  
  139.         return NULL;  
  140.     }  
  141.   
  142.     std::ostringstream oss;  
  143.     oss << kernelFile.rdbuf();  
  144.   
  145.     std::string srcStdStr = oss.str();  
  146.     const char *srcStr = srcStdStr.c_str();  
  147.     //创建程序对象    
  148.     program = clCreateProgramWithSource(context, 1,  
  149.         (const char**)&srcStr,  
  150.         NULL, NULL);  
  151.     if (program == NULL)  
  152.     {  
  153.         std::cerr << ”Failed to create CL program from source.” << std::endl;  
  154.         return NULL;  
  155.     }  
  156.     //编译内核源代码    
  157.     errNum = clBuildProgram(program, 0, NULL, NULL, NULL, NULL);  
  158.     if (errNum != CL_SUCCESS)  
  159.     {  
  160.         // 编译失败可以通过clGetProgramBuildInfo获取日志    
  161.         char buildLog[16384];  
  162.         clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG,  
  163.             sizeof(buildLog), buildLog, NULL);  
  164.   
  165.         std::cerr << ”Error in kernel: ” << std::endl;  
  166.         std::cerr << buildLog;  
  167.         clReleaseProgram(program);  
  168.         return NULL;  
  169.     }  
  170.   
  171.     return program;  
  172. }  
  173.   
  174. //创建内存对象   
  175. bool CreateOpenCLDLL::CreateMemObjects(cl_context context, cl_mem memObjects[3],float *a, float *b)  
  176. {  
  177.     //创建内存对象    
  178.     memObjects[0] = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,  
  179.         sizeof(float)* KArraySize, a, NULL);  
  180.     memObjects[1] = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,  
  181.         sizeof(float)* KArraySize, b, NULL);  
  182.     memObjects[2] = clCreateBuffer(context, CL_MEM_READ_WRITE,  
  183.         sizeof(float)* KArraySize, NULL, NULL);  
  184.   
  185.     if (memObjects[0] == NULL || memObjects[1] == NULL || memObjects[2] == NULL)  
  186.     {  
  187.         std::cerr << ”Error creating memory objects.” << std::endl;  
  188.         return false;  
  189.     }  
  190.   
  191.     return true;  
  192. }  
  193.   
  194. //清理任何创建OpenCL的资源   
  195. void CreateOpenCLDLL::Cleanup(cl_context context, cl_command_queue commandQueue,  
  196.     cl_program program, cl_kernel kernel, cl_mem memObjects[3])  
  197. {  
  198.     for (int i = 0; i < 3; i++)  
  199.     {  
  200.         if (memObjects[i] != 0)  
  201.             clReleaseMemObject(memObjects[i]);  
  202.     }  
  203.     if (commandQueue != 0)  
  204.         clReleaseCommandQueue(commandQueue);  
  205.   
  206.     if (kernel != 0)  
  207.         clReleaseKernel(kernel);  
  208.   
  209.     if (program != 0)  
  210.         clReleaseProgram(program);  
  211.   
  212.     if (context != 0)  
  213.         clReleaseContext(context);  
  214. }  
  215.   
  216. void CreateOpenCLDLL::Calcaute()  
  217. {  
  218.     // 创建opencl上下文和第一个可用平台    
  219.     m_context = CreateContext();  
  220.     if (m_context == NULL)  
  221.     {  
  222.         std::cerr << ”Failed to create OpenCL context.” << std::endl;  
  223.     }  
  224.   
  225.     // 在创建的一个上下文中选择第一个可用的设备并创建一个命令队列    
  226.     m_commandQueue = CreateCommandQueue(m_context, &m_device);  
  227.     if (m_commandQueue == NULL)  
  228.     {  
  229.         Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);  
  230.     }  
  231.   
  232.     // 创建一个程序对象 HelloWorld.cl kernel source    
  233.     m_program = CreateProgram(m_context, m_device, ”HelloWorld.cl”);  
  234.     if (m_program == NULL)  
  235.     {  
  236.         Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);  
  237.     }  
  238.   
  239.     // 创建内核    
  240.     m_kernel = clCreateKernel(m_program, ”hello_kernel”, NULL);  
  241.     if (m_kernel == NULL)  
  242.     {  
  243.         std::cerr << ”Failed to create kernel” << std::endl;  
  244.         Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);  
  245.     }  
  246.   
  247.     // 创建一个将用作参数内核内存中的对象。首先创建将被用来将参数存储到内核主机存储器阵列    
  248.     float result[KArraySize];  
  249.     float a[KArraySize];  
  250.     float b[KArraySize];  
  251.     for (int i = 0; i < KArraySize; i++)  
  252.     {  
  253.         a[i] = (float)i;  
  254.         b[i] = (float)(i * 2);  
  255.     }  
  256.   
  257.     if (!CreateMemObjects(m_context, m_memObjects, a, b))  
  258.     {  
  259.         Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);  
  260.     }  
  261.   
  262.     // 设置内核参数、执行内核并读回结果    
  263.     m_errNum = clSetKernelArg(m_kernel, 0, sizeof(cl_mem), &m_memObjects[0]);  
  264.     m_errNum |= clSetKernelArg(m_kernel, 1, sizeof(cl_mem), &m_memObjects[1]);  
  265.     m_errNum |= clSetKernelArg(m_kernel, 2, sizeof(cl_mem), &m_memObjects[2]);  
  266.     if (m_errNum != CL_SUCCESS)  
  267.     {  
  268.         std::cerr << ”Error setting kernel arguments.” << std::endl;  
  269.         Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);  
  270.     }  
  271.   
  272.     size_t globalWorkSize[1] = { KArraySize };//让之等于数组的大小    
  273.     size_t localWorkSize[1] = { 1 };  //让之等于1    
  274.   
  275.                                       // 利用命令队列使将在设备执行的内核排队    
  276.   
  277.     m_errNum = clEnqueueNDRangeKernel(m_commandQueue, m_kernel, 1, NULL,  
  278.         globalWorkSize, localWorkSize,  
  279.         0, NULL, NULL);  
  280.     if (m_errNum != CL_SUCCESS)  
  281.     {  
  282.         std::cerr << ”Error queuing kernel for execution.” << std::endl;  
  283.         Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);  
  284.     }  
  285.   
  286.     std::cout << ”Executed program succesfully.” << std::endl;  
  287.     // Read the output buffer back to the Host    
  288.   
  289.   
  290.     m_errNum = clEnqueueReadBuffer(m_commandQueue, m_memObjects[2], CL_TRUE,  
  291.         0, KArraySize * sizeof(float), result,  
  292.         0, NULL, NULL);  
  293.     if (m_errNum != CL_SUCCESS)  
  294.     {  
  295.         std::cerr << ”Error reading result buffer.” << std::endl;  
  296.         Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);  
  297.     }  
  298.   
  299.   
  300.     //输出结果    
  301.     for (int i = 0; i < KArraySize; i++)  
  302.     {  
  303.         std::cout << result[i] << ” ”;  
  304.     }  
  305.     std::cout << std::endl;  
  306.     std::cout << ”Executed program succesfully.” << std::endl;  
  307.     Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);  
  308.   
  309. }  
  310. /* 导出函数定义 */  
  311. CreateOpenCLDLL* GetCreateOpenCLDLL(void)  
  312. {  
  313.     CreateOpenCLDLL* pCreateOpenCLDLL = new CreateOpenCLDLL();  
  314.     return pCreateOpenCLDLL;  
  315. }  
#define CREATEOPENCLDLL_EXPORTS





#include <iostream> #include <fstream> #include <string> #include <vector> #include <sstream> #include "createopencldll.h" using namespace std; const int KArraySize = 1000; CreateOpenCLDLL::CreateOpenCLDLL() { m_context = 0; m_commandQueue = 0; m_program = 0; m_device = 0; m_kernel = 0; m_memObjects[0] = 0; m_memObjects[1] = 0; m_memObjects[2] = 0; m_errNum = 0; } CreateOpenCLDLL::~CreateOpenCLDLL() { } // 选择平台并创建上下文 cl_context CreateOpenCLDLL::CreateContext() { cl_int errNum; cl_uint numPlatforms; cl_platform_id firstPlatformId; cl_context context = NULL; //选择第一个可用的平台 errNum = clGetPlatformIDs(1, &firstPlatformId, &numPlatforms); if (errNum != CL_SUCCESS || numPlatforms <= 0) { std::cerr << "Failed to find any OpenCL platforms." << std::endl; return NULL; } // 创建一个opencl上下文,成功则使用GUP上下文,否则使用cpu cl_context_properties contextProperties[] = { CL_CONTEXT_PLATFORM, (cl_context_properties)firstPlatformId, 0 }; context = clCreateContextFromType(contextProperties, CL_DEVICE_TYPE_GPU, NULL, NULL, &errNum); if (errNum != CL_SUCCESS) { std::cout << "Could not create GPU context, trying CPU..." << std::endl; context = clCreateContextFromType(contextProperties, CL_DEVICE_TYPE_CPU, NULL, NULL, &errNum); if (errNum != CL_SUCCESS) { std::cerr << "Failed to create an OpenCL GPU or CPU context." << std::endl; return NULL; } } return context; } //选择第一个可用的设备并创建一个命令队列 cl_command_queue CreateOpenCLDLL::CreateCommandQueue(cl_context context, cl_device_id *device) { cl_int errNum; cl_device_id *devices; cl_command_queue commandQueue = NULL; size_t deviceBufferSize = -1; //这个clGetContextInfo获得设备缓冲区的大小 errNum = clGetContextInfo(context, CL_CONTEXT_DEVICES, 0, NULL, &deviceBufferSize); if (errNum != CL_SUCCESS) { std::cerr << "Failed call to clGetContextInfo(...,GL_CONTEXT_DEVICES,...)"; return NULL; } if (deviceBufferSize <= 0) { std::cerr << "No devices available."; return NULL; } //为设备缓冲区分配内存,这个clGetContextInfo用来获得上下文中所有可用的设备 devices = new cl_device_id[deviceBufferSize / sizeof(cl_device_id)]; errNum = clGetContextInfo(context, CL_CONTEXT_DEVICES, deviceBufferSize, devices, NULL); if (errNum != CL_SUCCESS) { delete[] devices; std::cerr << "Failed to get device IDs"; return NULL; } char deviceName[512]; char deviceVendor[512]; char deviceVersion[512]; errNum = clGetDeviceInfo(devices[0], CL_DEVICE_VENDOR, sizeof(deviceVendor), deviceVendor, NULL); errNum |= clGetDeviceInfo(devices[0], CL_DEVICE_NAME, sizeof(deviceName), deviceName, NULL); errNum |= clGetDeviceInfo(devices[0], CL_DEVICE_VERSION, sizeof(deviceVersion), deviceVersion, NULL); printf("OpenCL Device Vendor = %s, OpenCL Device Name = %s, OpenCL Device Version = %s\n", deviceVendor, deviceName, deviceVersion); // 在这个例子中,我们只选择第一个可用的设备。在实际的程序,你可能会使用所有可用的设备或基于OpenCL设备查询选择性能最高的设备 commandQueue = clCreateCommandQueue(context, devices[0], 0, NULL); if (commandQueue == NULL) { delete[] devices; std::cerr << "Failed to create commandQueue for device 0"; return NULL; } *device = devices[0]; delete[] devices; return commandQueue; } //从磁盘加载内核源文件并创建一个程序对象 cl_program CreateOpenCLDLL::CreateProgram(cl_context context, cl_device_id device, const char* fileName) { cl_int errNum; cl_program program; std::ifstream kernelFile(fileName, std::ios::in); if (!kernelFile.is_open()) { std::cerr << "Failed to open file for reading: " << fileName << std::endl; return NULL; } std::ostringstream oss; oss << kernelFile.rdbuf(); std::string srcStdStr = oss.str(); const char *srcStr = srcStdStr.c_str(); //创建程序对象 program = clCreateProgramWithSource(context, 1, (const char**)&srcStr, NULL, NULL); if (program == NULL) { std::cerr << "Failed to create CL program from source." << std::endl; return NULL; } //编译内核源代码 errNum = clBuildProgram(program, 0, NULL, NULL, NULL, NULL); if (errNum != CL_SUCCESS) { // 编译失败可以通过clGetProgramBuildInfo获取日志 char buildLog[16384]; clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, sizeof(buildLog), buildLog, NULL); std::cerr << "Error in kernel: " << std::endl; std::cerr << buildLog; clReleaseProgram(program); return NULL; } return program; } //创建内存对象 bool CreateOpenCLDLL::CreateMemObjects(cl_context context, cl_mem memObjects[3],float *a, float *b) { //创建内存对象 memObjects[0] = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float)* KArraySize, a, NULL); memObjects[1] = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float)* KArraySize, b, NULL); memObjects[2] = clCreateBuffer(context, CL_MEM_READ_WRITE, sizeof(float)* KArraySize, NULL, NULL); if (memObjects[0] == NULL || memObjects[1] == NULL || memObjects[2] == NULL) { std::cerr << "Error creating memory objects." << std::endl; return false; } return true; } //清理任何创建OpenCL的资源 void CreateOpenCLDLL::Cleanup(cl_context context, cl_command_queue commandQueue, cl_program program, cl_kernel kernel, cl_mem memObjects[3]) { for (int i = 0; i < 3; i++) { if (memObjects[i] != 0) clReleaseMemObject(memObjects[i]); } if (commandQueue != 0) clReleaseCommandQueue(commandQueue); if (kernel != 0) clReleaseKernel(kernel); if (program != 0) clReleaseProgram(program); if (context != 0) clReleaseContext(context); } void CreateOpenCLDLL::Calcaute() { // 创建opencl上下文和第一个可用平台 m_context = CreateContext(); if (m_context == NULL) { std::cerr << "Failed to create OpenCL context." << std::endl; } // 在创建的一个上下文中选择第一个可用的设备并创建一个命令队列 m_commandQueue = CreateCommandQueue(m_context, &m_device); if (m_commandQueue == NULL) { Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects); } // 创建一个程序对象 HelloWorld.cl kernel source m_program = CreateProgram(m_context, m_device, "HelloWorld.cl"); if (m_program == NULL) { Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects); } // 创建内核 m_kernel = clCreateKernel(m_program, "hello_kernel", NULL); if (m_kernel == NULL) { std::cerr << "Failed to create kernel" << std::endl; Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects); } // 创建一个将用作参数内核内存中的对象。首先创建将被用来将参数存储到内核主机存储器阵列 float result[KArraySize]; float a[KArraySize]; float b[KArraySize]; for (int i = 0; i < KArraySize; i++) { a[i] = (float)i; b[i] = (float)(i * 2); } if (!CreateMemObjects(m_context, m_memObjects, a, b)) { Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects); } // 设置内核参数、执行内核并读回结果 m_errNum = clSetKernelArg(m_kernel, 0, sizeof(cl_mem), &m_memObjects[0]); m_errNum |= clSetKernelArg(m_kernel, 1, sizeof(cl_mem), &m_memObjects[1]); m_errNum |= clSetKernelArg(m_kernel, 2, sizeof(cl_mem), &m_memObjects[2]); if (m_errNum != CL_SUCCESS) { std::cerr << "Error setting kernel arguments." << std::endl; Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects); } size_t globalWorkSize[1] = { KArraySize };//让之等于数组的大小 size_t localWorkSize[1] = { 1 }; //让之等于1 // 利用命令队列使将在设备执行的内核排队 m_errNum = clEnqueueNDRangeKernel(m_commandQueue, m_kernel, 1, NULL, globalWorkSize, localWorkSize, 0, NULL, NULL); if (m_errNum != CL_SUCCESS) { std::cerr << "Error queuing kernel for execution." << std::endl; Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects); } std::cout << "Executed program succesfully." << std::endl; // Read the output buffer back to the Host m_errNum = clEnqueueReadBuffer(m_commandQueue, m_memObjects[2], CL_TRUE, 0, KArraySize * sizeof(float), result, 0, NULL, NULL); if (m_errNum != CL_SUCCESS) { std::cerr << "Error reading result buffer." << std::endl; Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects); } //输出结果 for (int i = 0; i < KArraySize; i++) { std::cout << result[i] << " "; } std::cout << std::endl; std::cout << "Executed program succesfully." << std::endl; Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects); } /* 导出函数定义 */ CreateOpenCLDLL* GetCreateOpenCLDLL(void) { CreateOpenCLDLL* pCreateOpenCLDLL = new CreateOpenCLDLL(); return pCreateOpenCLDLL; }


(4)生成代码

按生成后,在的debug或者release文件夹中,会生成相应的DLL文件和LIB文件,如下图生成buildopencldll.dllbuildopencldll.lib



2. C++ 多个类的DLL封装

封装好一个类之后,在后面的类可以调用这个类生成的dll,再封装新类的dll。具体过程可以参考第1步。

在工程中添加头文件createopencldll.h到新的工程目录。在debug或者release文件中复制buildopencldll.lib到新的工程目录如下:

在项目中,属性->连接器->输入->附加依赖项中,添加buildopencldll.lib

将buildopencldll.dll复制到新工程的debug或者release目录下。
具体新项目的createopencvdll.h代码如下:
  1. #pragma once  
  2. #ifdef CREATEOPENCVDLL_EXPORTS  
  3. #define CREATEOPENCVDLL_API __declspec(dllexport)   
  4. #else  
  5. #define CREATEOPENCVDLL_API __declspec(dllimport)   
  6. #endif  
  7.   
  8. #include “createopencldll.h”  
  9.   
  10. #include “opencv2\opencv_modules.hpp”  
  11. #include “opencv2\highgui\highgui.hpp”  
  12. #include “opencv2\core\core.hpp”  
  13.   
  14. using namespace cv;  
  15.   
  16. class CREATEOPENCVDLL_API CreateOpenCVDLL  
  17. {  
  18. public:  
  19.     CreateOpenCVDLL();  
  20.     ~CreateOpenCVDLL();  
  21.     void showImage();  
  22.   
  23. private:  
  24.     Mat m_image;  
  25. };  
#pragma once




#ifdef CREATEOPENCVDLL_EXPORTS #define CREATEOPENCVDLL_API __declspec(dllexport) #else #define CREATEOPENCVDLL_API __declspec(dllimport) #endif #include "createopencldll.h" #include "opencv2\opencv_modules.hpp" #include "opencv2\highgui\highgui.hpp" #include "opencv2\core\core.hpp" using namespace cv; class CREATEOPENCVDLL_API CreateOpenCVDLL { public: CreateOpenCVDLL(); ~CreateOpenCVDLL(); void showImage(); private: Mat m_image; };
createopencvdll.cpp代码如下:
  1. #define CREATEOPENCVDLL_EXPORTS  
  2.   
  3. #include “createopencvdll.h”  
  4.   
  5. CreateOpenCVDLL::CreateOpenCVDLL()  
  6. {  
  7.   
  8. }  
  9.   
  10. CreateOpenCVDLL::~CreateOpenCVDLL()  
  11. {  
  12.   
  13. }  
  14.   
  15. void CreateOpenCVDLL::showImage()  
  16. {  
  17.     m_image = imread(”1.jpg”);  
  18.     namedWindow(”image”, CV_WINDOW_AUTOSIZE);  
  19.     imshow(”image”, m_image);  
  20.     cvWaitKey(0);  
  21. }  
#define CREATEOPENCVDLL_EXPORTS





#include "createopencvdll.h" CreateOpenCVDLL::CreateOpenCVDLL() { } CreateOpenCVDLL::~CreateOpenCVDLL() { } void CreateOpenCVDLL::showImage() { m_image = imread("1.jpg"); namedWindow("image", CV_WINDOW_AUTOSIZE); imshow("image", m_image); cvWaitKey(0); }

3. DLL的调用

新建一个win32控制台应用程序,选择控制台应用程序,工程名字为:testdll,新建main函数。


(1)隐式链接

方法1:

在工程中添加头文件createopencldll.h到新的工程目录。在debug或者release文件中复制buildopencldll.lib到新的工程目录如下:

在项目中,属性->连接器->输入->附加依赖项中,添加buildopencldll.lib

将buildopencldll.dll复制到新工程的debug或者release目录下。
测试代码如下:
  1. #include “createopencldll.h”  
  2.   
  3. int main()  
  4. {  
  5.     CreateOpenCLDLL createopencldll;  
  6.     createopencldll.Calcaute();  
  7.     return 0;  
  8. }  
#include "createopencldll.h"

int main()
{
    CreateOpenCLDLL createopencldll;
    createopencldll.Calcaute();
    return 0;
}
结果图如下:


方法2:

在方法1的基础下,不添加buildopencldll.lib到附加依赖项中,使用代码的方式添加,即在原来的代码下多添加: #pragma comment( lib, “buildopencldll.lib”)代码如下:
  1. #pragma comment(lib, “buildopencldll.lib”)  
  2.   
  3. #include “createopencldll.h”  
  4.   
  5. int main()  
  6. {  
  7.     CreateOpenCLDLL createopencldll;  
  8.     createopencldll.Calcaute();  
  9.     return 0;  
  10. }  
#pragma comment(lib, "buildopencldll.lib")





#include "createopencldll.h" int main() { CreateOpenCLDLL createopencldll; createopencldll.Calcaute(); return 0; }

(2)显式链接

参考博客:http://blog.csdn.net/chollima/article/details/5324808

这个博客先定义虚基类,然后到基类等的实现,这样比较好。

这种方法不需要createopencldll.lib,将createopencldll.dll复制到debug或者release目录。

具体实现代码如下:
增加一个虚基类:createdll.h
  1. #pragma once  
  2. #ifndef _CREATEDLL_H_  
  3. #define _CREATEDLL_H_  
  4.   
  5. #ifdef CREATEDLL_EXPORTS  
  6. #define CREATEDLL_API __declspec(dllexport)   
  7. #else  
  8. #define CREATEDLL_API __declspec(dllimport)   
  9. #endif  
  10. /* 
  11. 设计这个接口类的作用: 
  12. 能采用动态调用方式使用这个类 
  13. */  
  14. #ifdef __APPLE__  
  15. #include <OpenCL/cl.h>  
  16. #else  
  17. #include <CL/cl.h>  
  18. #endif  
  19.   
  20. class CREATEDLL_API CreateDLL  
  21. {  
  22. public:  
  23.     CreateDLL() {}  
  24.     virtual ~CreateDLL(void) {};  
  25.     //  选择平台并创建上下文    
  26.     virtual cl_context CreateContext() = 0;  
  27.     virtual cl_command_queue CreateCommandQueue(cl_context context, cl_device_id *device) = 0;  
  28.     virtual cl_program CreateProgram(cl_context context, cl_device_id device, const char* fileName) = 0;  
  29.     virtual bool CreateMemObjects(cl_context context, cl_mem memObjects[3], float *a, float *b) = 0;  
  30.     virtual void Cleanup(cl_context context, cl_command_queue commandQueue,  
  31.         cl_program program, cl_kernel kernel, cl_mem memObjects[3]) = 0;  
  32.     virtual void Calcaute() = 0;  
  33.   
  34. };  
  35. /* 导出函数声明 */  
  36. extern “C”  
  37. {  
  38.     CREATEDLL_API CreateDLL* GetCreateDLL(void);  
  39.     typedef CreateDLL* (*PFNGetCreateOpenCLDLL)(void);  
  40. }  
  41.   
  42. #endif    
#pragma once




ifndef CREATEDLL_H

define CREATEDLL_H

ifdef CREATEDLL_EXPORTS

define CREATEDLL_API __declspec(dllexport)

else

define CREATEDLL_API __declspec(dllimport)

endif

/*
设计这个接口类的作用:
能采用动态调用方式使用这个类
*/

ifdef APPLE

include <OpenCL/cl.h>

else

include <CL/cl.h>

endif

class CREATEDLL_API CreateDLL
{
public:
CreateDLL() {}
virtual ~CreateDLL(void) {};
// 选择平台并创建上下文
virtual cl_context CreateContext() = 0;
virtual cl_command_queue CreateCommandQueue(cl_context context, cl_device_id *device) = 0;
virtual cl_program CreateProgram(cl_context context, cl_device_id device, const char* fileName) = 0;
virtual bool CreateMemObjects(cl_context context, cl_mem memObjects[3], float *a, float *b) = 0;
virtual void Cleanup(cl_context context, cl_command_queue commandQueue,
cl_program program, cl_kernel kernel, cl_mem memObjects[3]) = 0;
virtual void Calcaute() = 0;

};
/* 导出函数声明 */
extern "C"
{
CREATEDLL_API CreateDLL* GetCreateDLL(void);
typedef CreateDLL* (*PFNGetCreateOpenCLDLL)(void);
}

endif


createopencldll.h 更改如下:


  1. #pragma once  
  2. #define CREATEDLL_EXPORTS  
  3. #include “createdll.h”  
  4.   
  5. class CREATEDLL_API CreateOpenCLDLL :public CreateDLL  
  6. {  
  7. public:  
  8.     CreateOpenCLDLL();  
  9.     ~CreateOpenCLDLL();  
  10.     //  选择平台并创建上下文    
  11.     cl_context CreateContext();  
  12.     cl_command_queue CreateCommandQueue(cl_context context, cl_device_id *device);  
  13.     cl_program CreateProgram(cl_context context, cl_device_id device, const char* fileName);  
  14.     bool CreateMemObjects(cl_context context, cl_mem memObjects[3], float *a, float *b);  
  15.     void Cleanup(cl_context context, cl_command_queue commandQueue,  
  16.         cl_program program, cl_kernel kernel, cl_mem memObjects[3]);  
  17.     void Calcaute();  
  18.   
  19. private:  
  20.     cl_context m_context;  
  21.     cl_command_queue m_commandQueue;  
  22.     cl_program m_program;  
  23.     cl_device_id m_device;  
  24.     cl_kernel m_kernel;  
  25.     cl_mem m_memObjects[3];  
  26.     cl_int m_errNum;  
  27.     cl_event m_prof_event;  
  28.     cl_int   m_status;  
  29.   
  30. };  
  31.   
  32.   
  33. //#pragma once  
  34. //#ifndef _CREATEOPENCLDLL_H_  
  35. //#define _CREATEOPENCLDLL_H_  
  36. //  
  37. //#ifdef CREATEOPENCLDLL_EXPORTS  
  38. //#define CREATEOPENCLDLL_API __declspec(dllexport)   
  39. //#else  
  40. //#define CREATEOPENCLDLL_API __declspec(dllimport)   
  41. //#endif  
  42. //  
  43. //#ifdef __APPLE__  
  44. //#include <OpenCL/cl.h>  
  45. //#else  
  46. //#include <CL/cl.h>  
  47. //#endif  
  48. //  
  49. //  
  50. //class CREATEOPENCLDLL_API CreateOpenCLDLL  
  51. //{  
  52. //public:  
  53. //  CreateOpenCLDLL();  
  54. //  ~CreateOpenCLDLL();  
  55. //  //  选择平台并创建上下文    
  56. //  cl_context CreateContext();  
  57. //  cl_command_queue CreateCommandQueue(cl_context context, cl_device_id *device);  
  58. //  cl_program CreateProgram(cl_context context, cl_device_id device, const char* fileName);  
  59. //  bool CreateMemObjects(cl_context context, cl_mem memObjects[3], float *a, float *b);  
  60. //  void Cleanup(cl_context context, cl_command_queue commandQueue,  
  61. //      cl_program program, cl_kernel kernel, cl_mem memObjects[3]);  
  62. //  void Calcaute();  
  63. //  
  64. //private:  
  65. //  cl_context m_context;  
  66. //  cl_command_queue m_commandQueue;  
  67. //  cl_program m_program;  
  68. //  cl_device_id m_device;  
  69. //  cl_kernel m_kernel;  
  70. //  cl_mem m_memObjects[3];  
  71. //  cl_int m_errNum;  
  72. //};  
  73. ///* 导出函数声明 */  
  74. //extern “C”  
  75. //{  
  76. //  CREATEOPENCLDLL_API CreateOpenCLDLL* GetCreateOpenCLDLL(void);  
  77. //  typedef CreateOpenCLDLL* (*PFNGetCreateOpenCLDLL)(void);  
  78. //}  
  79. //  
  80. //#endif    
#pragma once




define CREATEDLL_EXPORTS

include "createdll.h"

class CREATEDLL_API CreateOpenCLDLL :public CreateDLL
{
public:
CreateOpenCLDLL();
~CreateOpenCLDLL();
// 选择平台并创建上下文
cl_context CreateContext();
cl_command_queue CreateCommandQueue(cl_context context, cl_device_id *device);
cl_program CreateProgram(cl_context context, cl_device_id device, const char* fileName);
bool CreateMemObjects(cl_context context, cl_mem memObjects[3], float *a, float *b);
void Cleanup(cl_context context, cl_command_queue commandQueue,
cl_program program, cl_kernel kernel, cl_mem memObjects[3]);
void Calcaute();

private:
cl_context m_context;
cl_command_queue m_commandQueue;
cl_program m_program;
cl_device_id m_device;
cl_kernel m_kernel;
cl_mem m_memObjects[3];
cl_int m_errNum;
cl_event m_prof_event;
cl_int m_status;

};

//#pragma once
//#ifndef CREATEOPENCLDLL_H
//#define CREATEOPENCLDLL_H
//
//#ifdef CREATEOPENCLDLL_EXPORTS
//#define CREATEOPENCLDLL_API __declspec(dllexport)
//#else
//#define CREATEOPENCLDLL_API __declspec(dllimport)
//#endif
//
//#ifdef APPLE
//#include <OpenCL/cl.h>
//#else
//#include <CL/cl.h>
//#endif
//
//
//class CREATEOPENCLDLL_API CreateOpenCLDLL
//{
//public:
// CreateOpenCLDLL();
// ~CreateOpenCLDLL();
// // 选择平台并创建上下文
// cl_context CreateContext();
// cl_command_queue CreateCommandQueue(cl_context context, cl_device_id *device);
// cl_program CreateProgram(cl_context context, cl_device_id device, const char* fileName);
// bool CreateMemObjects(cl_context context, cl_mem memObjects[3], float *a, float *b);
// void Cleanup(cl_context context, cl_command_queue commandQueue,
// cl_program program, cl_kernel kernel, cl_mem memObjects[3]);
// void Calcaute();
//
//private:
// cl_context m_context;
// cl_command_queue m_commandQueue;
// cl_program m_program;
// cl_device_id m_device;
// cl_kernel m_kernel;
// cl_mem m_memObjects[3];
// cl_int m_errNum;
//};
///* 导出函数声明 */
//extern "C"
//{
// CREATEOPENCLDLL_API CreateOpenCLDLL* GetCreateOpenCLDLL(void);
// typedef CreateOpenCLDLL* (*PFNGetCreateOpenCLDLL)(void);
//}
//
//#endif


createopencldll.cpp更改如下:


  1. //#define CREATEOPENCLDLL_EXPORTS  
  2.   
  3. #include <iostream>  
  4. #include <fstream>  
  5. #include <string>  
  6. #include <vector>  
  7. #include <sstream>  
  8. #include <time.h>  
  9.   
  10. #include “createopencldll.h”  
  11.   
  12. using namespace std;  
  13.   
  14. const int KArraySize = 1000;  
  15.   
  16. CreateOpenCLDLL::CreateOpenCLDLL()  
  17. {  
  18.     m_context = 0;  
  19.     m_commandQueue = 0;  
  20.     m_program = 0;  
  21.     m_device = 0;  
  22.     m_kernel = 0;  
  23.     m_memObjects[0] = 0;  
  24.     m_memObjects[1] = 0;  
  25.     m_memObjects[2] = 0;  
  26.     m_errNum = 0;  
  27. }  
  28.   
  29. CreateOpenCLDLL::~CreateOpenCLDLL()  
  30. {  
  31.   
  32. }  
  33.   
  34. //  选择平台并创建上下文    
  35. cl_context CreateOpenCLDLL::CreateContext()  
  36. {  
  37.     cl_int errNum;  
  38.     cl_uint numPlatforms;  
  39.     cl_platform_id firstPlatformId;  
  40.     cl_context context = NULL;  
  41.   
  42.     //选择第一个可用的平台      
  43.     errNum = clGetPlatformIDs(1, &firstPlatformId, &numPlatforms);  
  44.     if (errNum != CL_SUCCESS || numPlatforms <= 0)  
  45.     {  
  46.         std::cerr << ”Failed to find any OpenCL platforms.” << std::endl;  
  47.         return NULL;  
  48.     }  
  49.   
  50.     // 创建一个opencl上下文,成功则使用GUP上下文,否则使用cpu      
  51.     cl_context_properties contextProperties[] =  
  52.     {  
  53.         CL_CONTEXT_PLATFORM,  
  54.         (cl_context_properties)firstPlatformId,  
  55.         0  
  56.     };  
  57.     context = clCreateContextFromType(contextProperties, CL_DEVICE_TYPE_GPU,  
  58.         NULL, NULL, &errNum);  
  59.     if (errNum != CL_SUCCESS)  
  60.     {  
  61.         std::cout << ”Could not create GPU context, trying CPU…” << std::endl;  
  62.         context = clCreateContextFromType(contextProperties, CL_DEVICE_TYPE_CPU,  
  63.             NULL, NULL, &errNum);  
  64.         if (errNum != CL_SUCCESS)  
  65.         {  
  66.             std::cerr << ”Failed to create an OpenCL GPU or CPU context.” << std::endl;  
  67.             return NULL;  
  68.         }  
  69.     }  
  70.   
  71.     return context;  
  72. }  
  73.   
  74. //选择第一个可用的设备并创建一个命令队列    
  75. cl_command_queue CreateOpenCLDLL::CreateCommandQueue(cl_context context, cl_device_id *device)  
  76. {  
  77.     cl_int errNum;  
  78.     cl_device_id *devices;  
  79.     cl_command_queue commandQueue = NULL;  
  80.     size_t deviceBufferSize = -1;  
  81.   
  82.     //这个clGetContextInfo获得设备缓冲区的大小      
  83.     errNum = clGetContextInfo(context, CL_CONTEXT_DEVICES, 0, NULL, &deviceBufferSize);  
  84.     if (errNum != CL_SUCCESS)  
  85.     {  
  86.         std::cerr << ”Failed call to clGetContextInfo(…,GL_CONTEXT_DEVICES,…)”;  
  87.         return NULL;  
  88.     }  
  89.   
  90.     if (deviceBufferSize <= 0)  
  91.     {  
  92.         std::cerr << ”No devices available.”;  
  93.         return NULL;  
  94.     }  
  95.   
  96.     //为设备缓冲区分配内存,这个clGetContextInfo用来获得上下文中所有可用的设备      
  97.     devices = new cl_device_id[deviceBufferSize / sizeof(cl_device_id)];  
  98.     errNum = clGetContextInfo(context, CL_CONTEXT_DEVICES, deviceBufferSize, devices, NULL);  
  99.     if (errNum != CL_SUCCESS)  
  100.     {  
  101.         delete[] devices;  
  102.         std::cerr << ”Failed to get device IDs”;  
  103.         return NULL;  
  104.     }  
  105.     char    deviceName[512];  
  106.     char    deviceVendor[512];  
  107.     char    deviceVersion[512];  
  108.     errNum = clGetDeviceInfo(devices[0], CL_DEVICE_VENDOR, sizeof(deviceVendor),  
  109.         deviceVendor, NULL);  
  110.     errNum |= clGetDeviceInfo(devices[0], CL_DEVICE_NAME, sizeof(deviceName),  
  111.         deviceName, NULL);  
  112.     errNum |= clGetDeviceInfo(devices[0], CL_DEVICE_VERSION, sizeof(deviceVersion),  
  113.         deviceVersion, NULL);  
  114.   
  115.     printf(”OpenCL Device Vendor = %s,  OpenCL Device Name = %s,  OpenCL Device Version = %s\n”, deviceVendor, deviceName, deviceVersion);  
  116.     // 在这个例子中,我们只选择第一个可用的设备。在实际的程序,你可能会使用所有可用的设备或基于OpenCL设备查询选择性能最高的设备      
  117.     commandQueue = clCreateCommandQueue(context, devices[0], 0, NULL);  
  118.     if (commandQueue == NULL)  
  119.     {  
  120.         delete[] devices;  
  121.         std::cerr << ”Failed to create commandQueue for device 0”;  
  122.         return NULL;  
  123.     }  
  124.   
  125.     *device = devices[0];  
  126.     delete[] devices;  
  127.     return commandQueue;  
  128. }  
  129.   
  130. //从磁盘加载内核源文件并创建一个程序对象    
  131. cl_program CreateOpenCLDLL::CreateProgram(cl_context context, cl_device_id device, const char* fileName)  
  132. {  
  133.     cl_int errNum;  
  134.     cl_program program;  
  135.   
  136.     std::ifstream kernelFile(fileName, std::ios::in);  
  137.     if (!kernelFile.is_open())  
  138.     {  
  139.         std::cerr << ”Failed to open file for reading: ” << fileName << std::endl;  
  140.         return NULL;  
  141.     }  
  142.   
  143.     std::ostringstream oss;  
  144.     oss << kernelFile.rdbuf();  
  145.   
  146.     std::string srcStdStr = oss.str();  
  147.     const char *srcStr = srcStdStr.c_str();  
  148.     //创建程序对象      
  149.     program = clCreateProgramWithSource(context, 1,  
  150.         (const char**)&srcStr,  
  151.         NULL, NULL);  
  152.     if (program == NULL)  
  153.     {  
  154.         std::cerr << ”Failed to create CL program from source.” << std::endl;  
  155.         return NULL;  
  156.     }  
  157.     //编译内核源代码      
  158.     errNum = clBuildProgram(program, 0, NULL, NULL, NULL, NULL);  
  159.     if (errNum != CL_SUCCESS)  
  160.     {  
  161.         // 编译失败可以通过clGetProgramBuildInfo获取日志      
  162.         char buildLog[16384];  
  163.         clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG,  
  164.             sizeof(buildLog), buildLog, NULL);  
  165.   
  166.         std::cerr << ”Error in kernel: ” << std::endl;  
  167.         std::cerr << buildLog;  
  168.         clReleaseProgram(program);  
  169.         return NULL;  
  170.     }  
  171.   
  172.     return program;  
  173. }  
  174.   
  175. //创建内存对象     
  176. bool CreateOpenCLDLL::CreateMemObjects(cl_context context, cl_mem memObjects[3], float *a, float *b)  
  177. {  
  178.     //创建内存对象      
  179.     memObjects[0] = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,  
  180.         sizeof(float)* KArraySize, a, NULL);  
  181.     memObjects[1] = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,  
  182.         sizeof(float)* KArraySize, b, NULL);  
  183.     memObjects[2] = clCreateBuffer(context, CL_MEM_READ_WRITE,  
  184.         sizeof(float)* KArraySize, NULL, NULL);  
  185.   
  186.     if (memObjects[0] == NULL || memObjects[1] == NULL || memObjects[2] == NULL)  
  187.     {  
  188.         std::cerr << ”Error creating memory objects.” << std::endl;  
  189.         return false;  
  190.     }  
  191.   
  192.     return true;  
  193. }  
  194.   
  195. //清理任何创建OpenCL的资源     
  196. void CreateOpenCLDLL::Cleanup(cl_context context, cl_command_queue commandQueue,  
  197.     cl_program program, cl_kernel kernel, cl_mem memObjects[3])  
  198. {  
  199.     for (int i = 0; i < 3; i++)  
  200.     {  
  201.         if (memObjects[i] != 0)  
  202.             clReleaseMemObject(memObjects[i]);  
  203.     }  
  204.     if (commandQueue != 0)  
  205.         clReleaseCommandQueue(commandQueue);  
  206.   
  207.     if (kernel != 0)  
  208.         clReleaseKernel(kernel);  
  209.   
  210.     if (program != 0)  
  211.         clReleaseProgram(program);  
  212.   
  213.     if (context != 0)  
  214.         clReleaseContext(context);  
  215. }  
  216.   
  217. void CreateOpenCLDLL::Calcaute()  
  218. {  
  219.     // 创建opencl上下文和第一个可用平台      
  220.     m_context = CreateContext();  
  221.     if (m_context == NULL)  
  222.     {  
  223.         std::cerr << ”Failed to create OpenCL context.” << std::endl;  
  224.     }  
  225.   
  226.     // 在创建的一个上下文中选择第一个可用的设备并创建一个命令队列      
  227.     m_commandQueue = CreateCommandQueue(m_context, &m_device);  
  228.     if (m_commandQueue == NULL)  
  229.     {  
  230.         Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);  
  231.     }  
  232.   
  233.     // 创建一个程序对象 HelloWorld.cl kernel source      
  234.     m_program = CreateProgram(m_context, m_device, ”HelloWorld.cl”);  
  235.     if (m_program == NULL)  
  236.     {  
  237.         Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);  
  238.     }  
  239.   
  240.     // 创建内核      
  241.     m_kernel = clCreateKernel(m_program, ”hello_kernel”, NULL);  
  242.     if (m_kernel == NULL)  
  243.     {  
  244.         std::cerr << ”Failed to create kernel” << std::endl;  
  245.         Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);  
  246.     }  
  247.   
  248.     // 创建一个将用作参数内核内存中的对象。首先创建将被用来将参数存储到内核主机存储器阵列      
  249.     float result[KArraySize];  
  250.     float a[KArraySize];  
  251.     float b[KArraySize];  
  252.     for (int i = 0; i < KArraySize; i++)  
  253.     {  
  254.         a[i] = (float)i;  
  255.         b[i] = (float)(i * 2);  
  256.     }  
  257.   
  258.     if (!CreateMemObjects(m_context, m_memObjects, a, b))  
  259.     {  
  260.         Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);  
  261.     }  
  262.   
  263.     // 设置内核参数、执行内核并读回结果      
  264.     m_errNum = clSetKernelArg(m_kernel, 0, sizeof(cl_mem), &m_memObjects[0]);  
  265.     m_errNum |= clSetKernelArg(m_kernel, 1, sizeof(cl_mem), &m_memObjects[1]);  
  266.     m_errNum |= clSetKernelArg(m_kernel, 2, sizeof(cl_mem), &m_memObjects[2]);  
  267.     if (m_errNum != CL_SUCCESS)  
  268.     {  
  269.         std::cerr << ”Error setting kernel arguments.” << std::endl;  
  270.         Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);  
  271.     }  
  272.   
  273.     size_t globalWorkSize[1] = { KArraySize };//让之等于数组的大小      
  274.     size_t localWorkSize[1] = { 1 };  //让之等于1      
  275.   
  276.                                       // 利用命令队列使将在设备执行的内核排队      
  277.   
  278.     m_errNum = clEnqueueNDRangeKernel(m_commandQueue, m_kernel, 1, NULL,  
  279.         globalWorkSize, localWorkSize,  
  280.         0, NULL, NULL);  
  281.     if (m_errNum != CL_SUCCESS)  
  282.     {  
  283.         std::cerr << ”Error queuing kernel for execution.” << std::endl;  
  284.         Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);  
  285.     }  
  286.   
  287.     std::cout << ”Executed program succesfully.” << std::endl;  
  288.     // Read the output buffer back to the Host      
  289.   
  290.   
  291.     m_errNum = clEnqueueReadBuffer(m_commandQueue, m_memObjects[2], CL_TRUE,  
  292.         0, KArraySize * sizeof(float), result,  
  293.         0, NULL, NULL);  
  294.     if (m_errNum != CL_SUCCESS)  
  295.     {  
  296.         std::cerr << ”Error reading result buffer.” << std::endl;  
  297.         Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);  
  298.     }  
  299.   
  300.   
  301.     //输出结果      
  302.     for (int i = 0; i < KArraySize; i++)  
  303.     {  
  304.         std::cout << result[i] << ” ”;  
  305.     }  
  306.     std::cout << std::endl;  
  307.     std::cout << ”Executed program succesfully.” << std::endl;  
  308.     Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);  
  309.   
  310. }  
  311. ///* 导出函数定义 */  
  312. //CreateOpenCLDLL* GetCreateOpenCLDLL(void)  
  313. //{  
  314. //  CreateOpenCLDLL* pCreateOpenCLDLL = new CreateOpenCLDLL();  
  315. //  return pCreateOpenCLDLL;  
  316. //}  
  317.   
  318. /* 导出函数定义 */  
  319. CreateDLL* GetCreateDLL(void)  
  320. {  
  321.     CreateDLL* pCreateDLL = new CreateOpenCLDLL();  
  322.     return pCreateDLL;  
  323. }  
//#define CREATEOPENCLDLL_EXPORTS





include <iostream>

include <fstream>

include <string>

include <vector>

include <sstream>

include <time.h>

include "createopencldll.h"

using namespace std;

const int KArraySize = 1000;

CreateOpenCLDLL::CreateOpenCLDLL()
{
m_context = 0;
m_commandQueue = 0;
m_program = 0;
m_device = 0;
m_kernel = 0;
m_memObjects[0] = 0;
m_memObjects[1] = 0;
m_memObjects[2] = 0;
m_errNum = 0;
}

CreateOpenCLDLL::~CreateOpenCLDLL()
{

}

// 选择平台并创建上下文
cl_context CreateOpenCLDLL::CreateContext()
{
cl_int errNum;
cl_uint numPlatforms;
cl_platform_id firstPlatformId;
cl_context context = NULL;

//选择第一个可用的平台    
errNum = clGetPlatformIDs(1, &amp;firstPlatformId, &amp;numPlatforms);
if (errNum != CL_SUCCESS || numPlatforms &lt;= 0)
{
    std::cerr &lt;&lt; "Failed to find any OpenCL platforms." &lt;&lt; std::endl;
    return NULL;
}

// 创建一个opencl上下文,成功则使用GUP上下文,否则使用cpu    
cl_context_properties contextProperties[] =
{
    CL_CONTEXT_PLATFORM,
    (cl_context_properties)firstPlatformId,
    0
};
context = clCreateContextFromType(contextProperties, CL_DEVICE_TYPE_GPU,
    NULL, NULL, &amp;errNum);
if (errNum != CL_SUCCESS)
{
    std::cout &lt;&lt; "Could not create GPU context, trying CPU..." &lt;&lt; std::endl;
    context = clCreateContextFromType(contextProperties, CL_DEVICE_TYPE_CPU,
        NULL, NULL, &amp;errNum);
    if (errNum != CL_SUCCESS)
    {
        std::cerr &lt;&lt; "Failed to create an OpenCL GPU or CPU context." &lt;&lt; std::endl;
        return NULL;
    }
}

return context;

}

//选择第一个可用的设备并创建一个命令队列
cl_command_queue CreateOpenCLDLL::CreateCommandQueue(cl_context context, cl_device_id *device)
{
cl_int errNum;
cl_device_id *devices;
cl_command_queue commandQueue = NULL;
size_t deviceBufferSize = -1;

//这个clGetContextInfo获得设备缓冲区的大小    
errNum = clGetContextInfo(context, CL_CONTEXT_DEVICES, 0, NULL, &amp;deviceBufferSize);
if (errNum != CL_SUCCESS)
{
    std::cerr &lt;&lt; "Failed call to clGetContextInfo(...,GL_CONTEXT_DEVICES,...)";
    return NULL;
}

if (deviceBufferSize &lt;= 0)
{
    std::cerr &lt;&lt; "No devices available.";
    return NULL;
}

//为设备缓冲区分配内存,这个clGetContextInfo用来获得上下文中所有可用的设备    
devices = new cl_device_id[deviceBufferSize / sizeof(cl_device_id)];
errNum = clGetContextInfo(context, CL_CONTEXT_DEVICES, deviceBufferSize, devices, NULL);
if (errNum != CL_SUCCESS)
{
    delete[] devices;
    std::cerr &lt;&lt; "Failed to get device IDs";
    return NULL;
}
char    deviceName[512];
char    deviceVendor[512];
char    deviceVersion[512];
errNum = clGetDeviceInfo(devices[0], CL_DEVICE_VENDOR, sizeof(deviceVendor),
    deviceVendor, NULL);
errNum |= clGetDeviceInfo(devices[0], CL_DEVICE_NAME, sizeof(deviceName),
    deviceName, NULL);
errNum |= clGetDeviceInfo(devices[0], CL_DEVICE_VERSION, sizeof(deviceVersion),
    deviceVersion, NULL);

printf("OpenCL Device Vendor = %s,  OpenCL Device Name = %s,  OpenCL Device Version = %s\n", deviceVendor, deviceName, deviceVersion);
// 在这个例子中,我们只选择第一个可用的设备。在实际的程序,你可能会使用所有可用的设备或基于OpenCL设备查询选择性能最高的设备    
commandQueue = clCreateCommandQueue(context, devices[0], 0, NULL);
if (commandQueue == NULL)
{
    delete[] devices;
    std::cerr &lt;&lt; "Failed to create commandQueue for device 0";
    return NULL;
}

*device = devices[0];
delete[] devices;
return commandQueue;

}

//从磁盘加载内核源文件并创建一个程序对象
cl_program CreateOpenCLDLL::CreateProgram(cl_context context, cl_device_id device, const char* fileName)
{
cl_int errNum;
cl_program program;

std::ifstream kernelFile(fileName, std::ios::in);
if (!kernelFile.is_open())
{
    std::cerr &lt;&lt; "Failed to open file for reading: " &lt;&lt; fileName &lt;&lt; std::endl;
    return NULL;
}

std::ostringstream oss;
oss &lt;&lt; kernelFile.rdbuf();

std::string srcStdStr = oss.str();
const char *srcStr = srcStdStr.c_str();
//创建程序对象    
program = clCreateProgramWithSource(context, 1,
    (const char**)&amp;srcStr,
    NULL, NULL);
if (program == NULL)
{
    std::cerr &lt;&lt; "Failed to create CL program from source." &lt;&lt; std::endl;
    return NULL;
}
//编译内核源代码    
errNum = clBuildProgram(program, 0, NULL, NULL, NULL, NULL);
if (errNum != CL_SUCCESS)
{
    // 编译失败可以通过clGetProgramBuildInfo获取日志    
    char buildLog[16384];
    clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG,
        sizeof(buildLog), buildLog, NULL);

    std::cerr &lt;&lt; "Error in kernel: " &lt;&lt; std::endl;
    std::cerr &lt;&lt; buildLog;
    clReleaseProgram(program);
    return NULL;
}

return program;

}

//创建内存对象
bool CreateOpenCLDLL::CreateMemObjects(cl_context context, cl_mem memObjects[3], float *a, float *b)
{
//创建内存对象
memObjects[0] = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
sizeof(float)* KArraySize, a, NULL);
memObjects[1] = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
sizeof(float)* KArraySize, b, NULL);
memObjects[2] = clCreateBuffer(context, CL_MEM_READ_WRITE,
sizeof(float)* KArraySize, NULL, NULL);

if (memObjects[0] == NULL || memObjects[1] == NULL || memObjects[2] == NULL)
{
    std::cerr &lt;&lt; "Error creating memory objects." &lt;&lt; std::endl;
    return false;
}

return true;

}

//清理任何创建OpenCL的资源
void CreateOpenCLDLL::Cleanup(cl_context context, cl_command_queue commandQueue,
cl_program program, cl_kernel kernel, cl_mem memObjects[3])
{
for (int i = 0; i < 3; i++)
{
if (memObjects[i] != 0)
clReleaseMemObject(memObjects[i]);
}
if (commandQueue != 0)
clReleaseCommandQueue(commandQueue);

if (kernel != 0)
    clReleaseKernel(kernel);

if (program != 0)
    clReleaseProgram(program);

if (context != 0)
    clReleaseContext(context);

}

void CreateOpenCLDLL::Calcaute()
{
// 创建opencl上下文和第一个可用平台
m_context = CreateContext();
if (m_context == NULL)
{
std::cerr << “Failed to create OpenCL context.” << std::endl;
}

// 在创建的一个上下文中选择第一个可用的设备并创建一个命令队列    
m_commandQueue = CreateCommandQueue(m_context, &amp;m_device);
if (m_commandQueue == NULL)
{
    Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);
}

// 创建一个程序对象 HelloWorld.cl kernel source    
m_program = CreateProgram(m_context, m_device, "HelloWorld.cl");
if (m_program == NULL)
{
    Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);
}

// 创建内核    
m_kernel = clCreateKernel(m_program, "hello_kernel", NULL);
if (m_kernel == NULL)
{
    std::cerr &lt;&lt; "Failed to create kernel" &lt;&lt; std::endl;
    Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);
}

// 创建一个将用作参数内核内存中的对象。首先创建将被用来将参数存储到内核主机存储器阵列    
float result[KArraySize];
float a[KArraySize];
float b[KArraySize];
for (int i = 0; i &lt; KArraySize; i++)
{
    a[i] = (float)i;
    b[i] = (float)(i * 2);
}

if (!CreateMemObjects(m_context, m_memObjects, a, b))
{
    Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);
}

// 设置内核参数、执行内核并读回结果    
m_errNum = clSetKernelArg(m_kernel, 0, sizeof(cl_mem), &amp;m_memObjects[0]);
m_errNum |= clSetKernelArg(m_kernel, 1, sizeof(cl_mem), &amp;m_memObjects[1]);
m_errNum |= clSetKernelArg(m_kernel, 2, sizeof(cl_mem), &amp;m_memObjects[2]);
if (m_errNum != CL_SUCCESS)
{
    std::cerr &lt;&lt; "Error setting kernel arguments." &lt;&lt; std::endl;
    Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);
}

size_t globalWorkSize[1] = { KArraySize };//让之等于数组的大小    
size_t localWorkSize[1] = { 1 };  //让之等于1    

                                  // 利用命令队列使将在设备执行的内核排队    

m_errNum = clEnqueueNDRangeKernel(m_commandQueue, m_kernel, 1, NULL,
    globalWorkSize, localWorkSize,
    0, NULL, NULL);
if (m_errNum != CL_SUCCESS)
{
    std::cerr &lt;&lt; "Error queuing kernel for execution." &lt;&lt; std::endl;
    Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);
}

std::cout &lt;&lt; "Executed program succesfully." &lt;&lt; std::endl;
// Read the output buffer back to the Host    


m_errNum = clEnqueueReadBuffer(m_commandQueue, m_memObjects[2], CL_TRUE,
    0, KArraySize * sizeof(float), result,
    0, NULL, NULL);
if (m_errNum != CL_SUCCESS)
{
    std::cerr &lt;&lt; "Error reading result buffer." &lt;&lt; std::endl;
    Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);
}


//输出结果    
for (int i = 0; i &lt; KArraySize; i++)
{
    std::cout &lt;&lt; result[i] &lt;&lt; " ";
}
std::cout &lt;&lt; std::endl;
std::cout &lt;&lt; "Executed program succesfully." &lt;&lt; std::endl;
Cleanup(m_context, m_commandQueue, m_program, m_kernel, m_memObjects);

}
///* 导出函数定义 */
//CreateOpenCLDLL* GetCreateOpenCLDLL(void)
//{
// CreateOpenCLDLL* pCreateOpenCLDLL = new CreateOpenCLDLL();
// return pCreateOpenCLDLL;
//}

/* 导出函数定义 */
CreateDLL* GetCreateDLL(void)
{
CreateDLL* pCreateDLL = new CreateOpenCLDLL();
return pCreateDLL;
}


测试代码:buildopencldll项目中在新建一个工程testexplicitdll

testexplicitdll.cpp代码如下:


  1. #include <windows.h>    
  2. #include <cstdio>    
  3. #include ”..\buildopencldll\createdll.h”  
  4. #include ”..\buildopencldll\createopencldll.h”  
  5.   
  6. using namespace std;  
  7. int main()  
  8. {  
  9.     //..\\Debug\\buildopencldll.dll出现红色波浪线需要在字符集设置为 未设置  
  10.     //HMODULE hDll = ::LoadLibrary(“..\\Debug\\buildopencldll.dll”);  
  11.     //if (NULL != hDll)  
  12.     //{  
  13.     //  PFNGetCreateOpenCLDLL pFun = (PFNGetCreateOpenCLDLL)::GetProcAddress(hDll, “GetCreateOpenCLDLL”);  
  14.     //  if (NULL != pFun)  
  15.     //  {  
  16.     //      CreateOpenCLDLL* createOpenCLDLL = (*pFun)();  
  17.     //      if (NULL != createOpenCLDLL)  
  18.     //      {  
  19.     //          createOpenCLDLL->Calcaute();  
  20.     //          delete createOpenCLDLL;  
  21.     //      }  
  22.     //  }  
  23.     //  ::FreeLibrary(hDll);  
  24.     //}  
  25.   
  26.     HMODULE hDll = ::LoadLibrary(“..\\Debug\\buildopencldll.dll”);  
  27.     if (NULL != hDll)  
  28.     {  
  29.         PFNGetCreateOpenCLDLL pFun = (PFNGetCreateOpenCLDLL)::GetProcAddress(hDll, ”GetCreateDLL”);  
  30.         if (NULL != pFun)  
  31.         {  
  32.             CreateDLL* createOpenCLDLL = (*pFun)();  
  33.             if (NULL != createOpenCLDLL)  
  34.             {  
  35.                 createOpenCLDLL->Calcaute();  
  36.                 delete createOpenCLDLL;  
  37.             }  
  38.         }  
  39.         ::FreeLibrary(hDll);  
  40.     }  
  41.     return 0;  
  42. }  
#include <windows.h>  




include <cstdio>

include "..\buildopencldll\createdll.h"

include "..\buildopencldll\createopencldll.h"

using namespace std;
int main()
{
//..\Debug\buildopencldll.dll出现红色波浪线需要在字符集设置为 未设置
//HMODULE hDll = ::LoadLibrary("..\Debug\buildopencldll.dll");
//if (NULL != hDll)
//{
// PFNGetCreateOpenCLDLL pFun = (PFNGetCreateOpenCLDLL)::GetProcAddress(hDll, "GetCreateOpenCLDLL");
// if (NULL != pFun)
// {
// CreateOpenCLDLL* createOpenCLDLL = (*pFun)();
// if (NULL != createOpenCLDLL)
// {
// createOpenCLDLL->Calcaute();
// delete createOpenCLDLL;
// }
// }
// ::FreeLibrary(hDll);
//}

HMODULE hDll = ::LoadLibrary("..\\Debug\\buildopencldll.dll");
if (NULL != hDll)
{
    PFNGetCreateOpenCLDLL pFun = (PFNGetCreateOpenCLDLL)::GetProcAddress(hDll, "GetCreateDLL");
    if (NULL != pFun)
    {
        CreateDLL* createOpenCLDLL = (*pFun)();
        if (NULL != createOpenCLDLL)
        {
            createOpenCLDLL-&gt;Calcaute();
            delete createOpenCLDLL;
        }
    }
    ::FreeLibrary(hDll);
}
return 0;

}

所有demo代码可以在如下链接下载:

demo代码



如果出现错误:

错误    C2664       “HMODULELoadLibraryW(LPCWSTR)”: 无法将参数 1 从“const char [19]”转换为“LPCWSTR”    bulidopecvdll    f:\openclproject\bulidopecvdll\bulidopecvdll\main.cpp  18

进入属性->常规->字符集,将使用Unicode字符集改为未设置即可。


                </div>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值