Step to UEFI (161)Print 直接输出 ASCII 的String

之前我在处理 ASCII 的 String 时,都是进行格式化后再输出的,最近偶然间看到了 Print 有一个 %a 参数可以用来直接输出 CHAR8 这样定义的字符串。实验代码如下:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Library/BaseMemoryLib.h>

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{

    CHAR8      Buffer[]="This is a simple test\n\r from www.lab-z.com\n\r";

    Print(L"%a", Buffer);
  return EFI_SUCCESS;
}

 

运行结果:

完整的代码下载:
Printa

关于 Print 函数的具体实现,可以在 MdePkg\Library\BasePrintLib\PrintLibInternal.c 中看到。

Step to UEFI (163)替换已经存在Protocol中函数的实验

熟悉汇编语言的朋友都知道,DOS下面有很多INT服务,通过这样的入口能够实现硬件相关的服务。比如:INT 15 中可以支持关闭显示。与之类似,UEFI 里面是通过Protocol来实现这样的功能的。找到需要的 Protocol 即可 Call 到其提供的函数。这样的设计也给我们一个机会,可以用自己编写的函数来替换真实的函数。为此,设计了一个实验替换掉 Simple File System 中提供的 Read函数,这样每次 Application 读取到的都会是我们提供的数据。
代码比较长,从原理的角度解释具体流程:
首先,编写测试用的 Application,用 LocateHandleBuffer 枚举出来所有的 Simple File System Protocol,然后在找到的 Handle 上用 OpenVolume 打开 Root Directory。打开之后再用Open 函数打开文件,最后用 Read 来读取文件内容并且显示出来。实验中读取的是一个 44 Bytes 长的 Test.txt 文件。
实验环境是 NT32模拟器,我们只在 fs0: 上放置了 test.txt,因此,能找到2个SimpleFileSystemProtcol,第一个能够读取并且显示内容,第二个会出现Open File error 的错误。

接下来,编写我们的“驱动”,为了简单起见,这里并不是一个正经的驱动。对于我们来说,只是需要程序常驻内存,结束之后不希望被释放掉,否则会出现其他程序调用而无法找到函数的情况。因此,我们在 MdeModulePkg 中写程序,编译之后使用 Load 来加载。代码流程如下:
1. 在入口 MyEntryPoint 中查找一个 SimpleFileSystemProtocol,先保存OpenVolume 的实际入口,再用自己编写的 MySimpleFileSystemOpenVolume替换掉这个入口;
2. Application 在调用 OpenVolume 函数的时候,实际上是会进入MySimpleFileSystemOpenVolume 中。在这个函数中,使用之前保存的实际OpenVolume完成工作,然后将 OpenVolume返回的EFI_FILE_PROTOCOL替换为 MySimpleFileSystemOpen;
3. Application在调用 Open 函数的时候,实际上是会进入MySimpleFileSystemOpen。我们在这里检查参数判断要打开的是否为我们指定的 test.txt 文件,如果是,那么用 MySimpleFileSystemOpen来替换实际的 Open 函数;
4. 这样,当 Application 要Read test.txt 的时候,我们就可以送一个假的值出去。这样就实现了替换的功能。

完整的代码:
用于测试的 Application

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Library/BaseMemoryLib.h>
#include  <Protocol/SimpleFileSystem.h>
#include  <Library/MemoryAllocationLib.h>
                        
