Android 通过JNI C++进行MD5加密_jni md5-程序员宅基地

技术标签: jni  c语言  native  android  Android  

最近升级了Android Studio 3.0.1。版本升级,越是觉得好用了。

Android Studio使用JNI之前觉得是一件比较麻烦的事,官方文档也不多,有点无从下手。本篇以CMake方式说说如何利用JNI方式使用C++进行MD5加密,这样经过Native的方式不容易被反编译出来,所以加点复杂的盐,安全性会更高。

1、新建项目时勾选C++支持选项。然后后面每一步默认即可。
这里写图片描述

2、默认会在cpp目录下生成native-lib.cpp的文件,默认我们与C/C++代码的交互就在这个文件中。配置实在CMakeList.txt(app模块根目录下)文件中。
这里写图片描述

3、如图我们添加MD5.h和MD5.cpp文件,并在CMakeList.txt配置
这里写图片描述

4、完成这一步后,编写MD5的实现代码。
MD5.h如下:

#include <string>
#include <iostream>


// a small class for calculating MD5 hashes of strings or byte arrays
// it is not meant to be fast or secure
//
// usage: 1) feed it blocks of uchars with update()
//      2) finalize()
//      3) get hexdigest() string
//      or
//      MD5(std::string).hexdigest()
//
// assumes that char is 8 bit and int is 32 bit
class MD5
{
public:
    typedef unsigned int size_type; // must be 32bit

    MD5();
    MD5(const std::string& text);
    void update(const unsigned char *buf, size_type length);
    void update(const char *buf, size_type length);
    MD5& finalize();
    std::string hexdigest() const;
    friend std::ostream& operator<<(std::ostream&, MD5 md5);

private:
    void init();
    typedef unsigned char uint1; //  8bit
    typedef unsigned int uint4;  // 32bit
    enum { blocksize = 64 }; // VC6 won't eat a const static int here

    void transform(const uint1 block[blocksize]);
    static void decode(uint4 output[], const uint1 input[], size_type len);
    static void encode(uint1 output[], const uint4 input[], size_type len);

    bool finalized;
    uint1 buffer[blocksize]; // bytes that didn't fit in last 64 byte chunk
    uint4 count[2];   // 64bit counter for number of bits (lo, hi)
    uint4 state[4];   // digest so far
    uint1 digest[16]; // the result

    // low level logic operations
    static inline uint4 F(uint4 x, uint4 y, uint4 z);
    static inline uint4 G(uint4 x, uint4 y, uint4 z);
    static inline uint4 H(uint4 x, uint4 y, uint4 z);
    static inline uint4 I(uint4 x, uint4 y, uint4 z);
    static inline uint4 rotate_left(uint4 x, int n);
    static inline void FF(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac);
    static inline void GG(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac);
    static inline void HH(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac);
    static inline void II(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac);
};

std::string md5(const std::string str);

MD5.cpp如下:

#include "MD5.h"

/* system implementation headers */
#include <stdio.h>
#include <string.h>


// Constants for MD5Transform routine.
#define S11 7
#define S12 12
#define S13 17
#define S14 22
#define S21 5
#define S22 9
#define S23 14
#define S24 20
#define S31 4
#define S32 11
#define S33 16
#define S34 23
#define S41 6
#define S42 10
#define S43 15
#define S44 21

///////////////////////////////////////////////

// F, G, H and I are basic MD5 functions.
inline MD5::uint4 MD5::F(uint4 x, uint4 y, uint4 z) {
    return x&y | ~x&z;
}

inline MD5::uint4 MD5::G(uint4 x, uint4 y, uint4 z) {
    return x&z | y&~z;
}

inline MD5::uint4 MD5::H(uint4 x, uint4 y, uint4 z) {
    return x^y^z;
}

inline MD5::uint4 MD5::I(uint4 x, uint4 y, uint4 z) {
    return y ^ (x | ~z);
}

// rotate_left rotates x left n bits.
inline MD5::uint4 MD5::rotate_left(uint4 x, int n) {
    return (x << n) | (x >> (32 - n));
}

// FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
// Rotation is separate from addition to prevent recomputation.
inline void MD5::FF(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) {
    a = rotate_left(a + F(b, c, d) + x + ac, s) + b;
}

inline void MD5::GG(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) {
    a = rotate_left(a + G(b, c, d) + x + ac, s) + b;
}

