开发环境:win2k + vc6

解决方案 »

  1.   


    LoadLibrary(...)
    GetProcAddress(..)
    FreeLibrary(...)
      

  2.   

    LoadLibrary(...)不能获得该函数的句柄
      

  3.   

    如何在32位的程序中调用16位的动态连接库    在有些情况下我们要在32bit的程序中调用16bit DLLs,象作者的“多内码汉字支支
    撑平台”中为了兼顾16bit的程序,就需使用16bit DLLs。但是问题出来了,32bit并不
    能直接调用16bit DLLs,怎么办呢?事实上我们的“瘟 95/98”中也是有很多地方是在
    32bit程序中调用16bit的例程,因而说明我们一样可以实现。如何处理呢?下面就如何
    在32bit的程序中调用16bit DLLs的方法和存在的问题进行讲解。
        首先我们要用未公布的函数LoadLibrary16, GetProcAddress16, FreeLibrary16进
    行加载、取得函数的地址和解载16bit DLLs,有了这些函数的帮助我们就可以载入一个
    16bit DLLs,并取得我们所需函数的地址。但是我们要使用这些函数还应解决存在几个
    问题:
        1.KERNEL32的函数导出表中并没有声明这些函数的名称和序号,这样我们只能分析
          PE (portable executable)文件格式的结构,并从中的函数导出表中查找我们所
          需的函数,具体可以参见GetProcAddress32。
        2.16bit程序使用的是段地址:偏移地址格式,而我们现在却是使用线性地址,因而
          我们需要“模拟”成一个16bit的系统。
        现在我们可以载入16bit DLLs并取得所需函数得地址,而我们要调用它还须建立一
    个 16bit的堆栈并做一些工作。这些工作我们可以用一个未公开的KERNEL32函数,其名
    称是“QT_Thunk”来处理。这时我们需要插入一些汇编程序,将 16bit函数的参数依次
    推入堆栈中并call QT_Thunk。但是调用该函数要注意以下几点:
        1.保留最少60个字节的堆栈,可以申明一个最少60的字符的局部变量,但这是不一
          定能够达到我们的要求,因为在 Delphi2中程序将被优化,这些未被使用的变量
          将没有编译到程序中并且所需求的堆栈并没有被保留。要解决这个问题,我们可
          以尝试并写一个值到变量中,这样优化程序将认为你已经使用了这个变量而分配
          给指定的空间。
        2.参数将使用汇编推入到堆栈中,一般情况下使用Pascal编译的函数,是从左往右
          依次推入的,而使用C/C++编译的则是从右往左推如堆栈的,而我们Windows提供
          的函数一般是须用Pascal格式调用。
        3.如果该16bit函数是用C格式调用的话,我们在调用完毕时要进行出栈处理。
        4.返回格式一般是这样的,字符型、整数型返回的值在AX中,长整数、各种指针等
          是DX:AX。
        5.如果调用 16bit函数得参数中有指针的还须对其32bit指针映射成16bit的指针,
          这就需要使用未公开的KERNEL32函数SMapLS_IP_EBP_8和SUnMapLS_IP_EBP_8,来
          进行映射和解除映射,调用 SMapLS_IP_EBP_8须先将要映射的地址入栈,再进行
          调用,不过因为之后要进行解除映射,所以并没有出栈处理,这就需要我们在解
          除映射关系后紧接着进行出栈。
        至此我们已经可以在32bit程序中调用16bit的函数了,不过由于系统限制,只能在
    Windows95/98中运行。对次有兴起的读者欢迎和我联系email: [email protected]。附:
    /**************************************************************************
    程序名称: DLL16.CPP
    程序用途: 在32bit程序中调用16bit的函数
    编译环境: Borland C++ 5.02
    运行环境: Windows 95/98
    编译时间: 1999/ 6/ 7
    作    者: 游勇鹏
    **************************************************************************/
    #include <windows.h>
    #include <stdio.h>extern "C" VOID WINAPI QT_Thunk(VOID);#define ERROR_USER_DEFINED_BASE  0xf000
    #define ERR_CANNOT_FOUND         ERROR_USER_DEFINED_BASE+0x0527 //无法找到指定的模块
    #define ERR_NOT_DOS_FILE         ERROR_USER_DEFINED_BASE+0x0528 //非DOS文件
    #define ERR_NOT_PE_HEADER        ERROR_USER_DEFINED_BASE+0x0529 //非PE文件#define isin(address,start,length) ((char*)(address)>=(char*)(start) && (char*)(address)<(char*)(start)+(length))static HINSTANCE (FAR WINAPI *LoadLibrary16)(LPCSTR lpszLibFileName); //加载16位DLL的函数
    static VOID (FAR WINAPI *FreeLibrary16)(HINSTANCE hinst);             //释放16位DLL的函数
    static FARPROC (FAR WINAPI *GetProcAddress16)(HINSTANCE hinst,LPCSTR lpszProcName); //取16位DLL的函数地址的函数
    static VOID (FAR WINAPI *SMapLS_IP_EBP_8)(VOID);                      //将32位地址映射成相应的16地址
    static VOID (FAR WINAPI *SUnMapLS_IP_EBP_8)(VOID);                    //解除映射关系//  查找PE文件的函数引出表,从而得到所需函数的地址
    DWORD FindExportTable(const void *const section_data,
                          const DWORD section_start_virtual,
                          const size_t section_length,
                          const IMAGE_DATA_DIRECTORY * const directories)
    {
     int directory;
      for (directory = 0; directory < IMAGE_NUMBEROF_DIRECTORY_ENTRIES; directory++)
          if (directories[directory].VirtualAddress && isin(directories[directory].VirtualAddress, section_start_virtual, section_length))
            {
                const void *const stuff_start = (char *)section_data + (directories[directory].VirtualAddress - section_start_virtual);
                                                /* (virtual address of stuff               -                    virtual address of section) = offset of stuff in section */
                if(directory==IMAGE_DIRECTORY_ENTRY_EXPORT)
                 return (DWORD)stuff_start;
             }
     return NULL;
    }
    DWORD BaseOfCode;
      

  4.   


    //  从函数引出表查出所需函数的地址
    FARPROC GetExportTable(const void *const section_base, const DWORD section_base_virtual, const IMAGE_EXPORT_DIRECTORY * const exp,DWORD NumofExp)
    {
     #define adr(rva) ((const void*)((char*)section_base+((DWORD)(rva))-section_base_virtual))
     if(IsBadReadPtr(exp, sizeof(*exp)))
      return NULL;
     const DWORD *function_table =(const DWORD *) adr(exp->AddressOfFunctions);
     return (FARPROC)(function_table[NumofExp-1]+BaseOfCode);
    }
    /*
      从指定模块中导出指定函数的地址,类同于GetProcAddress函数,但不同之处在于
      GetProcAddress32函数使用的是函数编号,而GetProcAddress使用的是函数名称.
      格式:
             GetProcAddress32(模块名称,指定函数的编号);
      返还:
             NULL表示程序出错,可用GetLastError函数取的出错码.
             其他值,表示成功,其就是所指定的函数的地址.
    */
    FARPROC GetProcAddress32(LPCSTR lpszLibFileName,WORD Ordinal)
    {
     HMODULE ModuleHandle;
     DWORD ExportTableAddress;
     const IMAGE_DOS_HEADER *dos_head;
     FARPROC tmp;
     const struct{
      DWORD signature;
      IMAGE_FILE_HEADER FileHeader;
      IMAGE_OPTIONAL_HEADER OptionalHeader;
      IMAGE_SECTION_HEADER section_header[];  /* actual number in NumberOfSections */
     } *PeHeader;                           //PE文件的格式,其定义在WINNT.H文件中
     ModuleHandle=GetModuleHandle(lpszLibFileName);
     if(ModuleHandle){
      dos_head=(const IMAGE_DOS_HEADER *)ModuleHandle;
      if(dos_head->e_magic != IMAGE_DOS_SIGNATURE){
       SetLastError(ERR_NOT_DOS_FILE);     //非DOS文件
       return 0;}
      (const void *)PeHeader=(const void *)((char *)dos_head + dos_head->e_lfanew);
      if(IsBadReadPtr(PeHeader, sizeof(PIMAGE_NT_HEADERS))){  /* start of PE-Header */
       SetLastError(ERR_NOT_PE_HEADER);    //无效的PE文件头
       return 0;}
      BaseOfCode=PeHeader->OptionalHeader.ImageBase;
      ExportTableAddress=FindExportTable(ModuleHandle,0,PeHeader->OptionalHeader.SizeOfHeaders, PeHeader->OptionalHeader.DataDirectory);
      if(!ExportTableAddress){
       int sect;
       const IMAGE_SECTION_HEADER *section_header;
       for (sect = 0, section_header = PeHeader->section_header; sect < PeHeader->FileHeader.NumberOfSections; sect++, section_header++){
        ExportTableAddress=FindExportTable((char *)ModuleHandle + section_header->PointerToRawData,
                                        section_header->VirtualAddress,
                                        section_header->SizeOfRawData,
                                        PeHeader->OptionalHeader.DataDirectory);
        //取得函数导出表
        if(ExportTableAddress){
         tmp=GetExportTable((char *)ModuleHandle + section_header->PointerToRawData,
                         section_header->VirtualAddress,
                         ( const IMAGE_EXPORT_DIRECTORY * const )ExportTableAddress,
                         Ordinal);
         //取得所指定得函数地址
         break;}}}
      else{
       tmp=GetExportTable((char *)ModuleHandle,0,
                       ( const IMAGE_EXPORT_DIRECTORY * const )ExportTableAddress,
                       Ordinal);}}
     else{
      SetLastError(ERR_CANNOT_FOUND);      //无法找到指定模块
      return 0;}
     return tmp;
    }
    static DWORD globalProcPointer;
    WORD GetWindowsDirectory16(LPSTR lpszSysPath,WORD cbSysPath){
     BYTE Reserved[0x3c];    //由于QT_Thunk函数要使用至少60字节的局部变量,
                             //所以要保留至少60字节的局部变量, 而且在最好不
                             //使用其他的任何的动态变量
     asm{
       push lpszSysPath      //注意由于SUnMapLS_IP_EBP_8解除映射时还要用该值,
                             //所以SMapLS_IP_EBP_8并没有把它出栈,我们在处理完
                             //时要进行出栈才不会出错.
       call SMapLS_IP_EBP_8  //将32位地址lpszSysPath映射成16地址
       push eax              //返回eax即为映射后的16地址
       push cbSysPath        //由于WINDOWS的标准函数是Pascal调用方法, 所有参数
                             //应从左往右依次入栈
       mov edx, globalProcPointer //填充要调用的函数地址到寄存器EDX中
       call QT_Thunk         //调用 thunk 函数
       call SUnMapLS_IP_EBP_8 //解除映射
       pop  ecx} //由于前面有一条push lpszSysPath, 所以要出栈才不会出错
     return _AX;             //从寄存器AX中拷贝返回值
    }
    int main()
    {
     HINSTANCE dll16;
     WORD Result;
     BYTE AppDir[200]; (FARPROC)LoadLibrary16=GetProcAddress32("KERNEL32", 35); //取得所需函数
     (FARPROC)FreeLibrary16=GetProcAddress32("KERNEL32", 36);
     (FARPROC)GetProcAddress16=GetProcAddress32("KERNEL32", 37);
     (FARPROC)SMapLS_IP_EBP_8=GetProcAddress(GetModuleHandle("KERNEL32"),"SMapLS_IP_EBP_8");
     (FARPROC)SUnMapLS_IP_EBP_8=GetProcAddress(GetModuleHandle("KERNEL32"),"SUnMapLS_IP_EBP_8"); dll16=LoadLibrary16("KRNL386.EXE"); //载入16动态连接库
     globalProcPointer=(DWORD)GetProcAddress16(dll16,"GetWindowsDirectory"); //取的所需的函数
     GetWindowsDirectory16(AppDir,200);  //调用
     printf("Libhandle (user.exe): %lX\n",dll16);
     printf("Virtual Proc address: %lX\n",globalProcPointer);
     printf("Windows Directory: %s\n",AppDir);
     FreeLibrary16(dll16);               //释放16动态连接库
    }
      

  5.   

    使用Flat ThunkHow To Call 32-bit Code from 16-bit Code Under Windows 95, Windows 98, or Windows Millennium Editionhttp://support.microsoft.com/default.aspx?scid=kb;en-us;154093