extern EFI_RUNTIME_SERVICES      *gRT;
extern EFI_BOOT_SERVICES         *gBS;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{

    EFI_STATUS Status;
    EFI_HANDLE  *HandleBuffer = NULL;
    UINTN       NumHandles;
    UINTN       Index; 
    EFI_FILE_IO_INTERFACE *ioDevice; 
    EFI_FILE_HANDLE handleRoots;
    EFI_FILE_PROTOCOL *TestFile;
    CHAR8      Buffer[4*1024];
    UINTN      BufferSize;
    
    //Find all Simnple File System Protocol Handle
    Status = gBS->LocateHandleBuffer(
        ByProtocol,
        &gEfiSimpleFileSystemProtocolGuid,
        NULL,
        &NumHandles,
        &HandleBuffer);
        
    Print(L"Walk handles %ld\n", NumHandles);
    for(Index =0; Index<NumHandles; Index++){
        //Get one Simple File Protocol from a Handle    
        Status = gBS->HandleProtocol (
                        HandleBuffer[Index],
                        &gEfiSimpleFileSystemProtocolGuid,
                        (VOID **) &ioDevice
                        );
        //Opens the root directory on the volume                
        Status = ioDevice->OpenVolume( ioDevice, &handleRoots );   
        if (EFI_ERROR(Status)) {
                Print(L"OpenVolume error \n");
        }           
        //Open a file
        Status = handleRoots->Open(handleRoots,&TestFile,L"test.txt",EFI_FILE_MODE_READ, 0);
        if (!EFI_ERROR(Status)) {
                //The size of "Test.txt" is 44bytes, I use hard code here
                BufferSize=44;
                TestFile->Read(TestFile,&BufferSize,&Buffer);
                Print(L"%a",Buffer);
                TestFile->Close(TestFile);
        }
        else
        {
                Print(L"Open file error [%r]\n",Status);    
        }
    }
    
  FreePool (HandleBuffer);
  
  return EFI_SUCCESS;
}

 

简单的“驱动” 代码

#include <PiDxe.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/MemoryAllocationLib.h>
#include <Protocol/SimpleFileSystem.h>
#include <Library/MemoryAllocationLib.h>

extern EFI_SYSTEM_TABLE         *gST;

EFI_GUID        gEfiSimpleFileSystemProtocolGuid ={ 0x964E5B22, 0x6459, 0x11D2, 
                        { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }};

//Backup of Read function                        
EFI_FILE_READ OldSimpleFileSystemRead;
//Backup of Open function
EFI_FILE_OPEN OldSimpleFileSystemOpen;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_OPEN_VOLUME         OldSimpleFileSystemOpenVolume;

//This one will replace the Read function of Read in Simple File System
EFI_STATUS
EFIAPI
MySimpleFileSystemRead (
  IN     EFI_FILE_PROTOCOL  *This,
  IN OUT UINTN              *BufferSize,
  OUT    VOID               *Buffer 
)
{
     CHAR8   TestBuffer[]="Replaced buffer\n\r";
     AsciiStrCpyS(Buffer,AsciiStrnLenS(TestBuffer,255)+1,TestBuffer);
     return EFI_SUCCESS;
}

//This one will replace the Open function of Open in Simple File System
EFI_STATUS
EFIAPI
MySimpleFileSystemOpen (
  IN  EFI_FILE_PROTOCOL  *This,
  OUT EFI_FILE_PROTOCOL  **NewHandle,
  IN  CHAR16             *FileName,
  IN  UINT64             OpenMode,
  IN  UINT64             Attributes
  )
{
        EFI_STATUS Status;

        //Call into Open function in the Simple File system
        Status=(*OldSimpleFileSystemOpen)(This,NewHandle,FileName,OpenMode,Attributes);
        if (!EFI_ERROR(Status)) {
                //Check the filename to make sure it's the one we will replace
                if (StrnCmp(FileName,L"test.txt",StrLen(FileName))==0) {
                        if ((**NewHandle).Read!=MySimpleFileSystemRead) {
                                //Backup the Read Function
                                OldSimpleFileSystemRead=(**NewHandle).Read;
                                //Replace the Read Function
                                (**NewHandle).Read=MySimpleFileSystemRead;
                        }
                        
                }
        }
       return Status;
} 


