記憶體映射函數remap_pfn_range學習——示例分析(1)

来源:https://www.cnblogs.com/pengdonglin137/archive/2017/12/30/8149859.html
-Advertisement-
Play Games

作者 彭東林 QQ 405728433 平臺 Linux-4.10.17 Qemu-2.8 + vexpress-a9 DDR:1GB 參考 Linux 虛擬記憶體和物理記憶體的理解 Linux進程分配記憶體的兩種方式--brk() 和mmap() Linux中的mmap的使用 程式(進程)記憶體分佈 解析 ...


作者

彭東林 QQ 405728433  

平臺

Linux-4.10.17 Qemu-2.8 + vexpress-a9 DDR:1GB  

參考

Linux 虛擬記憶體和物理記憶體的理解 Linux進程分配記憶體的兩種方式--brk() 和mmap() Linux中的mmap的使用 程式(進程)記憶體分佈 解析  

概述

Linux內核提供了remap_pfn_range函數來實現將內核空間的記憶體映射到用戶空間:
 1 /**
 2  * remap_pfn_range - remap kernel memory to userspace
 3  * @vma: user vma to map to
 4  * @addr: target user address to start at
 5  * @pfn: physical address of kernel memory
 6  * @size: size of map area
 7  * @prot: page protection flags for this mapping
 8  *
 9  *  Note: this is only safe if the mm semaphore is held when called.
10  */
11 int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
12             unsigned long pfn, unsigned long size, pgprot_t prot);
上面的註釋對參數進行了說明。當用戶調用mmap時,驅動中的file_operations->mmap會被調用,可以在mmap中調用remap_pfn_range,它的大部分參數的值都由VMA提供。具體可以參考LDD3的P420.  

正文

下麵結合一個簡單的例子學習一下。在驅動中申請一個32個Page的緩衝區,這裡的PAGE_SIZE是4KB,所以內核中的緩衝區大小是128KB。user_1和user_2將前64KB映射到自己的用戶空間,其中user_1向緩衝區中寫入字元串,user_2去讀取。user_3和user_4將後64KB映射到自己的用戶空間,其中user_3向緩衝區中寫入字元串,user_4讀取字元串。user_5將整個128KB映射到自己的用戶空間,然後將緩衝區清零。此外,在驅動中申請緩衝區的方式有多種,可以用kmalloc、也可以用alloc_pages,當然也可用vmalloc,下麵會分別針對這三個介面實現驅動。   涉及到的測試程式和驅動程式可以到下麵的鏈接下載: https://github.com/pengdonglin137/remap_pfn_demo  

一、驅動程式

下麵現以kzalloc申請緩衝區的方式為例介紹,調用kmalloc申請32個頁,我們知道kzalloc返回的虛擬地址的特點是對應的物理地址也是連續的,所以在調用remap_pfn_range的時候很方便。首先在驅動init的時候申請128KB的緩衝區:
 1 static int __init remap_pfn_init(void)
 2 {
 3     int ret = 0;
 4 
 5     kbuff = kzalloc(BUF_SIZE, GFP_KERNEL);  // 這裡的BUF_SIZE是128KB
 6     if (!kbuff) {
 7         ret = -ENOMEM;
 8         goto err;
 9     }
10 
11     ret = misc_register(&remap_pfn_misc);   // 註冊一個misc設備
12     if (unlikely(ret)) {
13         pr_err("failed to register misc device!\n");
14         goto err;
15     }
16 
17     return 0;
18 
19 err:
20     return ret;
21 }

第11行註冊了一個misc設備,相關信息如下:

1 static struct miscdevice remap_pfn_misc = {
2     .minor = MISC_DYNAMIC_MINOR,
3     .name = "remap_pfn",
4     .fops = &remap_pfn_fops,
5 };

這樣載入驅動後會在/dev下生成一個名為remap_pfn的節點,用戶程式可以通過這個節點跟驅動通信。其中remap_pfn_fops的定義如下:

1 static const struct file_operations remap_pfn_fops = {
2     .owner = THIS_MODULE,
3     .open = remap_pfn_open,
4     .mmap = remap_pfn_mmap,
5 };

第3行的open函數這裡沒有做什麼實際的工作,只是列印一些log,比如將進程的記憶體佈局信息輸出

第4行,負責處理用戶的mmap請求,這是需要關心的。 先看一下open函數具體列印了那些內容:
 1 static int remap_pfn_open(struct inode *inode, struct file *file)
 2 {
 3     struct mm_struct *mm = current->mm;
 4 
 5     printk("client: %s (%d)\n", current->comm, current->pid);
 6     printk("code  section: [0x%lx   0x%lx]\n", mm->start_code, mm->end_code);
 7     printk("data  section: [0x%lx   0x%lx]\n", mm->start_data, mm->end_data);
 8     printk("brk   section: s: 0x%lx, c: 0x%lx\n", mm->start_brk, mm->brk);
 9     printk("mmap  section: s: 0x%lx\n", mm->mmap_base);
10     printk("stack section: s: 0x%lx\n", mm->start_stack);
11     printk("arg   section: [0x%lx   0x%lx]\n", mm->arg_start, mm->arg_end);
12     printk("env   section: [0x%lx   0x%lx]\n", mm->env_start, mm->env_end);
13 
14     return 0;
15 }
第5行將進程的名字以及pid列印出來 第6行列印進程的代碼段的範圍 第7行列印進程的data段的範圍,其中存放的是已初始化全局變數。而bss段存放的是未初始化全局變數,存放位置緊跟在data段後面,堆區之前 第8行列印進程的堆區的起始地址和當前地址 第9行列印進程的mmap區的基地址,這裡的mmap區是向下增長的。具體mmap區的基地址跟系統允許的當前進程的用戶棧的大小有關,用戶棧的最大size越大,mmap區的基地址就越小。修改用戶棧的最大尺寸需要用到ulimit -s xxx命令,單位是KB,表示用戶棧的最大尺寸,用戶棧的尺寸可以上G,而內核棧卻只有區區的2個頁。 第10行列印進程的用戶棧的起始地址,向下增長 第11行和第12行的暫不關心。   下麵是remap_pfn_mmap的實現:
 1 static int remap_pfn_mmap(struct file *file, struct vm_area_struct *vma)
 2 {
 3     unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
 4     unsigned long pfn_start = (virt_to_phys(kbuff) >> PAGE_SHIFT) + vma->vm_pgoff;
 5     unsigned long virt_start = (unsigned long)kbuff + offset;
 6     unsigned long size = vma->vm_end - vma->vm_start;
 7     int ret = 0;
 8 
 9     printk("phy: 0x%lx, offset: 0x%lx, size: 0x%lx\n", pfn_start << PAGE_SHIFT, offset, size);
10 
11     ret = remap_pfn_range(vma, vma->vm_start, pfn_start, size, vma->vm_page_prot);
12     if (ret)
13         printk("%s: remap_pfn_range failed at [0x%lx  0x%lx]\n",
14             __func__, vma->vm_start, vma->vm_end);
15     else
16         printk("%s: map 0x%lx to 0x%lx, size: 0x%lx\n", __func__, virt_start,
17             vma->vm_start, size);
18 
19     return ret;
20 }

第3行的vma_pgoff表示的是該vma表示的區間在緩衝區中的偏移地址,單位是頁。這個值是用戶調用mmap時傳入的最後一個參數,不過用戶空間的offset的單位是位元組(當然必須是頁對齊),進入內核後,內核會將該值右移PAGE_SHIFT(12),也就是轉換為以頁為單位。因為要在第9行列印這個編譯地址,所以這裡將其再左移PAGE_SHIFT,然後賦值給offset。

