C++中的Thunk技术 / 非静态类成员函数作为回调函数 的实现方法

C++中的Thunk技术和它的使用场景 阅读详情

申明:本文非笔者原创,原文转载自:http://www.cnblogs.com/memset/p/thunk_in_cpp.html


用我的理解通俗地解释一下什么是C++中的Thunk技术吧!
  Thunk技术就是申请一段可执行的内存, 并通过手动构造CPU指令的形式来生成一个小巧的, 具有明确作用的代码块.

  小巧? 具有明确作用? 你曾经初学C++时, 如果我没猜错的话, 肯定尝试过用C++封装一个窗口类(因为我也尝试过 :-) ),
在封装窗口类的时候,在类内部定义一个私有(或公有)的成员函数来作为窗口回调函数, 并以
CreateWindowEx(...,&MyWindowClass::WindowProc,...)的形式构造一个窗口, 可哪知, 这完全是行不通的, 因为(非静态)类
成员函数的指针可不是简单的全局成员函数指针那样!

  于是, 你不得不把窗口过程定义为全局函数. 但是这样的话, 每个类都共享一个窗口过程了, 这显然不行! 于是,你可能又想到了
一种算是解决办法的办法, 使用CreateWindowEx的最后一个参数LPARAM来传递this指针! 关于窗口类的封装, 这里我不再多说, 因为
我打算再写一篇文章介绍用多种方法来实现窗口类的封装, 当然, 这里将要讨论的Thunk技术算是最完美的一种了! 但是,Thunk技术也
不只是用于封装窗口类, 也可以用来封装线程类, etc.

  传言这种技术来自于ATL/WTL, 我不会ATL/WTL, Thunk技术是我在网上学来的.
  MFC不是使用我接下来要介绍的通用(非完全)Thunk方式, 关于MFC的封装方式, 我将在另一篇文章里面提及.
  这里有一篇介绍通过Thunk技术的文档:Generic Thunk with 5 combinations of Calling Conventions

  好吧, 言归正传, 谈谈Thunk的原理与实现...

  要理解Thunk的实现, 需要清楚C/C++中的函数调用约定, 如果有不懂的, 可以参考:C/C++/动态链接库DLL中函数的调用约定与名称修饰

  C++的成员函数(不讨论继承)在调用时和普通的函数并没有太大的区别, 唯一很重要的是, 需要在调用每个非静态成员函数时悄悄地
传入this指针. 在类内部调用时的直接调用, 或在类外部调用时通过obj->MemberFunction的形式调用时, 编译器都在生成代码的时候
帮我们传入了this指针, 所以我们能正确访问类内部的数据. 

  但是, 像Windows的窗口回调函数WindowProc, 线程的回调函数ThreadProc, SQLite3的回调函数sqlite3_callback在被传给主调函数时,
它们是不能被直接使用的, 因为主调函数不属于类的成员函数, 他们也没有this指针!

  看看下面的代码:

    A a1,a2;
    a1.foo(1,2,3);
    a2.foo(4,5,6);

 


  
    这是我们的书写方式, 编译器在编译时将生成如下调用(只考虑__cdecl和__stdcall,没有哪一个全局函数需要__thiscall的回调):

    foo(&a1,1,2,3);
    foo(&a2,4,5,6);

 


    我在C/C++/动态链接库DLL中函数的调用约定与名称修饰中已经讨论过这个东西了...

  好了, 现在我们知道foo函数的原型可以是如下的形式 int __cdecl foo(int a,int b,intc);
  假如我们有一个全局的函数, 她的原型是这样的:

int func( int (__cdecl*)(int,int,int) );

   你会怎样把A类里面的foo作为回调, 传递给func?  func(&A::foo); ? 这是不可行的, 我们需要借助Thunk!


  1.下面将拿Windows中的WindowProc窗口回调函数来作具体讲解__stdcall的回调函数Thunk应用.

  Windows的窗口管理在调用我们提供的全局窗口过程时, 此时的堆栈形式如下:
    低                                               高
  -----------------------------------------------------------
   返回地址     hWnd      uMsg       wParam      lParam


  如果我们将WindowProc定义为类成员的形式, 并在类内调用她, 则参数栈应该是如下形式(__cdecl,__stdcall):
    低                                               高
  --------------------------------------------------------------
   返回地址     this   hWnd      uMsg       wParam      lParam

  
  好了, 现在我们就可以动动手脚, 修改一下堆栈, 传入this指针, 然后就可以交给我们的成员WindowProc函数来处理啦~

  我们申请一段可执行的内存, 并把他作为回调函数传递给DialogBoxParam/CreateDialogParam,(这里只讨论对话框)
  申请可执行内存, 使用 VirtualAlloc
  
  因为是WindowProc是__stdcall调用约定, 就算我们多压入了一个this参数, 也不管调用者的事, 因为堆栈是由被调用者(windowProc)