EFI_STATUS
EFIAPI
MySimpleFileSystemOpenVolume (
  IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL  *This,
  OUT EFI_FILE_PROTOCOL               **Root
  )
{
        EFI_STATUS Status;
        
        Status=(*OldSimpleFileSystemOpenVolume)(This,Root);
         
        if (!EFI_ERROR(Status)) {
                if ((**Root).Open!=MySimpleFileSystemOpen) {
                        OldSimpleFileSystemOpen=(**Root).Open;
                        (**Root).Open=MySimpleFileSystemOpen;
                }        
        }
       return Status;
}  
EFI_STATUS
EFIAPI
MyEntryPoint (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
        EFI_STATUS        Status;
        //EFI_FILE_PROTOCOL *Root;
        EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFile;
        
        //Look for one Simple File System Protocol 
        Status = gBS->LocateProtocol (
                        &gEfiSimpleFileSystemProtocolGuid,
                        NULL,
                        &SimpleFile);
        if (EFI_ERROR(Status)) {
           gST->ConOut->OutputString(gST->ConOut,L"Can't find Simple File PROTOCOL\n");
           return Status;
        }
        OldSimpleFileSystemOpenVolume=SimpleFile->OpenVolume;
        SimpleFile->OpenVolume=MySimpleFileSystemOpenVolume;
        
        return Status;
}

 

运行结果:

完整的代码下载:

repfun

Devcon 下载

最近需要使用 DEVCON 又不想下载庞大的 WDK ,网上搜索了一下,找到如下的方法:

Windows 10 version 1803 Redstone 4
(April 2018 Update)
Windows Build: 10.0.17134
Driver Kit Build: 10.0.17134

X86

AMD64

下载之后,用7Z 之类的工具进行解压,在解压之后的文件中查找 filbad6e2cce5ebc45a401e19c613d0a28f 文件,改名为 devcon.exe 即可。

参考:
1.http://www.cnblogs.com/litifeng/p/9211792.html 如何安全的下载Devcon.exe文件
2.https://superuser.com/questions/1002950/quick-method-to-install-devcon-exe

Intel WHL HDK 来了

Whiskey Lake 是Intel 2018年推出的更新8代酷睿CPU,我最近拿到了一块六联智能(SIXUNITED)推出的 Whiskey Lake HDK 平台,这也是和我之前使用的 KabyLake-R HDK 属于同一系列的开发板。

相比之下,WHL HDK 的板子比之前 KBL-R 要小很多,有如下接口:
1. MIPI Camera
2. M.2 Sata/PCIE NVME
3. USB 接口,支持DCI 调试
4. M.2 PCIE 接口,支持WIFI、CNVI
5. PCIE x4 Slot
6. WWAN 接口 + SIM卡接口
7. Intel Sensor Hub 接口
8. HDMI 接口

开发板仍然采用 IO Board + Core Board 的设计方式,二者通过中间的 DIMM 插槽进行连接(去掉散热器的 Core Board 可以看到 SoC 还是挺大的):

此外,该板加入了检测功耗的功能,能够在本机或者远端机器上读取当前的各路电流功耗信息,从而能够实现监视每个设备的功耗的功能。下图是配套软件在另外的机器上读取功耗的情况,因为我不是硬件工程师,所以没做更详细的测试。

工作的照片,板载数码管直接输出80Port值,这样能够很方便的让用户识别当前状态。右下角的USB是提供给其他机器读取当前功耗信息的。

整体来说吗,感觉 WHL 平台功耗挺低的,在测试过程中,风扇转动的次数不多。

官网为http://www.hdkboards.com/ 有兴趣的朋友可以上去查看更多信息。

常见电容参数识别

常见电容器参数识别方法

电容单位是F

1法拉(F)= 1000毫法(mF)=1000000微法(μF)

1微法(μF)= 1000纳法(nF)= 1000000皮法(pF)   【参考1】

常见的电容标注:

  1. 直接在电容器上标注的,比如510P 10%。这种很容易识别。
  2. 三位数表示方法,前两位表示有效数字,第三位表示倍乘,有时候后面跟着一个字母表示允许误差,整体单位是 pf。比如:102表示 10X10^2pf。常见的标注如下
