zoukankan      html  css  js  c++  java
  • NEON在Android中的使用举例【转】

    1、  打开Eclipse,File-->New-->AndroidApplication Project-->Application Name:Hello-Neon, Project Name: Hello-Neon,Package Name:com.hello_neon.Android, Minimum Required SDK:API 9:Android 2.3(Gingerbread),Next-->去掉Create custom launcher icon的勾选,Next-->Next-->ActivityName:Hello_NeonProjectActivity,Finish.

    2、  打开Hello-Neon工程下的src-->com.hello_neon.android-->Hello_NeonProjectActivity.Java,将其内容改为:

    [java] view plain copy
     
    1. package com.hello_neon.android;  
    2.   
    3. import android.os.Bundle;  
    4. import android.app.Activity;  
    5. import android.widget.TextView;  
    6.   
    7. public class Hello_NeonProjectActivity extends Activity {  
    8.   
    9.     /** Called when the activity is first created. */  
    10.     @Override  
    11.     public void onCreate(Bundle savedInstanceState)  
    12.     {  
    13.         super.onCreate(savedInstanceState);  
    14.         /* Create a TextView and set its content. 
    15.          * the text is retrieved by calling a native function. 
    16.          */  
    17.         TextView  tv = new TextView(this);  
    18.         tv.setText( stringFromJNI() );  
    19.         setContentView(tv);  
    20.     }  
    21.   
    22.     /* A native method that is implemented by the 
    23.      * 'helloneon' native library, which is packaged with this application. 
    24.      */  
    25.     public native String  stringFromJNI();  
    26.   
    27.     /* this is used to load the 'helloneon' library on application 
    28.      * startup. The library has already been unpacked into 
    29.      * /data/data/com.example.neon/lib/libhelloneon.so at 
    30.      * installation time by the package manager. 
    31.      */  
    32.     static {  
    33.         System.loadLibrary("helloneon");  
    34.     }  
    35.   
    36. }  

    3、 保存Hello_NeonProjectActivity.java文件,打开命令行窗口,将其定位到inclasses目录下,输入命令:javah –classpath D:ProgramFilesAndroidandroid-sdkplatformsandroid-9android.jar;com.hello_neon.android.Hello_NeonProjectActivity ,会在inclasses目录下生成com_hello_neon_android_Hello_NeonProjectActivity.h文件(说明:*.jar也可以是其它版本);

    4、  选中Hello-Neon工程,点击右键-->New-->Folder新建一个jni文件夹,在此文件夹下添加Android.mk、Application.mk、helloneon.c、helloneon-intrinsics.c、helloneon-intrinsics.h五个文件,其中内容分别为:

    Android.mk:

    [html] view plain copy
     
    1. LOCAL_PATH := $(call my-dir)  
    2. include $(CLEAR_VARS)  
    3. LOCAL_MODULE    := helloneon  
    4.   
    5. #填写要编译的源文件路径  
    6. LOCAL_SRC_FILES := helloneon.c helloneon-intrinsics.c  
    7.   
    8. #默认包含的头文件路径  
    9. LOCAL_C_INCLUDES :=   
    10. $(LOCAL_PATH)   
    11. $(LOCAL_PATH)/..  
    12.   
    13. #-g 后面的一系列项目添加了才能使用arm_neon-h头文件, -mfloat-abi=softfp -mfpu=neon 使用arm_neon.h必须  
    14. LOCAL_CFLAGS := -g -mfloat-abi=softfp -mfpu=neon -march=armv7-a -mtune=cortex-a8  
    15.   
    16. LOCAL_LDLIBS := -lz -llog  
    17. TARGET_ARCH_ABI := armeabi-v7a   
    18. LOCAL_ARM_MODE := arm  
    19.   
    20. ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)  
    21. #采用NEON优化技术  
    22.     LOCAL_ARM_NEON := true  
    23.     #LOCAL_CFLAGS := -DHAVE_NEON=1  
    24. endif  
    25.   
    26. LOCAL_STATIC_LIBRARIES := cpufeatures  
    27.   
    28. #生成动态调用库  
    29. include $(BUILD_SHARED_LIBRARY)  
    30.   
    31. $(call import-module,cpufeatures)  

    Application.mk:
    [html] view plain copy
     
    1. APP_PROJECT_PATH := $(call my-dir)/..  
    2. APP_PLATFORM := android-10  
    3. #choose which library to compile against in your Makefile  
    4. APP_STL := stlport_static  
    5. #APP_ABI这句指定了编译的目标平台类型,可以针对不同平台进行优化,x86 or armeabi-v7a  
    6. # Build both ARMv5TE and ARMv7-A machine code.  
    7. APP_ABI := armeabi armeabi-v7a  
    8. APP_CPPFLAGS += -fexceptions  
    9. #for using c++ features,you need to enable these in your Makefile  
    10. APP_CPP_FEATURES += exceptions rtti  

    helloneon.c:
    [cpp] view plain copy
     
    1. #include <jni.h>  
    2. #include <time.h>  
    3. #include <stdio.h>  
    4. #include <stdlib.h>  
    5. #include <cpu-features.h>  
    6. #include "helloneon-intrinsics.h"  
    7.   
    8. #define DEBUG 0  
    9. #define HAVE_NEON  
    10.   
    11. #if DEBUG  
    12. #include <android/log.h>  
    13. #  define  D(x...)  __android_log_print(ANDROID_LOG_INFO,"helloneon",x)  
    14. #else  
    15. #  define  D(...)  do {} while (0)  
    16. #endif  
    17.   
    18. /* return current time in milliseconds */  
    19. static double  
    20. now_ms(void)  
    21. {  
    22.     struct timespec res;  
    23.     clock_gettime(CLOCK_REALTIME, &res);  
    24.     return 1000.0*res.tv_sec + (double)res.tv_nsec/1e6;  
    25. }  
    26.   
    27.   
    28. /* this is a FIR filter implemented in C */  
    29. static void  
    30. fir_filter_c(short *output, const short* input, const short* kernel, int width, int kernelSize)  
    31. {  
    32.     int  offset = -kernelSize/2;  
    33.     int  nn;  
    34.     for (nn = 0; nn < width; nn++) {  
    35.         int sum = 0;  
    36.         int mm;  
    37.         for (mm = 0; mm < kernelSize; mm++) {  
    38.             sum += kernel[mm]*input[nn+offset+mm];  
    39.         }  
    40.         output[nn] = (short)((sum + 0x8000) >> 16);  
    41.     }  
    42. }  
    43.   
    44. #define  FIR_KERNEL_SIZE   32  
    45. #define  FIR_OUTPUT_SIZE   2560  
    46. #define  FIR_INPUT_SIZE    (FIR_OUTPUT_SIZE + FIR_KERNEL_SIZE)  
    47. #define  FIR_ITERATIONS    600  
    48.   
    49. static const short  fir_kernel[FIR_KERNEL_SIZE] = {  
    50.     0x10, 0x20, 0x40, 0x70, 0x8c, 0xa2, 0xce, 0xf0, 0xe9, 0xce, 0xa2, 0x8c, 070, 0x40, 0x20, 0x10,  
    51.     0x10, 0x20, 0x40, 0x70, 0x8c, 0xa2, 0xce, 0xf0, 0xe9, 0xce, 0xa2, 0x8c, 070, 0x40, 0x20, 0x10 };  
    52.   
    53. static short        fir_output[FIR_OUTPUT_SIZE];  
    54. static short        fir_input_0[FIR_INPUT_SIZE];  
    55. static const short* fir_input = fir_input_0 + (FIR_KERNEL_SIZE/2);  
    56. static short        fir_output_expected[FIR_OUTPUT_SIZE];  
    57.   
    58. /* This is a trivial JNI example where we use a native method 
    59.  * to return a new VM String. See the corresponding Java source 
    60.  * file located at: 
    61.  * 
    62.  *   apps/samples/hello-neon/project/src/com/example/neon/HelloNeon.java 
    63.  */  
    64. JNIEXPORT jstring JNICALL Java_com_hello_1neon_android_Hello_1NeonProjectActivity_stringFromJNI(JNIEnv *env, jobject thiz)  
    65. {  
    66.     char*  str;  
    67.     uint64_t features;  
    68.     char buffer[512];  
    69.     char tryNeon = 0;  
    70.     double  t0, t1, time_c, time_neon;  
    71.   
    72.     /* setup FIR input - whatever */  
    73.     {  
    74.         int  nn;  
    75.         for (nn = 0; nn < FIR_INPUT_SIZE; nn++) {  
    76.             fir_input_0[nn] = (5*nn) & 255;  
    77.         }  
    78.         fir_filter_c(fir_output_expected, fir_input, fir_kernel, FIR_OUTPUT_SIZE, FIR_KERNEL_SIZE);  
    79.     }  
    80.   
    81.     /* Benchmark small FIR filter loop - C version */  
    82.     t0 = now_ms();  
    83.     {  
    84.         int  count = FIR_ITERATIONS;  
    85.         for (; count > 0; count--) {  
    86.             fir_filter_c(fir_output, fir_input, fir_kernel, FIR_OUTPUT_SIZE, FIR_KERNEL_SIZE);  
    87.         }  
    88.     }  
    89.     t1 = now_ms();  
    90.     time_c = t1 - t0;  
    91.   
    92.     asprintf(&str, "FIR Filter benchmark: C version          : %g ms ", time_c);  
    93.     strlcpy(buffer, str, sizeof buffer);  
    94.     free(str);  
    95.   
    96.     strlcat(buffer, "Neon version   : ", sizeof buffer);  
    97.   
    98.     if (android_getCpuFamily() != ANDROID_CPU_FAMILY_ARM) {  
    99.         strlcat(buffer, "Not an ARM CPU ! ", sizeof buffer);  
    100.         goto EXIT;  
    101.     }  
    102.   
    103.     features = android_getCpuFeatures();  
    104.     if ((features & ANDROID_CPU_ARM_FEATURE_ARMv7) == 0) {  
    105.         strlcat(buffer, "Not an ARMv7 CPU ! ", sizeof buffer);  
    106.         goto EXIT;  
    107.     }  
    108.   
    109.     /* HAVE_NEON is defined in Android.mk ! */  
    110. #ifdef HAVE_NEON  
    111.     if ((features & ANDROID_CPU_ARM_FEATURE_NEON) == 0) {  
    112.         strlcat(buffer, "CPU doesn't support NEON ! ", sizeof buffer);  
    113.         goto EXIT;  
    114.     }  
    115.   
    116.     /* Benchmark small FIR filter loop - Neon version */  
    117.     t0 = now_ms();  
    118.     {  
    119.         int  count = FIR_ITERATIONS;  
    120.         for (; count > 0; count--) {  
    121.             fir_filter_neon_intrinsics(fir_output, fir_input, fir_kernel, FIR_OUTPUT_SIZE, FIR_KERNEL_SIZE);  
    122.         }  
    123.     }  
    124.     t1 = now_ms();  
    125.     time_neon = t1 - t0;  
    126.     asprintf(&str, "%g ms (x%g faster) ", time_neon, time_c / (time_neon < 1e-6 ? 1. : time_neon));  
    127.     strlcat(buffer, str, sizeof buffer);  
    128.     free(str);  
    129.   
    130.     /* check the result, just in case */  
    131.     {  
    132.         int  nn, fails = 0;  
    133.         for (nn = 0; nn < FIR_OUTPUT_SIZE; nn++) {  
    134.             if (fir_output[nn] != fir_output_expected[nn]) {  
    135.                 if (++fails < 16)  
    136.                     D("neon[%d] = %d expected %d", nn, fir_output[nn], fir_output_expected[nn]);  
    137.             }  
    138.         }  
    139.         D("%d fails ", fails);  
    140.     }  
    141. #else /* !HAVE_NEON */  
    142.     strlcat(buffer, "Program not compiled with ARMv7 support ! ", sizeof buffer);  
    143. #endif /* !HAVE_NEON */  
    144. EXIT:  
    145.     return (*env)->NewStringUTF(env, buffer);  
    146. }  

    helloneon-intrinsics.h:

    [cpp] view plain copy
     
    1. #ifndef HELLONEON_INTRINSICS_H  
    2. #define HELLONEON_INTRINSICS_H  
    3.   
    4. void fir_filter_neon_intrinsics(short *output, const short* input, const short* kernel, int width, int kernelSize);  
    5.   
    6. #endif /* HELLONEON_INTRINSICS_H */  

    helloneon-intrinsics.c:
    [cpp] view plain copy
     
    1. #include "helloneon-intrinsics.h"  
    2. #include <arm_neon.h>  
    3.   
    4. /* this source file should only be compiled by Android.mk when targeting 
    5.  * the armeabi-v7a ABI, and should be built in NEON mode 
    6.  */  
    7. void  
    8. fir_filter_neon_intrinsics(short *output, const short* input, const short* kernel, int width, int kernelSize)  
    9. {  
    10. #if 1  
    11.    int nn, offset = -kernelSize/2;  
    12.   
    13.    for (nn = 0; nn < width; nn++)  
    14.    {  
    15.         int mm, sum = 0;  
    16.         int32x4_t sum_vec = vdupq_n_s32(0);  
    17.         for(mm = 0; mm < kernelSize/4; mm++)  
    18.         {  
    19.             int16x4_t  kernel_vec = vld1_s16(kernel + mm*4);  
    20.             int16x4_t  input_vec = vld1_s16(input + (nn+offset+mm*4));  
    21.             sum_vec = vmlal_s16(sum_vec, kernel_vec, input_vec);  
    22.         }  
    23.   
    24.         sum += vgetq_lane_s32(sum_vec, 0);  
    25.         sum += vgetq_lane_s32(sum_vec, 1);  
    26.         sum += vgetq_lane_s32(sum_vec, 2);  
    27.         sum += vgetq_lane_s32(sum_vec, 3);  
    28.   
    29.         if(kernelSize & 3)  
    30.         {  
    31.             for(mm = kernelSize - (kernelSize & 3); mm < kernelSize; mm++)  
    32.                 sum += kernel[mm] * input[nn+offset+mm];  
    33.         }  
    34.   
    35.         output[nn] = (short)((sum + 0x8000) >> 16);  
    36.     }  
    37. #else /* for comparison purposes only */  
    38.     int nn, offset = -kernelSize/2;  
    39.     for (nn = 0; nn < width; nn++) {  
    40.         int sum = 0;  
    41.         int mm;  
    42.         for (mm = 0; mm < kernelSize; mm++) {  
    43.             sum += kernel[mm]*input[nn+offset+mm];  
    44.         }  
    45.         output[nn] = (short)((sum + 0x8000) >> 16);  
    46.     }  
    47. #endif  
    48. }  

    5、 利用NDK生成.so文件:选中工程-->Properties-->Builders-->New-->选中Program-->OK,Name:Hello_Neon_Builder,Location: D:ProgramFilesAndroidandroid-sdkandroid-ndk-r9 dk-build.cmd,Working Directory: E:NEONEclipseHello-Neon -->Apply,选中Refresh,勾选Refreshresources upon completion, 勾选Specific resources,点击Specify Resources…,勾选Hello-Neon工程下的libs文件夹,Finish-->Apply,选中BuildOptions,勾选Allocate Console(necessary for input), After a “Clean”, During manualbuilds, During auto builds, Specify working set of relevant resources,点击SpecifyResoures…,勾选Hello-Neon工程下的jni文件夹,Finish-->Apply-->OK-->OK,会在libs文件夹下生成libhelloneon.so文件;

    6、  选中Hello-Neon,-->Run As-->AndroidApplication,运行结果为:

    FIRFilter benchmark:

    C version       :282.84 ms

    Neon version    :135985 ms(x2.07994 faster)

    以上是.c文件的操作步骤,若将.c文件该为.cpp文件,则需改动两个文件:

    1、将Android.mk改为:
    [html] view plain copy
     
    1. LOCAL_PATH := $(call my-dir)  
    2. include $(CLEAR_VARS)  
    3. LOCAL_MODULE    := helloneon  
    4.   
    5. #填写要编译的源文件路径  
    6. LOCAL_SRC_FILES := helloneon.cpp helloneon-intrinsics.cpp  
    7.   
    8. #默认包含的头文件路径  
    9. LOCAL_C_INCLUDES :=   
    10. $(LOCAL_PATH)   
    11. $(LOCAL_PATH)/..  
    12.   
    13. #-g 后面的一系列项目添加了才能使用arm_neon-h头文件, -mfloat-abi=softfp -mfpu=neon 使用arm_neon.h必须  
    14. LOCAL_CFLAGS := -g -mfloat-abi=softfp -mfpu=neon -march=armv7-a -mtune=cortex-a8  
    15.   
    16. LOCAL_LDLIBS := -lz -llog  
    17. TARGET_ARCH_ABI := armeabi-v7a   
    18. LOCAL_ARM_MODE := arm  
    19.   
    20. ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)  
    21. #采用NEON优化技术  
    22.     LOCAL_ARM_NEON := true  
    23.     #LOCAL_CFLAGS := -DHAVE_NEON=1  
    24. endif  
    25.   
    26. LOCAL_STATIC_LIBRARIES := cpufeatures  
    27.   
    28. #生成动态调用库  
    29. include $(BUILD_SHARED_LIBRARY)  
    30.   
    31. $(call import-module,cpufeatures)  

    2、helloneon.c改为:
    [cpp] view plain copy
     
    1. #include <jni.h>  
    2. #include <time.h>  
    3. #include <stdio.h>  
    4. #include <stdlib.h>  
    5. #include <cpu-features.h>  
    6. #include "helloneon-intrinsics.h"  
    7.   
    8. #define DEBUG 0  
    9. #define HAVE_NEON  
    10.   
    11. #ifdef __cplusplus  
    12. extern "C" {  
    13. #endif  
    14.   
    15. #if DEBUG  
    16. #include <android/log.h>  
    17. #  define  D(x...)  __android_log_print(ANDROID_LOG_INFO,"helloneon",x)  
    18. #else  
    19. #  define  D(...)  do {} while (0)  
    20. #endif  
    21.   
    22. /* return current time in milliseconds */  
    23. static double  
    24. now_ms(void)  
    25. {  
    26.     struct timespec res;  
    27.     clock_gettime(CLOCK_REALTIME, &res);  
    28.     return 1000.0*res.tv_sec + (double)res.tv_nsec/1e6;  
    29. }  
    30.   
    31.   
    32. /* this is a FIR filter implemented in C */  
    33. static void  
    34. fir_filter_c(short *output, const short* input, const short* kernel, int width, int kernelSize)  
    35. {  
    36.     int  offset = -kernelSize/2;  
    37.     int  nn;  
    38.     for (nn = 0; nn < width; nn++) {  
    39.         int sum = 0;  
    40.         int mm;  
    41.         for (mm = 0; mm < kernelSize; mm++) {  
    42.             sum += kernel[mm]*input[nn+offset+mm];  
    43.         }  
    44.         output[nn] = (short)((sum + 0x8000) >> 16);  
    45.     }  
    46. }  
    47.   
    48. #define  FIR_KERNEL_SIZE   32  
    49. #define  FIR_OUTPUT_SIZE   2560  
    50. #define  FIR_INPUT_SIZE    (FIR_OUTPUT_SIZE + FIR_KERNEL_SIZE)  
    51. #define  FIR_ITERATIONS    600  
    52.   
    53. static const short  fir_kernel[FIR_KERNEL_SIZE] = {  
    54.     0x10, 0x20, 0x40, 0x70, 0x8c, 0xa2, 0xce, 0xf0, 0xe9, 0xce, 0xa2, 0x8c, 070, 0x40, 0x20, 0x10,  
    55.     0x10, 0x20, 0x40, 0x70, 0x8c, 0xa2, 0xce, 0xf0, 0xe9, 0xce, 0xa2, 0x8c, 070, 0x40, 0x20, 0x10 };  
    56.   
    57. static short        fir_output[FIR_OUTPUT_SIZE];  
    58. static short        fir_input_0[FIR_INPUT_SIZE];  
    59. static const short* fir_input = fir_input_0 + (FIR_KERNEL_SIZE/2);  
    60. static short        fir_output_expected[FIR_OUTPUT_SIZE];  
    61.   
    62. /* This is a trivial JNI example where we use a native method 
    63.  * to return a new VM String. See the corresponding Java source 
    64.  * file located at: 
    65.  * 
    66.  *   apps/samples/hello-neon/project/src/com/example/neon/HelloNeon.java 
    67.  */  
    68. JNIEXPORT jstring JNICALL Java_com_hello_1neon_android_Hello_1NeonProjectActivity_stringFromJNI(JNIEnv *env, jobject thiz)  
    69. {  
    70.     char str[512] = {0};  
    71.     uint64_t features;  
    72.     char buffer[512];  
    73.     char tryNeon = 0;  
    74.     double  t0, t1, time_c, time_neon;  
    75.   
    76.     /* setup FIR input - whatever */  
    77.     {  
    78.         int  nn;  
    79.         for (nn = 0; nn < FIR_INPUT_SIZE; nn++) {  
    80.             fir_input_0[nn] = (5*nn) & 255;  
    81.         }  
    82.         fir_filter_c(fir_output_expected, fir_input, fir_kernel, FIR_OUTPUT_SIZE, FIR_KERNEL_SIZE);  
    83.     }  
    84.   
    85.     /* Benchmark small FIR filter loop - C version */  
    86.     t0 = now_ms();  
    87.     {  
    88.         int  count = FIR_ITERATIONS;  
    89.         for (; count > 0; count--) {  
    90.             fir_filter_c(fir_output, fir_input, fir_kernel, FIR_OUTPUT_SIZE, FIR_KERNEL_SIZE);  
    91.         }  
    92.     }  
    93.     t1 = now_ms();  
    94.     time_c = t1 - t0;  
    95.   
    96.     sprintf(str, "FIR Filter benchmark: C version          : %g ms ", time_c);  
    97.     strlcpy(buffer, str, sizeof buffer);  
    98.   
    99.     strlcat(buffer, "Neon version   : ", sizeof buffer);  
    100.   
    101.     if (android_getCpuFamily() != ANDROID_CPU_FAMILY_ARM) {  
    102.         strlcat(buffer, "Not an ARM CPU ! ", sizeof buffer);  
    103.         goto EXIT;  
    104.     }  
    105.   
    106.     features = android_getCpuFeatures();  
    107.     if ((features & ANDROID_CPU_ARM_FEATURE_ARMv7) == 0) {  
    108.         strlcat(buffer, "Not an ARMv7 CPU ! ", sizeof buffer);  
    109.         goto EXIT;  
    110.     }  
    111.   
    112.     /* HAVE_NEON is defined in Android.mk ! */  
    113. #ifdef HAVE_NEON  
    114.     if ((features & ANDROID_CPU_ARM_FEATURE_NEON) == 0) {  
    115.         strlcat(buffer, "CPU doesn't support NEON ! ", sizeof buffer);  
    116.         goto EXIT;  
    117.     }  
    118.   
    119.     /* Benchmark small FIR filter loop - Neon version */  
    120.     t0 = now_ms();  
    121.     {  
    122.         int  count = FIR_ITERATIONS;  
    123.         for (; count > 0; count--) {  
    124.             fir_filter_neon_intrinsics(fir_output, fir_input, fir_kernel, FIR_OUTPUT_SIZE, FIR_KERNEL_SIZE);  
    125.         }  
    126.     }  
    127.     t1 = now_ms();  
    128.     time_neon = t1 - t0;  
    129.     sprintf(str, "%g ms (x%g faster) ", time_neon, time_c / (time_neon < 1e-6 ? 1. : time_neon));  
    130.     strlcat(buffer, str, sizeof buffer);  
    131.   
    132.     /* check the result, just in case */  
    133.     {  
    134.         int  nn, fails = 0;  
    135.         for (nn = 0; nn < FIR_OUTPUT_SIZE; nn++) {  
    136.             if (fir_output[nn] != fir_output_expected[nn]) {  
    137.                 if (++fails < 16)  
    138.                     D("neon[%d] = %d expected %d", nn, fir_output[nn], fir_output_expected[nn]);  
    139.             }  
    140.         }  
    141.         D("%d fails ", fails);  
    142.     }  
    143. #else /* !HAVE_NEON */  
    144.     strlcat(buffer, "Program not compiled with ARMv7 support ! ", sizeof buffer);  
    145. #endif /* !HAVE_NEON */  
    146. EXIT:  
    147.     return env->NewStringUTF(buffer);  
    148. }  
    149.   
    150. #ifdef __cplusplus  
    151. }  
    152. #endif  


    参考文献:

    1、  http://blog.csdn.net/fengbingchun/article/details/11580983

    2、  android-ndk-r9-windows-x86_64中的hello-neon例子代码

  • 相关阅读:
    shell进行mysql统计
    java I/O总结
    Hbase源码分析:Hbase UI中Requests Per Second的具体含义
    ASP.NET Session State Overview
    What is an ISAPI Extension?
    innerxml and outerxml
    postman
    FileZilla文件下载的目录
    how to use webpart container in kentico
    Consider using EXISTS instead of IN
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/5694598.html
Copyright © 2011-2022 走看看