来清理的. 虽然只有4个显式参数, 但作为成员函数的WindowProc在结束的时候是用ret 14h返回的, this被自动清除, 你知道为什么吗? 
  我们只需构造如下的3条简单的指令即可: 

    machine code                    assembly code                       comment
    ------------------------------------------------------------------------------------------
    FF 34 24                        push    dword ptr[esp]              ;再次压入返回地址
    C7 44 24 04 ?? ?? ?? ??         mov     dword ptr[esp+4],this       ;修改前面那个返回地址为this指针
    E9 ?? ?? ?? ??                  jmp     (relative target)           ;转到成员函数

 

  你没有看错, 真的就只需要这么几条简单的指令~~~~ :-)


  2.下面再看一个__cdecl的回调函数的Thunk技术的实现
    __cdecl形式的回调函数的特点:
      1.参数个数比函数声明要多一个this
      2.参数栈由调用者清理

    我们需要以同样的方式压入this指针, 但是__cdecl约定是由调用者来清理参数栈, 我们多传了一个this指针进去, 如果直接返回,
  势必会导致堆栈指针ESP错误, 所以, this指针必须由我们的程序来清除, 返回时保持被调用前一样就行了.

    作为一个完整的函数, 我们不可能在函数的最后插入一条"add esp,4"来解决问题, 这办不到.
    __cdecl的Thunk的实现, 我在网上也没找到答案, 由于我汇编也不咋样, 所以搞了较长一段时间才把她搞出来~ 也算一劳永逸了.

    我的处理办法(较__stdcall复杂, 但也只有几条指令而已):
      1.弹出并保存原来的返回地址
      2.压入this指针
      3.压入我的返回地址
      4.转到成员函数执行
      5.清理this参数栈
      6.跳转到原返回地址

    汇编机器指令的实现(我并不擅长汇编, 你应该觉得还可以再优化一下):

    3E 8F 05 ?? ?? ?? ??            pop     dword ptr ds:[?? ?? ?? ??]  ;弹出并保存返回地址(我的变量)
    68 ?? ?? ?? ??                  push    this                        ;压入this指针
    68 ?? ?? ?? ??                  push    my_ret                      ;压入我的返回地址
    9E ?? ?? ?? ??                  jmp     (relative target)           ;跳转到成员函数
    83 C4 04                        add     esp,4                       ;清除this栈
    3E FF 25 ?? ?? ?? ??            jmp     dword ptr ds:[?? ?? ?? ??]  ;转到原返回地址

 

 






  下面贴出我写的完整代码:

复制代码
//Thunk.h
//ts=sts=sw=4
//女孩不哭 2013-09-11 22:00
//保留所有权利
#ifndef __THUNK_H__
#define __THUNK_H__

class AThunk
{
public:
    AThunk();
    ~AThunk();

public:
    template<typename T>
    void* Stdcall(void* pThis,T mfn)
    {
        return fnStdcall(pThis,getmfn(mfn));
    }

    template<typename T>
    void* Cdeclcall(void* pThis,T mfn)
    {
        return fnCdeclcall(pThis,getmfn(mfn));
    }

private:
    typedef unsigned char   byte1;
    typedef unsigned short  byte2;
    typedef unsigned int    byte4;

    void* fnStdcall(void* pThis,void* mfn);
    void* fnCdeclcall(void* pThis,void* mfn);

    template<typename T>
    void* getmfn(T t)
    {
        union{
            T t;
            void* p;
        }u;
        u.t = t;
        return u.p;
    }

private:
#pragma pack(push,1)
    struct MCODE_STDCALL{
        byte1 push[3];
        byte4 mov;
        byte4 pthis;
        byte1 jmp;
        byte4 addr;
    };

    struct MCODE_CDECL{
        byte1 pop_ret[7];
        byte1 push_this[5];
        byte1 push_my_ret[5];
        byte1 jmp_mfn[5];
        byte1 add_esp[3];
        byte1 jmp_ret[7];
        byte4 ret_addr;
    };
#pragma pack(pop)

private:
    MCODE_CDECL     m_cdecl;
    MCODE_STDCALL   m_stdcall;
    AThunk*         m_pthis;
};