第4行計算內核緩衝區中將被映射到用戶空間的地址對應的物理頁幀號。virt_to_phys接受的虛擬地址必須在低端記憶體範圍內,用戶將虛擬地址轉換為物理地址,而vmaloc返回的虛擬地址不在低端記憶體範圍內,所以需要用專門的函數。 第5行計算內核緩衝區中將被映射到用戶空間的地址對應的虛擬地址 第6行計算該vma表示的記憶體區間的大小 第11行調用remap_pfn_range將物理頁幀號pfn_start對應的物理記憶體映射到用戶空間的vm->vm_start處,映射長度為該虛擬記憶體區的長度。由於這裡的內核緩衝區是用kzalloc分配的,保證了物理地址的連續性,所以會將物理頁幀號從pfn_start開始的(size >> PAGE_SHIFT)個連續的物理頁幀依次按序映射到用戶空間。   將驅動編譯成模塊後,insmod到內核。  

二、用戶測試程式

這裡的五個測試程式都很簡單,只是為了證明他們之間確實共用了同一塊記憶體。 user_1.c:
 1 #define PAGE_SIZE (4*1024)
 2 #define BUF_SIZE (16*PAGE_SIZE)
 3 #define OFFSET (0)
 4 
 5 int main(int argc, const char *argv[])
 6 {
 7     int fd;
 8     char *addr = NULL;
 9 
10     fd = open("/dev/remap_pfn", O_RDWR);
11     
12     addr = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, OFFSET);
13     
14     sprintf(addr, "I am %s\n", argv[0]);
15 
16     while(1)
17         sleep(1);
18     return 0;
19 }

第10和第12行,打開設備節點,然後從內核空間映射64KB的記憶體到用戶空間,首地址存放在addr中,由於後面既要寫入也要共用,所以設置了對應的flags。這裡指定的offset是0,即映射前64KB。

第14行輸出字元串到addr指向的虛擬地址空間   user_2.c:
 1 #define PAGE_SIZE (4*1024)
 2 #define BUF_SIZE (16*PAGE_SIZE)
 3 #define OFFSET (0)
 4 
 5 int main(int argc, const char *argv[])
 6 {
 7     int fd;
 8     char *addr = NULL;
 9 
10     fd = open("/dev/remap_pfn", O_RDWR);
11     
12     addr = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, OFFSET);
13     
14     printf("%s", addr);
15 
16     while(1)
17         sleep(1);
18 
19     return 0;
20 }

user_2跟user_1實現一般一樣,不同之處是將addr指向的虛擬地址空間的內容列印出來。

  user_3.c:
 1 #define PAGE_SIZE (4*1024)
 2 #define BUF_SIZE (16*PAGE_SIZE)
 3 #define OFFSET (16*PAGE_SIZE)
 4 
 5 int main(int argc, const char *argv[])
 6 {
 7     int fd;
 8     char *addr = NULL;
 9 
10     fd = open("/dev/remap_pfn", O_RDWR);
11 
12     addr = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, OFFSET);
13 
14     sprintf(addr, "I am %s\n", argv[0]);
15 
16     while(1)
17         sleep(1);
18     return 0;
19 }

第12行的OFFSET設置的是64KB,表示將內核緩衝區的後64KB映射到用戶空間

第14行,向緩衝區中輸入字元串   user_4.c:
 1 #define PAGE_SIZE (4*1024)
 2 #define BUF_SIZE (16*PAGE_SIZE)
 3 #define OFFSET (16*PAGE_SIZE)
 4 
 5 int main(int argc, const char *argv[])
 6 {
 7     int fd;
 8     char *addr = NULL;
 9 
10     fd = open("/dev/remap_pfn", O_RDWR);
11 
12     addr = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, OFFSET);
13 
14     printf("%s", addr);
15 
16     while(1)
17         sleep(1);
18     return 0;
19 }
第12行的OFFSET設置的是64KB,表示將內核緩衝區的後64KB映射到用戶空間 第14行,輸出緩衝區中內容   user_5.c:
 1 #define PAGE_SIZE (4*1024)
 2 #define BUF_SIZE (32*PAGE_SIZE)
 3 #define OFFSET (0)
 4 
 5 int main(int argc, const char *argv[])
 6 {
 7     int fd;
 8     char *addr = NULL;
 9     int *brk;
10 
11     fd = open("/dev/remap_pfn", O_RDWR);
12 
13     addr = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, 0);
14     memset(addr, 0x0, BUF_SIZE);
15 
16     printf("Clear Finished\n");
17 
18     while(1)
19         sleep(1);
20     return 0;
21 }
第13行,將內核緩衝區的整個128KB都映射到用戶空間 第14行,清除緩衝區中內容  