inline void MD5::HH(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) {
    a = rotate_left(a + H(b, c, d) + x + ac, s) + b;
}

inline void MD5::II(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) {
    a = rotate_left(a + I(b, c, d) + x + ac, s) + b;
}

//////////////////////////////////////////////

// default ctor, just initailize
MD5::MD5()
{
    init();
}

//////////////////////////////////////////////

// nifty shortcut ctor, compute MD5 for string and finalize it right away
MD5::MD5(const std::string &text)
{
    init();
    update(text.c_str(), text.length());
    finalize();
}

//////////////////////////////

void MD5::init()
{
    finalized = false;

    count[0] = 0;
    count[1] = 0;

    // load magic initialization constants.
    state[0] = 0x67452301;
    state[1] = 0xefcdab89;
    state[2] = 0x98badcfe;
    state[3] = 0x10325476;
}

//////////////////////////////

// decodes input (unsigned char) into output (uint4). Assumes len is a multiple of 4.
void MD5::decode(uint4 output[], const uint1 input[], size_type len)
{
    for (unsigned int i = 0, j = 0; j < len; i++, j += 4)
        output[i] = ((uint4)input[j]) | (((uint4)input[j + 1]) << 8) |
                    (((uint4)input[j + 2]) << 16) | (((uint4)input[j + 3]) << 24);
}

//////////////////////////////

// encodes input (uint4) into output (unsigned char). Assumes len is
// a multiple of 4.
void MD5::encode(uint1 output[], const uint4 input[], size_type len)
{
    for (size_type i = 0, j = 0; j < len; i++, j += 4) {
        output[j] = input[i] & 0xff;
        output[j + 1] = (input[i] >> 8) & 0xff;
        output[j + 2] = (input[i] >> 16) & 0xff;
        output[j + 3] = (input[i] >> 24) & 0xff;
    }
}

//////////////////////////////

// apply MD5 algo on a block
void MD5::transform(const uint1 block[blocksize])
{
    uint4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
    decode(x, block, blocksize);

    /* Round 1 */
    FF(a, b, c, d, x[0], S11, 0xd76aa478); /* 1 */
    FF(d, a, b, c, x[1], S12, 0xe8c7b756); /* 2 */
    FF(c, d, a, b, x[2], S13, 0x242070db); /* 3 */
    FF(b, c, d, a, x[3], S14, 0xc1bdceee); /* 4 */
    FF(a, b, c, d, x[4], S11, 0xf57c0faf); /* 5 */
    FF(d, a, b, c, x[5], S12, 0x4787c62a); /* 6 */
    FF(c, d, a, b, x[6], S13, 0xa8304613); /* 7 */
    FF(b, c, d, a, x[7], S14, 0xfd469501); /* 8 */
    FF(a, b, c, d, x[8], S11, 0x698098d8); /* 9 */
    FF(d, a, b, c, x[9], S12, 0x8b44f7af); /* 10 */
    FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
    FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
    FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
    FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
    FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
    FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */

    /* Round 2 */
    GG(a, b, c, d, x[1], S21, 0xf61e2562); /* 17 */
    GG(d, a, b, c, x[6], S22, 0xc040b340); /* 18 */
    GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
    GG(b, c, d, a, x[0], S24, 0xe9b6c7aa); /* 20 */
    GG(a, b, c, d, x[5], S21, 0xd62f105d); /* 21 */
    GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */
    GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
    GG(b, c, d, a, x[4], S24, 0xe7d3fbc8); /* 24 */
    GG(a, b, c, d, x[9], S21, 0x21e1cde6); /* 25 */
    GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
    GG(c, d, a, b, x[3], S23, 0xf4d50d87); /* 27 */
    GG(b, c, d, a, x[8], S24, 0x455a14ed); /* 28 */
    GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
    GG(d, a, b, c, x[2], S22, 0xfcefa3f8); /* 30 */
    GG(c, d, a, b, x[7], S23, 0x676f02d9); /* 31 */
    GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */

    /* Round 3 */
    HH(a, b, c, d, x[5], S31, 0xfffa3942); /* 33 */
    HH(d, a, b, c, x[8], S32, 0x8771f681); /* 34 */
    HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
    HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
    HH(a, b, c, d, x[1], S31, 0xa4beea44); /* 37 */
    HH(d, a, b, c, x[4], S32, 0x4bdecfa9); /* 38 */
    HH(c, d, a, b, x[7], S33, 0xf6bb4b60); /* 39 */
    HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
    HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
    HH(d, a, b, c, x[0], S32, 0xeaa127fa); /* 42 */
    HH(c, d, a, b, x[3], S33, 0xd4ef3085); /* 43 */
    HH(b, c, d, a, x[6], S34, 0x4881d05); /* 44 */
    HH(a, b, c, d, x[9], S31, 0xd9d4d039); /* 45 */
    HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
    HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
    HH(b, c, d, a, x[2], S34, 0xc4ac5665); /* 48 */

    /* Round 4 */
    II(a, b, c, d, x[0], S41, 0xf4292244); /* 49 */
    II(d, a, b, c, x[7], S42, 0x432aff97); /* 50 */
    II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
    II(b, c, d, a, x[5], S44, 0xfc93a039); /* 52 */
    II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
    II(d, a, b, c, x[3], S42, 0x8f0ccc92); /* 54 */
    II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
    II(b, c, d, a, x[1], S44, 0x85845dd1); /* 56 */
    II(a, b, c, d, x[8], S41, 0x6fa87e4f); /* 57 */
    II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
    II(c, d, a, b, x[6], S43, 0xa3014314); /* 59 */
    II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
    II(a, b, c, d, x[4], S41, 0xf7537e82); /* 61 */
    II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
    II(c, d, a, b, x[2], S43, 0x2ad7d2bb); /* 63 */
    II(b, c, d, a, x[9], S44, 0xeb86d391); /* 64 */

    state[0] += a;
    state[1] += b;
    state[2] += c;
    state[3] += d;

    // Zeroize sensitive information.
    memset(x, 0, sizeof x);
}