#endif//!__THUNK_H__
复制代码

 

复制代码
//Thunk.cpp
//ts=sts=sw=4
//女孩不哭 2013-09-11 22:00
//保留所有权利
#include <Windows.h>
#include "Thunk.h"

AThunk::AThunk()
{
    m_pthis = (AThunk*)VirtualAlloc(NULL,sizeof(*this),MEM_COMMIT,PAGE_EXECUTE_READWRITE);
}

AThunk::~AThunk()
{
    if(m_pthis){
        VirtualFree(m_pthis,0,MEM_RELEASE);
    }
}

void* AThunk::fnStdcall(void* pThis,void* mfn)
{
    /****************************************************************************************
    machine code                    assembly code                       comment
    ------------------------------------------------------------------------------------------
    FF 34 24                        push    dword ptr[esp]              ;再次压入返回地址
    C7 44 24 04 ?? ?? ?? ??         mov     dword ptr[esp+4],this       ;传入this指针
    E9 ?? ?? ?? ??                  jmp     (relative target)           ;转到成员函数
    ****************************************************************************************/

    m_pthis->m_stdcall.push[0] = 0xFF;
    m_pthis->m_stdcall.push[1] = 0x34;
    m_pthis->m_stdcall.push[2] = 0x24;

    m_pthis->m_stdcall.mov = 0x042444C7;
    m_pthis->m_stdcall.pthis = (byte4)pThis;

    m_pthis->m_stdcall.jmp = 0xE9;
    m_pthis->m_stdcall.addr = (byte4)mfn-((byte4)&m_pthis->m_stdcall.jmp+5);

    FlushInstructionCache(GetCurrentProcess(),&m_pthis->m_stdcall,sizeof(m_pthis->m_stdcall));

    return &m_pthis->m_stdcall;
}

void* AThunk::fnCdeclcall(void* pThis,void* mfn)
{
    /****************************************************************************************
    machine code                    assembly code                       comment
    ------------------------------------------------------------------------------------------
    3E 8F 05 ?? ?? ?? ??            pop     dword ptr ds:[?? ?? ?? ??]  ;弹出并保存返回地址
    68 ?? ?? ?? ??                  push    this                        ;压入this指针
    68 ?? ?? ?? ??                  push    my_ret                      ;压入我的返回地址
    9E ?? ?? ?? ??                  jmp     (relative target)           ;跳转到成员函数
    83 C4 04                        add     esp,4                       ;清除this栈
    3E FF 25 ?? ?? ?? ??            jmp     dword ptr ds:[?? ?? ?? ??]  ;转到原返回地址
    ****************************************************************************************/
    m_pthis->m_cdecl.pop_ret[0] = 0x3E;
    m_pthis->m_cdecl.pop_ret[1] = 0x8F;
    m_pthis->m_cdecl.pop_ret[2] = 0x05;
    *(byte4*)&m_pthis->m_cdecl.pop_ret[3] = (byte4)&m_pthis->m_cdecl.ret_addr;

    
    m_pthis->m_cdecl.push_this[0] = 0x68;
    *(byte4*)&m_pthis->m_cdecl.push_this[1] = (byte4)pThis;
    
    m_pthis->m_cdecl.push_my_ret[0] = 0x68;     
    *(byte4*)&m_pthis->m_cdecl.push_my_ret[1] = (byte4)&m_pthis->m_cdecl.add_esp[0];
    
    m_pthis->m_cdecl.jmp_mfn[0] = 0xE9;
    *(byte4*)&m_pthis->m_cdecl.jmp_mfn[1] = (byte4)mfn-((byte4)&m_pthis->m_cdecl.jmp_mfn+5);
    
    m_pthis->m_cdecl.add_esp[0] = 0x83;
    m_pthis->m_cdecl.add_esp[1] = 0xC4;
    m_pthis->m_cdecl.add_esp[2] = 0x04;

    m_pthis->m_cdecl.jmp_ret[0] = 0x3E;
    m_pthis->m_cdecl.jmp_ret[1] = 0xFF;
    m_pthis->m_cdecl.jmp_ret[2] = 0x25;
    *(byte4*)&m_pthis->m_cdecl.jmp_ret[3] = (byte4)&m_pthis->m_cdecl.ret_addr;
    
    FlushInstructionCache(GetCurrentProcess(),&m_pthis->m_cdecl,sizeof(m_pthis->m_cdecl));
    
    return &m_pthis->m_cdecl;   
}
复制代码

 


  下面再贴出一篇使用示例程序, 我已经列出了我见过的常见的回调函数的使用形式:

复制代码
//main.cpp
#include <iostream>
#include <Windows.h>
#include <process.h>
#include "Thunk.h"
#include "resource.h"
using namespace std;

/////////////////////////////////////////////////////////
//第一个:__cdecl 回调类型
/////////////////////////////////////////////////////////

typedef int (__cdecl* CB)(int n);

void output(CB cb)
{
    for(int i=0; i<3; i++){
        cb(i);
    }
}

class ACDCEL
{
public:
    ACDCEL()
    {
        void* pthunk = m_Thunk.Cdeclcall(this,&ACDCEL::callback);
        ::output(CB(pthunk));
    }

private:
    int __cdecl callback(int n)
    {
        cout<<"n:"<<n<<endl;
        return n;
    }

private:
    AThunk m_Thunk;
};

/////////////////////////////////////////////////////////
//第二个:__stdcall 回调类型:封装窗口类
/////////////////////////////////////////////////////////
class ASTDCALL
{
public:
    ASTDCALL()
    {
        void* pthunk = m_Thunk.Stdcall(this,&ASTDCALL::DialogProc);
        DialogBoxParam(GetModuleHandle(NULL),MAKEINTRESOURCE(IDD_DIALOG1),NULL,(DLGPROC)pthunk,0);
    }
    
private:
    INT_PTR CALLBACK DialogProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
    {
        switch(uMsg)
        {
        case WM_CLOSE:
            EndDialog(hWnd,0);
            return 0;
        }
        return 0;
    }
private:
    AThunk m_Thunk;
};

/////////////////////////////////////////////////////////
//第三个:__stdcall 回调类型:内部线程
/////////////////////////////////////////////////////////
class AThread
{
public:
    AThread()
    {
        void* pthunk = m_Thunk.Stdcall(this,&AThread::ThreadProc);
        HANDLE handle = (HANDLE)_beginthreadex(NULL,0,(unsigned int (__stdcall*)(void*))pthunk,(void*)5,0,NULL);
        WaitForSingleObject(handle,INFINITE);
        CloseHandle(handle);
    }
    
private:
    unsigned int __stdcall ThreadProc(void* pv)
    {
        int i = (int)pv;
        while(i--){
            cout<<"i="<<i<<endl;
        }
        return 0;
    }
private:
    AThunk m_Thunk;
};

int main(void)
{
    ASTDCALL as;
    ACDCEL ac;
    cout<<endl;
    AThread at;
    return 0;
}
复制代码

 



C++ Thunk技术(初学版) 今天看到一篇关于Thunk技术的文章,特转过来。这里想说的是:代码中的关键点为用指令jmp pFunc跳转到你想要执行的函数pFunc。指令“jmp xxxx”占5个字节,代码中用了个一字节对齐的结构体struct Thunk ,当然也可以用 unsigned char code[5]; 说另一个关键点就是地址计算了,jmp xxxx指令用了相对跳转地址,相对地址 = 要跳转函数的 阅读详情

相关推荐

QT中使用THUNK技术实现回调问题