三、測試

1、內核空間的虛擬記憶體佈局

在內核的啟動log里可以看到內核空間的虛擬記憶體佈局信息:
 1 [    0.000000] Virtual kernel memory layout:
 2 [    0.000000]     vector  : 0xffff0000 - 0xffff1000   (   4 kB)
 3 [    0.000000]     fixmap  : 0xffc00000 - 0xfff00000   (3072 kB)
 4 [    0.000000]     vmalloc : 0xf0800000 - 0xff800000   ( 240 MB)
 5 [    0.000000]     lowmem  : 0xc0000000 - 0xf0000000   ( 768 MB)
 6 [    0.000000]     pkmap   : 0xbfe00000 - 0xc0000000   (   2 MB)
 7 [    0.000000]     modules : 0xbf000000 - 0xbfe00000   (  14 MB)
 8 [    0.000000]       .text : 0xc0008000 - 0xc0800000   (8160 kB)
 9 [    0.000000]       .init : 0xc0b00000 - 0xc0c00000   (1024 kB)
10 [    0.000000]       .data : 0xc0c00000 - 0xc0c7696c   ( 475 kB)
11 [    0.000000]        .bss : 0xc0c78000 - 0xc0cc9b8c   ( 327 kB)
用kzalloc分配的記憶體會落在第5行表示的虛擬記憶體範圍內 用vmalloc分配的記憶體會落在第4行表示的虛擬記憶體範圍內  

2、用戶虛擬地址空間的佈局

下麵是Linux系統下用戶的虛擬記憶體佈局大致信息:    

3、user_1和user_2

運行user1:
[root@vexpress mnt]# ./user_1

可以看到如下內核log:

 1 [ 2494.835749] client: user_1 (870)
 2 [ 2494.835918] code  section: [0x8000   0x87f4]
 3 [ 2494.836047] data  section: [0x107f4   0x1092c]
 4 [ 2494.836165] brk   section: s: 0x11000, c: 0x11000
 5 [ 2494.836307] mmap  section: s: 0xb6f17000
 6 [ 2494.836441] stack section: s: 0xbe909e20
 7 [ 2494.836569] arg   section: [0xbe909f23   0xbe909f2c]
 8 [ 2494.836689] env   section: [0xbe909f2c   0xbe909ff3]
 9 [ 2494.836943] phy: 0x8eb60000, offset: 0x0, size: 0x10000
10 [ 2494.837176] remap_pfn_mmap: map 0xeeb60000 to 0xb6d75000, size: 0x10000

 

進程號是870,可以分別用下麵的查看一下該進程的地址空間的map信息:

 1 [root@vexpress mnt]# cat /proc/870/maps 
 2 00008000-00009000 r-xp 00000000 00:12 1179664    /mnt/user_1
 3 00010000-00011000 rw-p 00000000 00:12 1179664    /mnt/user_1
 4 b6d75000-b6d85000 rw-s 00000000 00:10 8765       /dev/remap_pfn
 5 b6d85000-b6eb8000 r-xp 00000000 b3:01 143        /lib/libc-2.18.so
 6 b6eb8000-b6ebf000 ---p 00133000 b3:01 143        /lib/libc-2.18.so
 7 b6ebf000-b6ec1000 r--p 00132000 b3:01 143        /lib/libc-2.18.so
 8 b6ec1000-b6ec2000 rw-p 00134000 b3:01 143        /lib/libc-2.18.so
 9 b6ec2000-b6ec5000 rw-p 00000000 00:00 0 