1PF 20PF(200) 100PF(101) 510PF(511) 10NF(103) 220NF(224)
4.7PF 22P(220) 150PF(151) 680PF(681) 22NF(223) 330NF(334)
6.8PF 30PF(300) 180PF(181) 1NF(102) 33NF(333) 470NF(474)
10PF(100) 33PF(330) 220PF(221) 2.2NF(222) 47NF(473) 1UF(105)
15PF(150) 47PF(470) 330PF(331) 3.3NF(332) 68NF(683) 2.2UF(225)
18PF(180) 68PF(680) 470PF(471PF) 4.7NF(472) 100NF(104) 4.7UF(475)
3.3UF(335) 10UF(106)
  1. 四位数字表示方法,有下面2中情况
    1. 4位整数,此时单位是pf。比如:6800 表示 6800pf
    2. 用小数(有时不足4位),此时单位是 uf。比如:47表示 0.47uf。【参考2】

参考:

  1. https://baike.baidu.com/item/%E7%94%B5%E5%AE%B9/146658?fr=aladdin#2
  2. 电子工程师必备—-元器件应用宝典

Step to UEFI (160)UEFI 下的 GIF 解码

现在最常见的 GIF 就是各种动图了,因为它足够小,无需支付版权费用和不用安装额外的插件,使得它成为分享动图的首选。
这次介绍来自https://github.com/lecram/gifdec 的 GIF 解码库,可以用来解码静态和动态GIF格式。
需要注意的是,这个库有下面2个局限(GIF 最多支持256色,这个限制是来自他的调色板只有256色。通常情况下,一幅有很多帧的动图都会共享同一个调色板。特殊情况下,每一帧都有一个调色板。这个库不支持后面那种情况):
* no support for GIF files that don’t have a global color table
* no direct support for the plain text extension (rarely used)

实例程序如下,我在 IA32 和 X64 NT32环境下测试过,都是可以正常工作的:

#include <stdlib.h>
#include <stdio.h>
#include <Protocol/GraphicsOutput.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include "gifdec.h"

extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

#define EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID \
  { \
    0x9042a9de, 0x23dc, 0x4a38, {0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a } \
  }
  
static EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;

int
main (
  IN int Argc,
  IN char *Argv[]
  )
{
    EFI_STATUS    Status;
   EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput;
    int i,j;

    UINT8* RGB32;
    UINT8* frame;

    if (Argc<2) {
            printf("Please input file name\n");
            return 0;
    }

    gd_GIF* gif = gd_open_gif(Argv[1]);
    if (gif == NULL) {
        printf("Open file Error\n");
        return -1;
    }

    printf("width %d height %d\n",gif->width,gif->height);
    printf("number of colors: %d\n", gif->palette->size);
    printf("number of frames: %d\n", gif->loop_count);

    Status = gBS->LocateProtocol(&GraphicsOutputProtocolGuid, NULL, (VOID **) &GraphicsOutput);
    if (EFI_ERROR(Status)) {
                GraphicsOutput = NULL;
                printf("Loading Graphics_Output_Protocol error!\n");
                return EFI_SUCCESS;}

                
    frame=(UINT8*)AllocatePool(gif->width*gif->height*3);
    RGB32 = (UINT8*)AllocatePool(gif->width*gif->height*4); 

       while (1)
       {
        int retx = gd_get_frame(gif);
        if (retx == -1) 
            break;
        gd_render_frame(gif, frame);
                    for (j=0;j<gif->height;j++) {
                            for (i=0;i<gif->width;i++) {
                                RGB32[(j*gif->width+i)*4]  = frame[(j*gif->width+i)*3+2]; //Blue   
                                RGB32[(j*gif->width+i)*4+1]= frame[(j*gif->width+i)*3+1]; //Green 
                                RGB32[(j*gif->width+i)*4+2]= frame[(j*gif->width+i)*3]; //Red  
                                RGB32[(j*gif->width+i)*4+3]=0;
                            }
                    GraphicsOutput->Blt(
                                GraphicsOutput, 
                                (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *) RGB32,
                                EfiBltBufferToVideo,
                                0, 0, 
                                0, 0, 
                                gif->width, gif->height, 0);    
                              }                          

        if (retx == 0) 
                break;
            //gd_rewind(gif);
   }

   free(RGB32);  
    free(frame);

    gd_close_gif(gif);    

    return 0;
}

 