<br />   C++中使用回调,考虑了多种都不行,感觉不能达到自己的要求。搜索资料看到THUNK技术,貌似在基于对象实现回调这一块用的很广泛。看了看大致原理,的确不错。把成员函数的调用原理加以分析,相对应的寄存器写入this指针的值。THUNK形成汇编代码块,调用底层指令,对相应的寄存器操作。这些操作也就是基本模仿成员函数调用时的操作,实现成员函数的调用。大致这样吧,按照网上的方法,其实是能够达到效果的。但是总会有问题啊,而且很郁闷。<br />    m_thunk.Init(pointer_

fngyq的专栏 1117

C++ Thunk技术

一. 使用回调函数时遇到的问题 在使用Win32 API(如CreateThread等)时我们经常需要传入回调函数,这个回调函数不能是类的成员函数,只能是友元函数或静态函数。为了在回调函数中访问某个类的成员变量或函数,我们通常不得不通过某种方式将该类的This指针传入到回调函数中,从而实现在回调函数中访问类的公有的成员变量或函数。 如果有一种方式可以实现将类的成员函数作为回调函数来使用,那么就...

while(1) { smile(); } 5万+

基于Thunk技术的Windows Timer的封装

由于最近项目是要开发一个BHO浏览器插件,我们需要使用定时器来轮询页面的变化。实际上,就是让定时器能够访问成员变量,或者说使定时器成为成员函数。但是,定时器调用的是一个回调函数(CALLBACK),回调函数是一个系统调用的函数,它被封装在类里面只能以static的方式定义。这种定义方式和我们的项目要求不符合,因为static函数只能访问static变量。所以我用Thunk技术对定时器回调进行了封装。

Jedihy的博客 1191

类成员函数当回调函数的方法

普通的C++成员函数都隐含了一个“this”指针参数,当在类的非静态成员函数中访问类的非静态成员时,C++编译器通过传递一个指向对象本身的指针给其成员函数,从而能够访问类的数据成员。也就是说,即使你没有写上this指针,编译器在编译的时候自动加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。在创建线程调用回调函数时,传入类对象的指针(比如this指针)作为参数,并在回调函数中把void*强制转换为类的指针(MyClass*),就能使用该指针调用类的成员函数。

jinking01的专栏 1585

Thunk技术

NULL 博文链接:https://mengdejun.iteye.com/blog/932762

【IC设计实战】去耦电容decap在电源完整性中的关键作用与选型策略

本文深入探讨了去耦电容(decap)在IC设计电源完整性中的核心作用。文章分析了decap如何通过提供本地电荷缓冲来抑制电源噪声与电压波动,并详细阐述了其选型与布局策略,包括MIM、MOM、MOS电容的特性对比,以及如何权衡峰值抑制与建立速度的矛盾,为设计稳定可靠的芯片电源系统提供实践指导。

weixin_29233857的博客 247

利用Thunk让C++成员函数变回调函数

申明:本文非笔者原创,原文转载自:https://www.cnblogs.com/fangkm/archive/2009/05/25/1488727.html Windows API经常需要回调函数,而在C++开发中面向对象当行其道,若能让C++类的成员函数成为回调函数,简直就是大善!但是C++成员函数都隐含了一个this指针用于指向当前的对象.要实现回调确实不容易. 我大约一年前

计算机视觉小菜鸟的专栏 1903

【阿里云大模型高级工程师ACP习题集】2.7 通过微调增强模型能力 (上篇)(⭐️⭐️⭐️ 重点章节!!!)

【摘要】这是我精心整理出对应的习题集,里面足足有 30 道题目,从基础概念到深度拓展,全方位覆盖章节重点、难点。不管你是想检验自己的学习成果,还是想针对薄弱环节进行强化训练,它都能完美适配!

davidkorhenn的博客 1603

c++ thunk

<br />之前参加了一次面试,问到了SetTimer中回调设计成类成员函数的问题.后来想写一下,但是一直没有找到相关文章.后来lori哥指点,原来这就是C++ Thunk技术.<br /> <br />Thunk : 将一段机器码对应的字节保存在一个连续内存结构里, <br />然后将其指针强制转换成函数. 即用作函数来执行.<br /><br />这份代码用于将C语方的回调函数转换为具有this指针的C++成<br />员函数.<br /><br />在Windows系统编程中, 可以将一些系统回函数封

Johny的专栏 7345

AS5600磁编码器选型与安装避坑指南:磁铁形状、间隙距离到底怎么选?(附实测数据)

本文详细解析了AS5600磁编码器的选型与安装要点,包括磁铁形状、间隙距离等关键参数的选择,并附有实测数据支持。特别针对Arduino和I2C接口的应用场景,提供了硬件选型和机械安装的实用指南,帮助开发者避免常见陷阱,提升系统精度和稳定性。

weixin_27038245的博客 287

51雾化器实现

1. 雾化器简介 雾化器是将试液雾化的一种器械。雾化器是原子化系统的重要部件,其性能对测定的精密度和化学干扰等产生显著影响。因此要求雾化器喷雾稳定、雾滴细小、均匀和雾化效率高。雾化器的类型有超声雾化器,空气压缩式雾化器,网式雾化器。超声雾化器因其价格合适,适合家用,被广泛应用于加湿器,香薰机等各种小家电,也在医疗领域有很多的应用。家用雾化器的主要器件是一枚雾化片。 家用雾化器分为两种不同类别雾化片,实孔雾化片和微孔雾化片,这两种的喷雾方法和工作原理是有所不同的。不同的雾化片需要特定的频率才能工作。 实孔雾

qq_42415953的博客 2万+

ATL之thunk技术

<br /><br /><br />学习下 ATL 的 thunk<br />由于 C++ 成员函数的调用机制问题,对C语言回调函数的 C++ 封装是件比较棘手的事。为了保持C++对象的独立性,理想情况是将回调函数设置到成员函数,而一般的回调函数格式通常是普通的C函数,尤其是 Windows API 中的。好在有些回调函数中留出了一个额外参数,这样便可以由这个通道将 this 指针传入。比如线程函数的定义为:<br />typedef DWORD (WINAPI *PTHREAD_START_ROUTINE

xsh_123321的专栏 4790

点云PCL库学习-双目图像转化为点云PCD并显示

文章目录 双目视觉模型 代码实现 总结 参考文章 一、双目视觉模型 和RGBD相机主动发射光线实现测距不同,双目测距原理通过配置立体摄像头(通常由左眼相机和右眼相机两个水平放置的相机组成)来获取一对同一场景中的二维图像,再基于同一空间点在两个坐标系下的视差来计算深度。双目测距的示意图和数学表达式如下所示: 双目相机的成像示意图 上述示意图描述了一个空间点在左右两目相机中位置之间的成像关系,根据相似三角形原理可得: 其中,f和b分别为相机焦距和双目相机基线,d为空间点在两个坐标系下

weixin_41957687的博客 6948

c++ THUNK技术

这里想说的是:代码中的关键点为用指令jmp pFunc跳转到你想要执行的函数pFunc。 指令“jmp xxxx”占5个字节,代码中用了个一字节对齐的结构体struct Thunk , 当然也可以用 unsigned char code[5]; 说另一个关键点就是地址计算了,jmp xxxx指令用了相对跳转地址, 相对地址 = 要跳转函数的地址 - “jmp xxxx”指令的下一

ghevinn欢迎您光临 2450

EGO_Planner代码学习(三):px4ctrl代码流程

上节阅读了EGO_Planner的轨迹服务器部分的代码,其实质是将规划器生成的轨迹进行离散化,间隔10ms计算当前时刻在轨迹上的位置,并发送到话题上。这两天抽空看了下控制器部分的代码,在l这个软件包里,它的功能是进行px4的模式识别和上层控制,通过线性控制器把发布过来的位置命令解算成飞控的控制量,通过mavros发送给px4。下面就详细看一下它的启动过程。

qq_41069236的博客 6805

STM32 ST-LINK Utility固件烧录全流程详解与实战技巧

本文详细解析了使用STM32 ST-LINK Utility进行固件烧录的全流程与实战技巧。从硬件连接、软件安装、核心三步操作(连接、擦除、烧录)到进阶的选项字节配置与批量脚本烧录,提供了完整的避坑指南和问题排查方案,帮助开发者高效完成STM32程序下载。

lambda的博客 768

Windows下利用thunk实现回调成员函数(1)

今天由于工作需要,研究了下thunk技术,下面的内容算是个小小的总结吧。我们知道,在Windows和x86下,函数调用主要有以下几种方式:__stdcall这是Windows下默认的函数调用方式。调用者负责把函数参数从右到左压入堆栈,函数在返回前负责清理堆栈。用汇编语言表示就是:调用方:mov esi, esp;保存ESPpush ....

glyc的专栏 1475

uniapp - Invalid value “iife” for option “output.format” - UMD and IIFE output formats are not suppo

Invalid value “iife” for option “output.format” - UMD and IIFE output formats are not supported for code-splitting builds. uniapp 编译报错,vite vue3 Hbuilder 中编译报错,uniapp 打包 App 报错(vue3)hbuilder提示错误,uniapp运行到手机/模拟器 App 报错(vue3)uniapp项目启动报错,如何解决这个问题,安卓app,苹果app

🏆 前端领域优质创作者 3964

我对C++中THUNK一种实现技术的分析

我对C++中THUNK一种实现技术的分析KEY WORDS:C++ THUNK 内存模式 在互联网上看到这样一段代码,有些网友不知其然,我简单的把它分析一下。 #pragma pack(push,1)// structure to store the machine codestruct Thunk{    char    m_jmp;          /

1208

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

如何让C++类的成员函数作为回调函数

转自:http://my.oschina.net/opening/blog/55289 为什么类(class)的成员函数(member function)不能作为回调函数(callback function)? 首先来看看回调函数有怎样的特点。windows中,回调函都显式(explicit)使用CALLBACK修饰符(decorator)修饰 (decorated)。实际上CAL...

Tel18545051023的博客 155

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

上一篇: 利用Thunk让C++成员函数变回调函数
下一篇: 回调函数中如何调用类中的非静态成员变量或非静态成员函数
ChenLee_1
博客等级 码龄17年 6199粉丝 179原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值