10 b6ec5000-b6ee6000 r-xp 00000000 b3:01 188        /lib/libgcc_s.so.1
11 b6ee6000-b6eed000 ---p 00021000 b3:01 188        /lib/libgcc_s.so.1
12 b6eed000-b6eee000 rw-p 00020000 b3:01 188        /lib/libgcc_s.so.1
13 b6eee000-b6f0e000 r-xp 00000000 b3:01 165        /lib/ld-2.18.so
14 b6f13000-b6f15000 rw-p 00000000 00:00 0 
15 b6f15000-b6f16000 r--p 0001f000 b3:01 165        /lib/ld-2.18.so
16 b6f16000-b6f17000 rw-p 00020000 b3:01 165        /lib/ld-2.18.so
17 be8e9000-be90a000 rw-p 00000000 00:00 0          [stack]
18 bed1c000-bed1d000 r-xp 00000000 00:00 0          [sigpage]
19 bed1d000-bed1e000 r--p 00000000 00:00 0          [vvar]
20 bed1e000-bed1f000 r-xp 00000000 00:00 0          [vdso]
21 ffff0000-ffff1000 r-xp 00000000 00:00 0          [vectors]

上面的每一行都可以表示一個vma的映射信息,其中第4行是需要關心的:

1 b6d75000-b6d85000 rw-s 00000000 00:10 8765      /dev/remap_pfn

含義:

"b6d75000"是vma->vm_start的值,"b6d85000"是vma->vm_end的值,b6d85000減b6d75000是64KB,即給vma表示的虛擬記憶體區域的大小。 "rw-s"表示的是vma->vm_flags,其中's'表示share,'p'表示private "00000000"表示偏移量,也就是vma->vm_pgoff的值 "00:10"表示該設備節點的主次設備號 "8765"表示該設備節點的inode值 "/dev/remap_pfn"表示設備節點的名字。   也可以用pmap查看該進程的虛擬地址空間映射信息:
 1 [root@vexpress mnt]# pmap -x 870
 2 870: {no such process} ./user_1
 3 Address      Kbytes     PSS   Dirty    Swap  Mode  Mapping
 4 00008000       4       4       0       0  r-xp  /mnt/user_1
 5 00010000       4       4       4       0  rw-p  /mnt/user_1
 6 b6d75000      64       0       0       0  rw-s  /dev/remap_pfn
 7 b6d85000    1228     424       0       0  r-xp  /lib/libc-2.18.so
 8 b6eb8000      28       0       0       0  ---p  /lib/libc-2.18.so
 9 b6ebf000       8       8       8       0  r--p  /lib/libc-2.18.so
10 b6ec1000       4       4       4       0  rw-p  /lib/libc-2.18.so
11 b6ec2000      12       8       8       0  rw-p    [ anon ]
12 b6ec5000     132      64       0       0  r-xp  /lib/libgcc_s.so.1
13 b6ee6000      28       0       0       0  ---p  /lib/libgcc_s.so.1
14 b6eed000       4       4       4       0  rw-p  /lib/libgcc_s.so.1
15 b6eee000     128     122       0       0  r-xp  /lib/ld-2.18.so
16 b6f13000       8       8       8       0  rw-p    [ anon ]
17 b6f15000       4       4       4       0  r--p  /lib/ld-2.18.so
18 b6f16000       4       4       4       0  rw-p  /lib/ld-2.18.so
19 be8e9000     132       4       4       0  rw-p  [stack]
20 bed1c000       4       0       0       0  r-xp  [sigpage]
21 bed1d000       4       0       0       0  r--p  [vvar]
22 bed1e000       4       0       0       0  r-xp  [vdso]
23 ffff0000       4       0       0       0  r-xp  [vectors]
24 --------  ------  ------  ------  ------
25 total        1808     662      48       0

 