//////////////////////////////

// MD5 block update operation. Continues an MD5 message-digest
// operation, processing another message block
void MD5::update(const unsigned char input[], size_type length)
{
    // compute number of bytes mod 64
    size_type index = count[0] / 8 % blocksize;

    // Update number of bits
    if ((count[0] += (length << 3)) < (length << 3))
        count[1]++;
    count[1] += (length >> 29);

    // number of bytes we need to fill in buffer
    size_type firstpart = 64 - index;

    size_type i;

    // transform as many times as possible.
    if (length >= firstpart)
    {
        // fill buffer first, transform
        memcpy(&buffer[index], input, firstpart);
        transform(buffer);

        // transform chunks of blocksize (64 bytes)
        for (i = firstpart; i + blocksize <= length; i += blocksize)
            transform(&input[i]);

        index = 0;
    }
    else
        i = 0;

    // buffer remaining input
    memcpy(&buffer[index], &input[i], length - i);
}

//////////////////////////////

// for convenience provide a verson with signed char
void MD5::update(const char input[], size_type length)
{
    update((const unsigned char*)input, length);
}

//////////////////////////////

// MD5 finalization. Ends an MD5 message-digest operation, writing the
// the message digest and zeroizing the context.
MD5& MD5::finalize()
{
    static unsigned char padding[64] = {
            0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
    };

    if (!finalized) {
        // Save number of bits
        unsigned char bits[8];
        encode(bits, count, 8);

        // pad out to 56 mod 64.
        size_type index = count[0] / 8 % 64;
        size_type padLen = (index < 56) ? (56 - index) : (120 - index);
        update(padding, padLen);

        // Append length (before padding)
        update(bits, 8);

        // Store state in digest
        encode(digest, state, 16);

        // Zeroize sensitive information.
        memset(buffer, 0, sizeof buffer);
        memset(count, 0, sizeof count);

        finalized = true;
    }

    return *this;
}

//////////////////////////////

// return hex representation of digest as string
std::string MD5::hexdigest() const
{
    if (!finalized)
        return "";

    char buf[33];
    for (int i = 0; i < 16; i++)
        sprintf(buf + i * 2, "%02x", digest[i]);
    buf[32] = 0;

    return std::string(buf);
}

//////////////////////////////

std::ostream& operator<<(std::ostream& out, MD5 md5)
{
    return out << md5.hexdigest();
}

//////////////////////////////

std::string md5(const std::string str)
{
    MD5 md5 = MD5(str);

    return md5.hexdigest();
}

5、完成这一步后,make一下project。然后在Java和native-lib.cpp中分别编写MD5的测试代码:
比如在MainActivity.java中定义native的方法:

// Used to load the 'native-lib' library on application startup.
static {
    System.loadLibrary("native-lib");
}
public native String getMd5(String origin);

然后在native-lib.cpp中完成实现:

//记得导入头文件
#include "MD5.h"

//使用c中的md5加密,可以将盐加入不容易被反编译出来
JNIEXPORT jstring JNICALL
Java_com_dengpan_MainActivity_getMd5(JNIEnv *env, jobject, jstring str) {
    const char *originStr;
    //将jstring转化成char *类型
    originStr = env->GetStringUTFChars(str, false);
    MD5 md5 = MD5(originStr);
    std::string md5Result = md5.hexdigest();
    //将char *类型转化成jstring返回给Java层
    return env->NewStringUTF(md5Result.c_str());
}

6、于是运行程序在MainActivity的onCreate方法中可以看到程序打印出结果。

System.out.println("MD5加密结果:" + getMd5("123456"));
##运行结果
I/System.out: MD5加密结果:e10adc3949ba59abbe56e057f20f883e

另外,JNI中常用在C++中打印出log,需要有如下配置:

#include <android/log.h>

#define LOG_TAG   "lib_native_log"
#define  LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

另外,在JNI中调用Java定义的方法:比如在MainActivity中定义

//定义和native交互的方法
public native void toast();

//....
//定义被C++ 调用的方法
public void toastMain(Context c){
        Toast.makeText(c, "通过Native调用出现的提示", Toast.LENGTH_SHORT).show();
}

那么在native-lib.cpp中定义方法:

//需要调用此方法,则此方法定义在调用方法前。获取全局Context
jobject getGlobalContext(JNIEnv *env)
{
    //获取Activity Thread的实例对象
    jclass activityThread = env->FindClass("android/app/ActivityThread");
    jmethodID currentActivityThread = env->GetStaticMethodID(activityThread, "currentActivityThread", "()Landroid/app/ActivityThread;");
    jobject at = env->CallStaticObjectMethod(activityThread, currentActivityThread);
    //获取Application,也就是全局的Context
    jmethodID getApplication = env->GetMethodID(activityThread, "getApplication", "()Landroid/app/Application;");
    jobject context = env->CallObjectMethod(at, getApplication);
    return context;
}

JNIEXPORT void JNICALL
Java_com_dengpan_MainActivity_toast(JNIEnv *env, jobject obj) {
    
    jclass dpclazz = env->FindClass("com/dengpan/MainActivity");
    if(dpclazz==0){
        LOGE("find class error");
        return;
    }
    LOGI("find class ");

    //2 寻找class里面的方法
    jmethodID method1 = env->GetMethodID(dpclazz,"toastMain","(Landroid/content/Context;)V");//
    if(method1==0){
        LOGE("find method1 error");
        return;
    }
    LOGI("find method1");
    //3 .调用这个方法
    // void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
    env->CallVoidMethod(obj,method1,getGlobalContext(env));
}

注意,在native-lib.cpp定义的方法一定要放在extern "C" {}里面,不然编译运行时会报一些链接错误。

CMake的方式会自动将CPP文件编译成so文件拷贝打包到apk文件中。 如图:
这里写图片描述

至此JNI方法调用暂时写到这儿。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ada_dengpan/article/details/78687919

智能推荐

Android自定义摇杆_rockerview kongqw:leftbottombackground-程序员宅基地

文章浏览阅读2.4w次,点赞26次,收藏76次。转载请说明出处! 作者:kqw攻城狮 出处:个人站 | CSDN效果图源码KqwRockerDemo喜欢就给个star,谢谢!功能支持自适应大小支持2个方向、4个方向、8个方向的摇动监听支持摇动角度获取可选回调模式支持可摇动区域自定义支持摇杆自定义支持设置图片、色值、Share图形使用

###七段数码管绘制###python_绘制abcdef七段数码管,数码管风格不限。-程序员宅基地

文章浏览阅读291次。七段数码管绘制要求如下:‪‬‪‬‪‬‪‬‪‬‮‬‫‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬(1) 使用 time 库获得系统当前时间,格式如下..._绘制abcdef七段数码管,数码管风格不限。

设置harbor开机自启动_harbor 开机自启-程序员宅基地

文章浏览阅读7.4k次,点赞5次,收藏14次。编辑文件/lib/systemd/system/harbor.service[Unit]Description=HarborAfter=docker.service systemd-networkd.service systemd-resolved.serviceRequires=docker.serviceDocumentation=http://github.com/vmware/h..._harbor 开机自启

