一 按键驱动程序的简单实现

下面是基于中断和消息的按键驱动程序,其工作原理是:当应用程序读取键值时,会调用按键驱动程序的read函数,而我们实现的read函数检测完读取长度后没有直接读取键值而是等待按键消息,如果没有按键,程序会进入休眠状态,这样可以节省大量的CPU,而当我们按键时硬件会产生中断,程序自动进入中断处理函数,在中断处理函数中,驱动程序读取键值存入全局变量并激活read函数中等待的消息,应用程序被迅速唤醒并通过read函数读取键值,如此,完成了获取键值的工作。下面是源码,比较简单,也就不多说了。
#include <linux/types.h>  
#include <linux/module.h>  
#include <linux/cdev.h>  
#include <linux/fs.h>  
#include <linux/device.h>  
#include <linux/gpio.h>  
#include <linux/irq.h>  
#include <linux/interrupt.h>  
#include <linux/sched.h>   
#include <linux/wait.h>  
#include <linux/uaccess.h>  
 
static dev_t devno;
static struct cdev cdev;
static struct class* buttons_class;
static struct device* buttons_device;
 
static wait_queue_head_t button_waitq;
 
static volatile int pressed = 0;
static unsigned char key_val;
 
struct key_desc{
    unsigned int  pin;
    unsigned char value;
};
 
static struct key_desc key_descs[8] = {
    [0] = {
        .pin = S5PV210_GPH0(0),
        .value = 0x00,
    },
 
    [1] = {
        .pin = S5PV210_GPH0(1),
        .value = 0x01,
    },
 
    [2] = {
        .pin = S5PV210_GPH0(2),
        .value = 0x02,
    },
 
    [3] = {
        .pin = S5PV210_GPH0(3),
        .value = 0x03,
    },
 
    [4] = {
        .pin = S5PV210_GPH0(4),
        .value = 0x04,
    },
 
    [5] = {
        .pin = S5PV210_GPH0(5),
        .value = 0x05,
    },
 
    [6] = {
        .pin = S5PV210_GPH2(6),
        .value = 0x06,
    },
 
    [7] = {
        .pin = S5PV210_GPH2(7),
        .value = 0x07,
    },
};
 
static irqreturn_t buttons_irq(int irq, void *dev_id){
    volatile struct key_desc *key = (volatile struct key_desc *)dev_id;
 
    if(gpio_get_value(key->pin)){
        key_val = key->value|0x80;
    }
    else{
        key_val = key->value;
    }
 
    pressed = 1;
    wake_up_interruptible(&button_waitq);
 
    return IRQ_RETVAL(IRQ_HANDLED);
}
 