工作截图:

完整代码下载:
giftest

简单说一下工作流程,首先gd_open_gif() 函数打开 GIF,这步可以获得尺寸信息,但是从我实验来看,无法取得帧数信息(loop_count),但是readme 中说是可以的;接下来使用gd_get_frame() 函数检查当前GIF播放的位置,返回1表示还有下一帧,0表示达到尾部,-1表示出现错误;然后,gd_render_frame()函数取出来解码后的图像信息,经过格式变换即可通过 BLT显示出来;最后,检查是否播放完毕,完毕之后退出。
代码写的比较简单,效率不高,在处理较大 GIF 的时候,还是能够感受到刷新的。有兴趣的朋友可以琢磨一下如何提速,我猜测瓶颈应该是在处理解码后的数据RGB顺序的那里。

=======================================================================================
2018年11月23日 感谢 Cai 3023772977@qq.com 在评论中之处问题,代码中 GraphicsOutput() 函数,写在循环内了,这是导致播放缓慢的原因,将它移动到循环外面就可以正常工作了。

for (j=0;jheight;j++) {
for (i=0;iwidth;i++) {
RGB32[(j*gif->width+i)*4] = frame[(j*gif->width+i)*3+2]; //Blue
RGB32[(j*gif->width+i)*4+1]= frame[(j*gif->width+i)*3+1]; //Green
RGB32[(j*gif->width+i)*4+2]= frame[(j*gif->width+i)*3]; //Red
RGB32[(j*gif->width+i)*4+3]=0;
}

GraphicsOutput->Blt(
GraphicsOutput,
(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *) RGB32,
EfiBltBufferToVideo,
0, 0,
0, 0,
gif->width, gif->height, 0);

}

Teensy 3.2 GPIO 速度测试

做了一个简单的实验,测试 Teensy 3.2 板子IO 的速度,具体代码如下,就是使用digitalWrite进行 GPIO反转,然后示波器查看结果。

void setup() {
  // put your setup code here, to run once:
  pinMode(14,OUTPUT);
  digitalWrite(14,HIGH);
}

void loop() {
  // put your main code here, to run repeatedly:
  digitalWrite(14,LOW);
  digitalWrite(14,HIGH);
  digitalWrite(14,LOW);
  digitalWrite(14,HIGH);
  digitalWrite(14,LOW);
  digitalWrite(14,HIGH);
  digitalWrite(14,LOW);
  digitalWrite(14,HIGH);
  digitalWrite(14,LOW);
  digitalWrite(14,HIGH);
}

 

速度首先和主频有关系,可以在下面的位置找到,我这边测试72Mhz和96Mhz的情况。

另外,还和编译选项有关系。

1.72Mhz+Faster 测试结果是 1.21Mhz (光标测量,下同)

2.72Mhz + Faster with LTO 测试结果是 2.34Mhz (光标测量,下同)

3.96Mhz+Faster 测试结果是 1.61Mhz


4. 96Mhz+Faster with LTO 测试结果是 3.07Mhz

Step to UEFI (159)UEFI 下的排序

大多数情况下,2层循环的冒泡排序对于一般用户已经足够,但是如果对于速度有要求,那就需要考虑效率更高的排序算法了。最近偶然看到 MdeModulePkg\Include\Library\SortLib.h 提供的排序功能,研究之后编写如下的例子。
这个库的核心是PerformQuickSort() 函数,调用者给出需要排序的 buffer,然后给出其中每一个元素的大小和数量,然后这个函数还会调用作为参数指定的CompareFunction函数。通过这个函数,不但可以比较数字或者字符串,还可以比较自动定义的规则,比方说设定书记>厂长>科长之类的……

示例代码如下:

/**
  Function to perform a Quick Sort on a buffer of comparable elements.

  Each element must be equally sized.

  If BufferToSort is NULL, then ASSERT.
  If CompareFunction is NULL, then ASSERT.

  If Count is < 2 , then perform no action.
  If Size is < 1 , then perform no action.

  @param[in, out] BufferToSort   On call, a Buffer of (possibly sorted) elements;
                                 on return, a buffer of sorted elements.
  @param[in]  Count              The number of elements in the buffer to sort.
  @param[in]  ElementSize        The size of an element in bytes.
  @param[in]  CompareFunction    The function to call to perform the comparison
                                 of any two elements.
**/
VOID
EFIAPI
PerformQuickSort (
  IN OUT VOID                   *BufferToSort,
  IN CONST UINTN                Count,
  IN CONST UINTN                ElementSize,
  IN       SORT_COMPARE         CompareFunction
  );
代码如下:
#include <stdlib.h>
#include <stdio.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/SortLib.h>

#define TOTAL           100
#define NAMELENGTH        4

/** Expands to an integer constant expression that is the maximum value
    returned by the rand function.
**/
#define RAND_MAX  0x7fffffff
static UINT32 next = 1;

typedef struct {
            char Name[NAMELENGTH+1];
            int  Score;
} TScoreRecord;

/** Compute a pseudo-random number.
  *
  * Compute x = (7^5 * x) mod (2^31 - 1)
  * without overflowing 31 bits:
  *      (2^31 - 1) = 127773 * (7^5) + 2836
  * From "Random number generators: good ones are hard to find",
  * Park and Miller, Communications of the ACM, vol. 31, no. 10,
  * October 1988, p. 1195.
**/
int
rand()
{
  INT32 hi, lo, x;

  /* Can't be initialized with 0, so use another value. */
  if (next == 0)
    next = 123459876;
  hi = next / 127773;
  lo = next % 127773;
  x = 16807 * lo - 2836 * hi;
  if (x < 0)
    x += 0x7fffffff;
  return ((next = x) % ((UINT32)RAND_MAX + 1));
}

/**
  Test comparator.

  @param[in] b1   The first INTN
  @param[in] b2   The other INTN

  @retval 0       They are the same.
  @retval -1      b1 is less than b2
  @retval 1       b1 is greater then b2
**/
INTN
EFIAPI
Compare (CONST TScoreRecord *b1, CONST TScoreRecord *b2)
{
  if ((b1->Score)<(b2->Score)) {
          return -1;
  }
  if ((b1->Score)>(b2->Score)) {
          return 1;
  }
  return (0);
}

int main()
{       
        TScoreRecord   *SR;
        int             Index,NameIndex;
        
        SR = AllocatePool (sizeof(TScoreRecord)*TOTAL);
        
        for (Index=0;Index<TOTAL;Index++) {
            //Generate a name
            for (NameIndex=0;NameIndex<NAMELENGTH;NameIndex++) {
                        SR[Index].Name[NameIndex]=(rand()%26)+'A';
                }
            SR[Index].Name[NAMELENGTH]=0;
            //Generate a score
            SR[Index].Score=rand()%150;
            //Output
            printf("[%d] %s %d\n",Index,SR[Index].Name,SR[Index].Score);
        }

        //Sort
        PerformQuickSort (SR,TOTAL,sizeof(TScoreRecord),Compare);

        //Show the sort result
        for (Index=0;Index<TOTAL;Index++) {
            printf("[%d] %s %d\n",Index,SR[Index].Name,SR[Index].Score);
        }        
        
        FreePool(SR);
        
        return 0;
}

 

运行结果:

完整代码和EFI 下载:

SortTest

此外,还有一个有意思的问题:如果Compare (CONST TScoreRecord *b1, CONST TScoreRecord *b2) 这里定义为Compare (TScoreRecord *b1, TScoreRecord *b2),那么在编译时,会在PerformQuickSort (SR,TOTAL,sizeof(TScoreRecord),Compare); 报告C4090错误。因为编译器报告错误位置和实际位置着实不同,为了找到原因着实让我花费了不少时间。