从小白开始学python-网络爬虫二 (HTML、 CSS、Beautiful Soup介绍)_python基础知识与html/css样式基础知识的巩固,学习python爬虫并练习-程序员宅基地

文章浏览阅读652次,点赞5次,收藏10次。python网络爬虫HTML和CSS简介Beautiful Soup正则表达式_python基础知识与html/css样式基础知识的巩固,学习python爬虫并练习

vue中better-scroll中页面滚动无效的问题_vue使用better-scroll监听滚动不生效on('scroll-程序员宅基地

文章浏览阅读3.4k次,点赞5次,收藏2次。因为在做项目的时候要用到滚动,所以引用了better-scroll这个插件,于是按照官网用法:&lt;script&gt; import BScroll from 'better-scroll' export default{ name: "CityList", mounted(){ this.scroll = new BScroll(this..._vue使用better-scroll监听滚动不生效on('scroll

12.大小写转换(编程入门题-C/C++&Java&Python实现)_题目:从键盘输入一个小写字母,要求改用大写字母输出。char fun(char c)-程序员宅基地

文章浏览阅读966次。大小写转换(编程入门题-C/C++&Java&Python实现)题目描述从键盘输入一个大写字母,要求改用小写字母输出。输入包括多组测试数据以EOF结束,每组输入一个大写字母。输出输出对应的小写字母。样例输入 AB样例输出 ab_题目:从键盘输入一个小写字母,要求改用大写字母输出。char fun(char c)

随便推点

简单rpm打包测试_macrpm怎么测试-程序员宅基地

文章浏览阅读1.5k次。1 准备首先请准备一个Linux环境,比如CentOS。 RPM打包使用的是rpmbuild命令,这个命令来自rpm-build包,这个是必装的。$ yum install rpm-build当然也可以直接安装rpmdevtools,这个工具还包含一些其他的工具,同时它依赖rpm-build,所以直接安装的话会同时把rpm-build装上。$ yum install rpmdevtools当然,根..._macrpm怎么测试

【Pandas】修改Pandas的行或列的名字(重命名)-程序员宅基地

文章浏览阅读4.4w次,点赞6次,收藏34次。pandas.DataFrame.rename使用函数: DataFrame.rename(mapper=None, index=None, columns=None, axis=None, copy=True, inplace=False, level=None)功能:更改轴标签函数字典值必须是唯一的(1对1)。未包含在 字典/Series 中的标签将保留原样。列出的额外标签不会引...

Linux下载并安装JDK1.8_linux jdk 1.8.0_131下载-程序员宅基地

文章浏览阅读7.6k次,点赞3次,收藏4次。一、下载并配置下载地址:点击下载 使用命令进行解压 我们要将解压后的【jdk1.8.0_131】里面的所有数据移动到我们需要安装的文件夹当中,我们打算将jdk安装在usr/java当中,我们在usr目录下新建一个java文件夹 将【jdk1.8.0_131】里的数据拷贝至java目录下 修改环境变量 在打开的文件结尾添加如下的代码 export JA..._linux jdk 1.8.0_131下载

vs2015企业版安装教程-程序员宅基地

文章浏览阅读6.4k次。参考教程VS2015详细安装步骤_vs2015企业版安装教程

【老九学堂】【PHP】第一章_老九课堂讲义-程序员宅基地

文章浏览阅读299次。注意:hi~小伙伴们,老九君很激动又和大家见面了,作业是检验自己学习成效的方式之一,也希望大家认真对待。如有任何疑问和好的建议,欢迎骚扰老九君。【课后作业】1.列举常见的WEB服务器和数据库服务器。2.列举你所熟知的动态网页程序设计语言。3.PHP的开始标记与结束标记有哪些,使用时有何注意,你更喜欢哪种标记方式?4.echo语句和print语句有何区别和联系?print_r实现什么功能?..._老九课堂讲义

MySQL5.5安装教程及64位/32位下载指南-程序员宅基地

文章浏览阅读5.3k次。MySQL5.5是一款开源的关系型数据库管理系统,使用这款软件可以用于数据库的管理、存储操作,拥有性能稳定、执行速度快的特点,拥有速度快、配置简单的特点,可以说是php开发者以及web管理者的必备,这个mysql5.5版本也是较为经典稳定的一个版本,支持32位和64位系统。MySQL5.5安装教程1、运行安装包,勾选同意,Next2、选择安装类型第一个选项包含了一些MySQL其他组件,如果只安装M..._下载mysql-5.5-56-win32位