static int buttons_open(struct inode *inode, struct file *file){
    int ret;
 
    ret = request_irq(IRQ_EINT(0),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key1", &key_descs[0]);
    if(ret)
        return ret;
    ret = request_irq(IRQ_EINT(1),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key2", &key_descs[1]);
    if(ret)
        return ret;
    ret = request_irq(IRQ_EINT(2),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key3", &key_descs[2]);
    if(ret)
        return ret;
    ret = request_irq(IRQ_EINT(3),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key4", &key_descs[3]);
    if(ret)
        return ret;
    ret = request_irq(IRQ_EINT(4),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key5", &key_descs[4]);
    if(ret)
        return ret;
    ret = request_irq(IRQ_EINT(5),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key6", &key_descs[5]);
    if(ret)
        return ret;
    ret = request_irq(IRQ_EINT(22),  buttons_irq, IRQ_TYPE_EDGE_BOTH, "key7", &key_descs[6]);
    if(ret)
        return ret;
    ret = request_irq(IRQ_EINT(23),  buttons_irq, IRQ_TYPE_EDGE_BOTH, "key8", &key_descs[7]);
    if(ret)
        return ret;
    return 0;
}
 
static ssize_t buttons_read(struct file * file, char __user *data, size_t count, loff_t *loff){
    if(count != 1){
        printk(KERN_ERR "The driver can only give one key value once!\n");
        return -ENOMEM;
    }
 
    wait_event_interruptible(button_waitq, pressed);
    pressed = 0;
 
    if(copy_to_user(data, &key_val, 1)){
        printk(KERN_ERR "The driver can not copy the data to user area!\n");
        return -ENOMEM;
    }
     
    return 0;
}
 
static int buttons_close(struct inode *inode, struct file *file){
    free_irq(IRQ_EINT(0),  &key_descs[0]);
    free_irq(IRQ_EINT(1),  &key_descs[1]);   
    free_irq(IRQ_EINT(2),  &key_descs[2]);
    free_irq(IRQ_EINT(3),  &key_descs[3]);
    free_irq(IRQ_EINT(4),  &key_descs[4]);
    free_irq(IRQ_EINT(5),  &key_descs[5]);
    free_irq(IRQ_EINT(22), &key_descs[6]);
    free_irq(IRQ_EINT(23), &key_descs[7]);
    return 0;
}
 
struct file_operations buttons_ops = {
    .open    = buttons_open,
    .read    = buttons_read,
    .release = buttons_close,
};
 
int buttons_init(void){
    int ret;
 
    cdev_init(&cdev, &buttons_ops);
    cdev.owner = THIS_MODULE;
 
    ret = alloc_chrdev_region(&devno, 0, 1, "buttons");
    if(ret){
        printk(KERN_ERR "alloc char device region faild!\n");
        return ret;
    }
 
    ret = cdev_add(&cdev, devno, 1);
    if(ret){
        printk(KERN_ERR "add char device faild!\n");
        goto add_error;
    }
 
    buttons_class = class_create(THIS_MODULE, "buttonsdrv");
    if(IS_ERR(buttons_class)){
        printk(KERN_ERR "create class error!\n");
        goto class_error;
    }
 
    buttons_device = device_create(buttons_class, NULL, devno, NULL, "buttons");
    if(IS_ERR(buttons_device)){
        printk(KERN_ERR "create buttons device error!\n");
        goto device_error;
    }
 
    init_waitqueue_head(&button_waitq);
 
    return 0;
 
device_error:
    class_destroy(buttons_class);
class_error:
    cdev_del(&cdev);
add_error:
    unregister_chrdev_region(devno,1);
 
    return -ENODEV;
}
 
void buttons_exit(void){
    device_destroy(buttons_class, devno);
    class_destroy(buttons_class);
    cdev_del(&cdev);
    unregister_chrdev_region(devno, 1);
}
 
module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");

#include <linux/types.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/uaccess.h>

static dev_t devno;
static struct cdev cdev;
static struct class* buttons_class;
static struct device* buttons_device;

static wait_queue_head_t button_waitq;

static volatile int pressed = 0;
static unsigned char key_val;

struct key_desc{
 unsigned int  pin;
 unsigned char value;
};

static struct key_desc key_descs[8] = {
 [0] = {
  .pin = S5PV210_GPH0(0),
  .value = 0x00,
 },

[1] = {
  .pin = S5PV210_GPH0(1),
  .value = 0x01,
 },

[2] = {
  .pin = S5PV210_GPH0(2),
  .value = 0x02,
 },

[3] = {
  .pin = S5PV210_GPH0(3),
  .value = 0x03,
 },

[4] = {
  .pin = S5PV210_GPH0(4),
  .value = 0x04,
 },

[5] = {
  .pin = S5PV210_GPH0(5),
  .value = 0x05,
 },

[6] = {
  .pin = S5PV210_GPH2(6),
  .value = 0x06,
 },

[7] = {
  .pin = S5PV210_GPH2(7),
  .value = 0x07,
 },
};

static irqreturn_t buttons_irq(int irq, void *dev_id){
 volatile struct key_desc *key = (volatile struct key_desc *)dev_id;

if(gpio_get_value(key->pin)){
  key_val = key->value|0x80;
 }
 else{
  key_val = key->value;
 }

pressed = 1;
 wake_up_interruptible(&button_waitq);

return IRQ_RETVAL(IRQ_HANDLED);
}

static int buttons_open(struct inode *inode, struct file *file){
 int ret;

ret = request_irq(IRQ_EINT(0),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key1", &key_descs[0]);
 if(ret)
  return ret;
 ret = request_irq(IRQ_EINT(1),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key2", &key_descs[1]);
 if(ret)
  return ret;
  ret = request_irq(IRQ_EINT(2),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key3", &key_descs[2]);
 if(ret)
  return ret;
  ret = request_irq(IRQ_EINT(3),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key4", &key_descs[3]);
 if(ret)
  return ret;
 ret = request_irq(IRQ_EINT(4),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key5", &key_descs[4]);
 if(ret)
  return ret;
 ret = request_irq(IRQ_EINT(5),   buttons_irq, IRQ_TYPE_EDGE_BOTH, "key6", &key_descs[5]);
 if(ret)
  return ret;
 ret = request_irq(IRQ_EINT(22),  buttons_irq, IRQ_TYPE_EDGE_BOTH, "key7", &key_descs[6]);
 if(ret)
  return ret;
 ret = request_irq(IRQ_EINT(23),  buttons_irq, IRQ_TYPE_EDGE_BOTH, "key8", &key_descs[7]);
 if(ret)
  return ret;
 return 0;
}

static ssize_t buttons_read(struct file * file, char __user *data, size_t count, loff_t *loff){
 if(count != 1){
  printk(KERN_ERR "The driver can only give one key value once!\n");
  return -ENOMEM;
 }

wait_event_interruptible(button_waitq, pressed);
 pressed = 0;

if(copy_to_user(data, &key_val, 1)){
  printk(KERN_ERR "The driver can not copy the data to user area!\n");
  return -ENOMEM;
 }
 
 return 0;
}

static int buttons_close(struct inode *inode, struct file *file){
 free_irq(IRQ_EINT(0),  &key_descs[0]);
 free_irq(IRQ_EINT(1),  &key_descs[1]);
 free_irq(IRQ_EINT(2),  &key_descs[2]);
 free_irq(IRQ_EINT(3),  &key_descs[3]);
 free_irq(IRQ_EINT(4),  &key_descs[4]);
 free_irq(IRQ_EINT(5),  &key_descs[5]);
 free_irq(IRQ_EINT(22), &key_descs[6]);
 free_irq(IRQ_EINT(23), &key_descs[7]);
 return 0;
}

struct file_operations buttons_ops = {
 .open    = buttons_open,
 .read    = buttons_read,
 .release = buttons_close,
};

int buttons_init(void){
 int ret;

cdev_init(&cdev, &buttons_ops);
 cdev.owner = THIS_MODULE;

ret = alloc_chrdev_region(&devno, 0, 1, "buttons");
 if(ret){
  printk(KERN_ERR "alloc char device region faild!\n");
  return ret;
 }

ret = cdev_add(&cdev, devno, 1);
 if(ret){
  printk(KERN_ERR "add char device faild!\n");
  goto add_error;
 }

buttons_class = class_create(THIS_MODULE, "buttonsdrv");
 if(IS_ERR(buttons_class)){
  printk(KERN_ERR "create class error!\n");
  goto class_error;
 }

buttons_device = device_create(buttons_class, NULL, devno, NULL, "buttons");
 if(IS_ERR(buttons_device)){
  printk(KERN_ERR "create buttons device error!\n");
  goto device_error;
 }

init_waitqueue_head(&button_waitq);

return 0;

device_error:
 class_destroy(buttons_class);
class_error:
 cdev_del(&cdev);
add_error:
 unregister_chrdev_region(devno,1);

return -ENODEV;
}

void buttons_exit(void){
 device_destroy(buttons_class, devno);
 class_destroy(buttons_class);
 cdev_del(&cdev);
 unregister_chrdev_region(devno, 1);
}

module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");
测试程序代码:

[cpp]
#include <stdio.h>  
#include <fcntl.h>  
 
int main(){
    int fd = open("/dev/buttons", O_RDWR);
    if(fd < 0){
        printf("open error");;
        return 0;
    }
 
    unsigned char key;
    while(1){
        read(fd, &key, 1);
        printf("The key = %x\n", key);
    }
 
    close(fd);
}

#include <stdio.h>
#include <fcntl.h>

int main(){
 int fd = open("/dev/buttons", O_RDWR);
 if(fd < 0){
  printf("open error");;
  return 0;
 }

unsigned char key;
 while(1){
  read(fd, &key, 1);
  printf("The key = %x\n", key);
 }

close(fd);
}相比轮询方式的按键驱动程序,中断方式编写的按键驱动程序可以很大程度上节省CPU资源,因此,推荐使用中断方式。

二 支持POLL机制

上面这种方式实现的按键驱动程序有个弊端,如果我们不按键,应用程序将会永远阻塞在这里,幸运的是,linux内核提供了poll机制,可以设置超时等待时间,如果在这个时间内读取到键值则正常返回,反之则超时退出。使内核支持poll非常简单,为file_operations的poll成员提供poll处理函数即可。

使内核支持poll还需要以下几步:

添加poll头文件

[cpp]
#include <linux/poll.h>

#include <linux/poll.h>

编写poll处理函数:

[cpp]
static unsigned buttons_poll(struct file *file, poll_table *wait){
    unsigned int mask = 0;
    poll_wait(file, &button_waitq, wait);
 
    if (pressed)
        mask |= POLLIN | POLLRDNORM;
 
    return mask;
}

static unsigned buttons_poll(struct file *file, poll_table *wait){
 unsigned int mask = 0;
 poll_wait(file, &button_waitq, wait);

if (pressed)
  mask |= POLLIN | POLLRDNORM;

return mask;
}将poll处理函数添加给file_operations:

[cpp]
.poll    = buttons_poll,

.poll    = buttons_poll,这样,驱动程序就支持poll机制了。下面是poll方式的测试程序:

[cpp]
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <stdio.h>  
#include <poll.h>  
 
int main(int argc, char **argv){
    int fd;
    unsigned char key_val;
    int ret;
 
    struct pollfd fds[1];
     
    fd = open("/dev/buttons", O_RDWR);
    if (fd < 0){
        printf("can't open!\n");
    }
 
    fds[0].fd     = fd;
    fds[0].events = POLLIN;
    while (1){
        ret = poll(fds, 1, 5000);
        if (ret == 0){
            printf("time out\n");
        }
        else{
            read(fd, &key_val, 1);
            printf("key_val = 0x%x\n", key_val);
        }
    }
     
    return 0;
}

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>

int main(int argc, char **argv){
 int fd;
 unsigned char key_val;
 int ret;

struct pollfd fds[1];
 
 fd = open("/dev/buttons", O_RDWR);
 if (fd < 0){
  printf("can't open!\n");
 }

fds[0].fd     = fd;
 fds[0].events = POLLIN;
 while (1){
  ret = poll(fds, 1, 5000);
  if (ret == 0){
   printf("time out\n");
  }
  else{
   read(fd, &key_val, 1);
   printf("key_val = 0x%x\n", key_val);
  }
 }
 
 return 0;
}这样,应用程序可以限制时间,如果在一定时间内读取不到键值就可以做特殊处理,这种思想在网络通信中应用广泛。

三 支持异步机制

很多情况下,我们的程序在等待按键期间需要处理其它任务而不是在这里空等,这时,就需要采用异步模式了。所谓异步模式,实际上是采用消息机制(以本文的按键程序为例),即当驱动程序检测到按键后发送消息给应用程序,应用程序接收到消息后再去读取键值。与前面的两种模式相比,最大的不同在于异步方式是驱动告诉应用程序来读而不是应用程序主动去读。添加异步支持更加简单,首先是为file_operations注册fasync函数,函数内容如下:

[cpp]
static int buttons_fasync(int fd, struct file * file, int on){
    return fasync_helper(fd, file, on,  &button_async);
}

static int buttons_fasync(int fd, struct file * file, int on){
    return fasync_helper(fd, file, on,  &button_async);
}然后再buttons_read函数中添加一行代码,修改后的代码如下:[cpp] view plaincopyprint?static ssize_t buttons_read(struct file * file, char __user *data, size_t count, loff_t *loff){
    if(count != 1){
        printk(KERN_ERR "The driver can only give one key value once!\n");
        return -ENOMEM;
    }
 
    wait_event_interruptible(button_waitq, pressed);
    pressed = 0;
 
    if(copy_to_user(data, &key_val, 1)){
        printk(KERN_ERR "The driver can not copy the data to user area!\n");
        return -ENOMEM;
    }
 
    return 0;
}

static ssize_t buttons_read(struct file * file, char __user *data, size_t count, loff_t *loff){
    if(count != 1){
        printk(KERN_ERR "The driver can only give one key value once!\n");
        return -ENOMEM;
    }

wait_event_interruptible(button_waitq, pressed);
    pressed = 0;

if(copy_to_user(data, &key_val, 1)){
        printk(KERN_ERR "The driver can not copy the data to user area!\n");
        return -ENOMEM;
    }

return 0;
}这样,驱动程序就支持异步获取键值了,为了测试效果,测试程序也需要修改,代码如下:[cpp] view plaincopyprint?#include <fcntl.h>  
#include <stdio.h>  
#include <poll.h>  
#include <signal.h>  
#include <sys/types.h>  
#include <unistd.h>  
#include <fcntl.h>  
 
 
/* sixthdrvtest
 */
int fd;  
 
void my_signal_fun(int signum)
{
    unsigned char key_val;
    read(fd, &key_val, 1);  
    printf("key_val: 0x%x\n", key_val);
}
 
int main(int argc, char **argv)
{
    unsigned char key_val;
    int ret;
    int Oflags;
 
    signal(SIGIO, my_signal_fun);
 
    fd = open("/dev/buttons", O_RDWR | O_NONBLOCK);
    if (fd < 0){  
        printf("can't open!\n");
        return -1;  
    }    
 
    fcntl(fd, F_SETOWN, getpid());
    Oflags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, Oflags | FASYNC);
 
 
    int rest;
    while (1){
        printf("Hello\n");
        while(rest = sleep(50)){
            sleep(rest);
        }
    }
 
    return 0;
}

#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>

/* sixthdrvtest
 */
int fd;

void my_signal_fun(int signum)
{
    unsigned char key_val;
    read(fd, &key_val, 1);
    printf("key_val: 0x%x\n", key_val);
}

int main(int argc, char **argv)
{
    unsigned char key_val;
    int ret;
    int Oflags;

signal(SIGIO, my_signal_fun);

fd = open("/dev/buttons", O_RDWR | O_NONBLOCK);
    if (fd < 0){
        printf("can't open!\n");
        return -1;
    }

fcntl(fd, F_SETOWN, getpid());
    Oflags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, Oflags | FASYNC);

int rest;
    while (1){
        printf("Hello\n");
        while(rest = sleep(50)){
            sleep(rest);
        }
    }

return 0;
}这里需要注意的是,应用程序接收到消息会打断sleep,比如执行sleep(5)之后程序接收到了一个消息,这时,应用程序就被唤醒了,虽然是去执行的消息处理函数。如果程序接收到消息时仅睡眠了2秒,那么sleep被中断时会返回5-2=3,所以代码中采用while循环方式进行sleep,这样,即使接收到了消息也能完整的休眠5秒,当然,sleep函数本身是不够精确的,不过相差无几。

到这里,这个驱动程序基本上就算可以了,当然,还有对阻塞和非阻塞的支持,同步与互斥的支持,而阻塞与非阻塞无非是加上个逻辑判断,同步与互斥根应用程序的同步控制也差不多,无非就是信号量或者原子操作,这里就不多说了,如果有朋友需要这些内容可以留言讨论。

keyboard的更多相关文章

  1. Fedora 22中的Locale and Keyboard Configuration

    Introduction The system locale specifies the language settings of system services and user interface ...

  2. android:configChanges="keyboard|keyboardHidden|orientation|screenSize"

    <activity android:name="xxxActivity" android:configChanges="keyboard|keyboardHidde ...

  3. USB Keyboard Recorder

    catalogue . 引言 . Device Class Definition for Human Interface Devices (HID) . USB HID Report Descript ...

  4. imx6 matrix keyboard

    imx6需要添加4x4的矩阵键盘.本文记录添加方法. 参考链接 http://processors.wiki.ti.com/index.php/TI-Android-JB-PortingGuide h ...

  5. Codeforces Round #389 Div.2 B. Santa Claus and Keyboard Check

    time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standa ...

  6. UVa 11998 Broken Keyboard (数组模拟链表问题)

    题目链接: 传送门 Broken Keyboard #include<bits/stdc++.h> using namespace std; char str[100010]; int m ...

  7. vimium Keyboard Bindings

    Modifier keys are specified as `<c-x>`, `<m-x>`, and `<a-x>` for ctrl+x, meta+x, a ...

  8. UVa 11988 Broken Keyboard(链表->数组实现)

    /*数组形式描述链表:链表不一定要用指针. 题目链接:UVa 11988 Broken Keyboard 题目大意: 小明没有开屏幕输入一个字符串,电脑键盘出现了问题会不定时的录入 home end ...

  9. 6754 Keyboard of a Mobile Telephone

    /*实践再次说明ch=getchar()的速度非常慢*/ /*大水题,不解释*/ #include<stdio.h> #include<string.h> int main() ...

  10. uGUI练习(三) KeyBoard Navigation

    练习目标 练习通过键盘在按钮或其它Selectable类型组件上导航 步骤 创建一排的Button,及一个右边的Button 2.查看Button的属性里有一栏下拉列表Navigation,默认选择的 ...

随机推荐

  1. 剑指Offer25 二叉搜索树转换为排序双向链表

    /************************************************************************* > File Name: 25_BSTCon ...

  2. poj 1390 动态规划

    思路: 黑书的例题 #include<iostream> #include<cstring> #include<algorithm> #include<cma ...

  3. POJ 3422 Kaka's Matrix Travels K取方格数

    题目:给出n*n的方格矩阵,现在从左上方走m次到右下方,问m次能够获得的最大价值和. 分析:最大费用流.拆点进行限制每个格子只取一次,假设点x拆成 x,xx,右边(假设有)y,yy,下方(假设有)z, ...

  4. 熔断器设计模式<转>

    熔断器设计模式 如果大家有印象的话,尤其是夏天,如果家里用电负载过大,比如开了很多家用电器,就会”自动跳闸”,此时电路就会断开.在以前更古老的一种方式是”保险丝”,当负载过大,或者电路发生故障或异常时 ...

  5. asp.net从一个页面的单击按钮事件控制另一个页面的刷新

    分步说(比如你的三个页面分别为main.aspx; left.aspx;right.aspx,且点击left.aspx页面的button,则right.aspx刷新): 1. 在父页面main.asp ...

  6. OpenGl从零开始之坐标变换

    http://www.tuicool.com/articles/uiayYrI OpenGL学习脚印: 坐标变换过程(vertex transformation) http://blog.csdn.n ...

  7. PictureBox控件鼠标进入的手形改变和提示

    PictureBox控件载入了图片后,如果要设置其为链接作用的功能,一般需要当鼠标移动到其上时鼠标自动变为手形以提示用户此时可以点击,如果图形无法方便辨识链接的具体功能,最好此时给与一定提示. 1.鼠 ...

  8. DNS map file in windows

    Edit "C:\WINDOWS\system32\drivers\etc\hosts", add the IP to DNS name mapping.

  9. SQL取某个字段最大(小)数值及其相应行的其他字段值的句语

    如下表Z 中,取 字段a 最大的那行 字段a           字段a    字段cSP000016964 5 20SP000016964 7 30SP000016964 1 15SP0000177 ...

  10. C#反射技术的简单操作(读取和设置类的属性)

    public class A { public int Property1 { get; set; } } static void Main(){ A aa = new A(); Type type ...