然後運行user_2:
1 [root@vexpress mnt]# ./user_2
2 I am ./user_1

可以看到user_1寫入的信息,下麵是內核log以及虛擬地址空間映射信息:

 1 [ 2545.832903] client: user_2 (873)
 2 [ 2545.833087] code  section: [0x8000   0x87e0]
 3 [ 2545.833178] data  section: [0x107e0   0x10918]
 4 [ 2545.833262] brk   section: s: 0x11000, c: 0x11000
 5 [ 2545.833346] mmap  section: s: 0xb6fb5000
 6 [ 2545.833423] stack section: s: 0xbea0ee20
 7 [ 2545.833499] arg   section: [0xbea0ef23   0xbea0ef2c]
 8 [ 2545.833590] env   section: [0xbea0ef2c   0xbea0eff3]
 9 [ 2545.833761] phy: 0x8eb60000, offset: 0x0, size: 0x10000
10 [ 2545.833900] remap_pfn_mmap: map 0xeeb60000 to 0xb6e13000, size: 0x10000
11 
12 [root@vexpress mnt]# cat /proc/873/maps 
13 00008000-00009000 r-xp 00000000 00:12 1179665    /mnt/user_2
14 00010000-00011000 rw-p 00000000 00:12 1179665    /mnt/user_2
15 b6e13000-b6e23000 rw-s 00000000 00:10 8765       /dev/remap_pfn
16 b6e23000-b6f56000 r-xp 00000000 b3:01 143        /lib/libc-2.18.so
17 b6f56000-b6f5d000 ---p 00133000 b3:01 143        /lib/libc-2.18.so
18 b6f5d000-b6f5f000 r--p 00132000 b3:01 143        /lib/libc-2.18.so
19 

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • C# //將格式為yyyyMMdd的時間轉化為yyyy-MM-dd類型 詳例: string beginDate="20140224"; beginDate =DateTime.ParseExact(beginDate,"yyyyMMdd",CultureInfo.CurrentCulture).T ...
  • 這次由於項目的需求:什麼定時發送郵件通知,定時篩選取消客戶下單未支付的訂單 重新撿起定時器,在網上翻來找去找到 Quartz.Net老字型大小了並不表示它就真的老了哦 github:https://github.com/quartznet/quartznet 當然介紹的園子里文章很多跟官方文檔https ...
  • DataTable轉List public static List<T> ToListModel<T>(this DataTable table) where T : new() { var type = typeof(T); var properties = type.GetProperties( ...
  • 3day Python基礎語法 1、運算符:算數運算符、比較運算符、賦值運算符、邏輯運算符 A、算數運算符:a=10,b=3 + 加 a+b - 減 a-b * 乘 a*b / 除 a/b %取模 a%b (取餘數) //取整數 a//b (取商的整數部分) **冪 a**b (a的b次方) B、比 ...
  • 1,首先需要在一臺有MacOS系統,在Apple stroe下載MacOS High Sierra安裝程式; 2,準備一個至少8G容量的U盤; 3,打開 “應用程式 → 實用工具 → 磁碟工具”,將U盤「抹掉」(格式化) 成「Mac OS X 擴展(日誌式)」 格式、GUID 分區圖,並將U盤命名為 ...
  • python ./setup.py install --record install.txt cat install.txt | xargs rm -rf ...
  • 硬體:pixhawk 2.4.8 代碼:2017/12/30 PX4 firemare px4fmuv2 MCU: STM32F427VIT6, STN32F103C8T6. 感測器: MPU6000; L3GD20; LSM303D; MS5611, 省略了一個L3GD20焊盤. CAN口用的是M ...
  • fxml文件使用SceneBuilder打開報錯 解決方法:Window-->Preferences-->JavaFX-->browse 路徑是可執行的JavaFX Scene Builder文件,例如:C:\Program Files (x86)\Oracle\JavaFX Scene Build ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...