精华内容
下载资源
问答
  • delphi多线程调用dll

    热门讨论 2011-08-28 12:00:08
    delphi多线程调用dll delphi多线程调用dll 有点复杂
  • 环境是这样的 java调用dll a,此dll调用另一个dll b控件 请问这样情况下,java是否可以多线程多用dll a呢 如题 非常感谢
  • 今天就说一说其中一个,DLL多线程全局变量互相干扰的问题。 JAVA的业务需要在调用过程中采用多线程的方式,因为C实现算法中用到了很多全局静态变量,JNI在调用的时候就不可避免的出现各个线程间的全局变量互相干扰的...

            最近公司项目用到C/C++的跨平台调用,因为调用方是JAVA,所以调用方式选择了JNI,但是在实现过程中遇到了颇多问题。今天就说一说其中一个,DLL多线程全局变量互相干扰的问题


            JAVA的业务需要在调用过程中采用多线程的方式,因为C实现算法中用到了很多全局静态变量,JNI在调用的时候就不可避免的出现各个线程间的全局变量互相干扰的问题。然后各种查找解决方案。


            最初是想在不改DLL的前提下解决,尝试的是通过java掉命令的方式,在多个进程中调用dll,问题肯定是可以解决的,但是综合考虑系统资源开销太大。PASS


            最后决定修改DLL,敲定的解决方案是使用TLS方式存储用到的全局变量。各种查询,发现了C/C++解决全局变量多线程调用互相干扰的问题很简单的方式,就是在用到的全局变量前都加上__declspec(thread)来修饰就可以了(例如:__declspec(thread) int index;)。看起来确实很简单,开始修改DLL并调用调试,但是结果却不是跟预想中的一样!!!继续谷哥、度娘,http://blog.csdn.net/pgmsoul/article/details/8580415,看到了这位仁兄的这篇文章,豁然开朗。原来__declspec(thread)这种方式在动态连接库中调用是不行的,需要自己去实现TLS存储。好吧,还是去找权威吧https://msdn.microsoft.com/en-us/library/ms686997(v=vs.85).aspx,这里写的很详细了。根据微软说明,简单封装了一下需要用到的函数,调试通过,问题解决了。


    下载调试过程中的DLL源码,请移步至 http://download.csdn.net/detail/bingge1022/9870979

    Tls.cpp源码

    #include "Tls.h"
    int threadId;
    bool DllSet(int fdwReason)
    {
        LPVOID lpvData;
        BOOL fIgnore;
        switch (fdwReason)
        {
            // The DLL is loading due to process
            // initialization or a call to LoadLibrary.
            case DLL_PROCESS_ATTACH:
                // Allocate a TLS index.
                if ((threadId = TlsAlloc()) == TLS_OUT_OF_INDEXES)
                    return FALSE;

                // No break: Initialize the index for first thread.

            // The attached process creates a new thread.
            case DLL_THREAD_ATTACH:

                // Initialize the TLS index for this thread.

                lpvData = (LPVOID) LocalAlloc(LPTR, 256);
                if (lpvData != NULL)
                    fIgnore = TlsSetValue(threadId, lpvData);

                break;

            // The thread of the attached process terminates.

            case DLL_THREAD_DETACH:

                // Release the allocated memory for this thread.

                lpvData = TlsGetValue(threadId);
                if (lpvData != NULL)
                    LocalFree((HLOCAL) lpvData);

                break;

            // DLL unload due to process termination or FreeLibrary.

            case DLL_PROCESS_DETACH:

                // Release the allocated memory for this thread.

                lpvData = TlsGetValue(threadId);
                if (lpvData != NULL)
                    LocalFree((HLOCAL) lpvData);

                // Release the TLS index.

                TlsFree(threadId);
                break;

            default:
                break;
        }
        return TRUE;
    }

    bool StoreDataInt(int iv, int intTlsVar)
    {
       LPVOID lpvData;
       int * pData;  // The stored memory pointer

       lpvData = TlsGetValue(intTlsVar);
       if (lpvData == NULL)
       {
          lpvData = (LPVOID) LocalAlloc(LPTR, 256);
          if (lpvData == NULL)
             return FALSE;
          if (!TlsSetValue(intTlsVar, lpvData))
             return FALSE;
       }

       pData = (int *) lpvData;
       // Cast to my data type.
       // In this example, it is only a pointer to a int
       // but it can be a structure pointer to contain more complicated data.

       (*pData) = iv;
       return TRUE;
    }

    bool GetDataI(int *piv, int intTlsVar)
    {
       LPVOID lpvData;
       int * pData;  // The stored memory pointer

       lpvData = TlsGetValue(intTlsVar);
       if (lpvData == NULL)
          return FALSE;
       pData = (int *) lpvData;
       (*piv) = (*pData);
       return TRUE;
    }

    int GetDataInt(int intTlsVar)
    {
        int ivOut;
        if(GetDataI(&ivOut, intTlsVar)){
            return ivOut;
        }
        return -1;
    }


    bool StoreDataChar(char cv, char charTlsVar)
    {
       LPVOID lpvData;
       int * pData;  // The stored memory pointer

       lpvData = TlsGetValue(charTlsVar);
       if (lpvData == NULL)
       {
          lpvData = (LPVOID) LocalAlloc(LPTR, 256);
          if (lpvData == NULL)
             return FALSE;
          if (!TlsSetValue(charTlsVar, lpvData))
             return FALSE;
       }

       pData = (int *) lpvData;
       // Cast to my data type.
       // In this example, it is only a pointer to a int
       // but it can be a structure pointer to contain more complicated data.

       (*pData) = cv;
       return TRUE;
    }


    bool GetDataC(char *pcv, char charTlsVar)
    {
       LPVOID lpvData;
       int * pData;  // The stored memory pointer


       lpvData = TlsGetValue(charTlsVar);
       if (lpvData == NULL)
          return FALSE;
       pData = (int *) lpvData;
       (*pcv) = (*pData);
       return TRUE;
    }


    int GetDataChar(char charTlsVar)
    {
        int cvOut;
        if(GetDataI(&cvOut, charTlsVar)){
            return cvOut;
        }
        return -1;
    }

    取值\赋值调用关键代码

    int _intTlsVar = GetDataInt(intTlsVar);


     _intTlsVar = _intTlsVar+1;


     if(!StoreDataInt(_intTlsVar, intTlsVar)){


        printf("%s","StoreData error");


     }




    展开全文
  • 多线程调用DLL(面向对象)

    千次阅读 2015-08-14 14:58:15
     关于DLL的说明,这里所使用的DLL,并非是一些功能函数的集合,每次调用一个函数实现相应功能即可。而是封装了一个“小程序对象”,其前身是OCX,我们将OCX封装为一个DLL。  因此,此DLL中会存在某个对象A,生存...

    前言:

           关于DLL的说明,这里所使用的DLL,并非是一些功能函数的集合,每次调用一个函数实现相应功能即可。而是封装了一个“小程序对象”,其前身是OCX,我们将OCX封装为一个DLL

           因此,此DLL中会存在某个对象A,生存周期伴随调用它的程序。

           可以认为,A便是我们封装的“小程序对象”。DLL所有的接口,实际上都是对应调用A的成员函数。

           这里便会存在一个问题,多线程情况下,DLL中的这个对象A,是共用的。而我们希望,每个线程所使用到的A是互不干扰的,它只属于各自的线程。      

    例如OCX调用DLL,浏览器打开N个页面,调用OCX,是多线程的情况。但DLL不会生成多个副本去对应ocx,它只有一个DLL实例在浏览器进程下运行,此时就会出现我们上面所描述的问题。

    因此,此处我们使用的对象是指针类型,每个线程,都会拥有它所对应的A

     

    实现:

           采用面向对象方式,进行类的封装,程序中定义基类BASEDLL中的类CHILD,继承自BASECHILD型指针,便是对象A的类型。

           BASE类中提供所有的函数接口。在程序中,创建一个成员变量B,类型是BASE*

           我们在DLL中提供接口,用来NEW 一个CHILD,并将指针返还给程序。返还的指针,即赋给B

           此时,我们在程序中使用DLL,即可直接对B进行操作。因为前面所说,DLL所有的接口,实际上都是对应调用A的成员函数。因此,此处直接操作B,调用B的函数即可。

           因为我们每个程序实例,都会调用DLL接口,创建各自的A。所以在多线程情况下,也不会冲突。

     

    代码示例:

    基类CMyTestBaseClass

    class CMyTestBaseClass: public CObject

    {

    public:

           CMyTestBaseClass();

           virtual ~CMyTestBaseClass();

     

    public:

           virtual void TestShow() = 0;

    };

     

     

     

    子类CMyTestClass

    class CMyTestClass: public CMyTestBaseClass

    {

    public:

           CMyTestClass();

           virtual ~CMyTestClass();

     

    public:

           void TestShow();

    };

     

    DLL提供接口

    LONG __declspec(dllexport)Kcxzt_TestCreate();

    LONG  Kcxzt_TestCreate()

    {

           CMyTestClass* p = new CMyTestClass;

           return (LONG)p;

    }

     

    程序中声明一个变量

    CMyTestBaseClass* m_testPtr;

    为变量赋值

    m_testPtr = (CMyTestBaseClass*)(Kcxzt_TestCreate());

     

    之后就可直接使用m_testPtr来操作DLL

    展开全文
  • Java 多线程调用 C++ dll

    千次阅读 2009-08-17 17:58:00
    前段时间到一个公司工作。在工作期间,需要给公司解决一个java调用dll的问题,该公司以前的java掉用dll存在一个多线程的问题。...所以当多线程调用时,就会出现数据混乱。因为全局变量在一个进程中是共享的。

       前段时间到一个公司工作。在工作期间,需要给公司解决一个java调用dll的问题,该公司以前的java掉用dll存在一个多线程的问题。

    经过一天多的思考和分析。得出解决办法。以下是我对这个问题的一些想法。

         1。 该dll中定义了许多全局变量,并且每回要调用dll时都要根据调用端的情况来初始化这些全局变量。所以当多线程调用时,就会出现数据混乱。因为全局变量在一个进程中是共享的。

         2。第二个问题是: JNIEnv指针不能直接在多线程中共享使用。

     

         第一个的问题的解决的方法:做一个线程池管理类:每个线程调用中的全局进行管理。

         第二个问题的解决方法:JNIEnv *env指针不可为多个线程共用,但是java虚拟机的JavaVM指针是整个jvm公用的,我们可以通过JavaVM来得到当前线程的JNIEnv指针.对每一个调用的线程产生一个临时的JNIEnv指针拷贝。

         

     

    展开全文
  • 多线程调用MFC DLL、在MFC DLL创建多线程与线程安全. |' u f8 D8 G @# _( O & _! Y: J5 o' U9 d5 P. [+ \多线程调用: ! z: {1 ^9 c+ N g: ? S 1、动态库只有一个导出函数: $ w6 Y7 L/ S& `" | 这种情况...
    多线程调用MFC DLL、在MFC DLL创建多线程与线程安全. |' u  f8 D8 G  @# _( O

    & _! Y: J5 o' U9 d5 P. [+ \多线程调用: ! z: {1 ^9 c+ N  g: ?  S
    1、动态库只有一个导出函数: $ w6 Y7 L/ S& `" |
    这种情况非常少,也是最容易处理的情况。这种情况下编写函数时,只需要考虑不要有冲突的全局数据就可以了。这里的全局数据包括了在堆中分配的数据块和静态全局变量等。如果存在这样的全局数据,那么进程中的不同线程访问这个函数就会造成冲突。 & r3 s  |1 P/ `- v8 G" u
    解决办法也很简单,就是尽量用堆栈(stack)来解决问题。由于堆栈的所有人是线程,所以它必然是线程安全的。当然也要注意避免堆栈溢出。
    # c' O" o6 F' R2 t# D我们都知道,如果要在函数再次调用时保留前一次调用的状态,可以使用静态变量。但如果你要保持函数的线程安全,那么静态变量是不能用的,因为静态变量是全局的,是属于进程的,也就是属于进程内线程共享的。所以如果确实需要在同一线程中保持函数的状态,相当于在不同次调用间传递参数,可以考虑使用静态全局线程局部变量,即:
    ' H3 |2 W1 w0 M3 h* H/ l2 ?0 G__declspec( thread ) int tls_i = 1; , ?( X: `# i9 ~" M- a
    该变量定义就使编译器保证了tls_i是对应于每个线程的,即每个线程都一个tls_i的副本(copy),这样必然就是线程安全的。
    - K# w8 m! }$ {  R- e+ K" N2、动态库导出了多个函数,而且多个函数间存在数据传递。
    4 i! O6 F+ @, D就像前面说的,一般DLL都导出多个函数,一个初始化,一个资源释放,其他为核心功能函数。这些函数间极有可能发生数据传递。如果一个初始化函数是在线程A中调用的,而核心功能函数是在线程B中调用的,那么线程A初始化函数的资源就无法对应线程B中的核心功能,此外还有核心功能函数间的数据传递,这样的DLL就不是线程安全的,必然导致错误。
    5 S( H6 P$ v, H  ~: ]7 n解决办法是由用户(即使用DLL的人)保证这些导出函数是在一个线程中调用。但这样会很大程度上限制接口的设计和用户的使用自由度。所以最好的方法是函数只管自己的线程安全,不同函数传递数据用动态TLS,线程局部存储。
    ! I1 L7 _( \$ s) H6 Y2 W比如:
    5 f; {# E; X3 j我在全局定义了一个变量,用于存储当前线程局部存储的index ID。8 g+ e9 h+ ~7 Q) J/ q. |
    __declspec( thread ) int tls_i = 1; * c/ e! _" \1 e+ d4 Z$ h- ]
    当调用分配资源的函数时,调用动态TLS函数TlsAlloc,分配一个ID,将其记录在全局的线程安全的tls_i变量,并通过TlsSetValue函数将数据保存在线程安全的区域;当调用获取资源的函数时,通过TlsGetValue获取资源,处理完成后,调用Tlsfree对TLS index释放,以便新线程占有。 $ L" r0 f; w- S2 G) c* A% G( {; j
    这样,只要DLL中每个函数保证其局部是线程安全的,函数间传递数据通过TLS(静态和动态),就可以实现整个DLL的线程安全。 - _, ^( P) f2 z" @5 y
    3、限制访问DLL中某一函数的线程数目。 & O8 P3 N0 t% Q
    有时候,对于DLL中的某一个函数的访问线程数目是有限制的,超过了限制其他线程就得等一定的时间,一定的时间过后如果还不能得到执行机会,那就返回超时。这样的设计对用户来说是友好的,而且很实用,有的商业程序确实是按照允许用户访问的通道数目来计价的。 8 s4 @" Q/ G+ M5 ^7 ]( m
    对DLL中的函数做这样的一个封装,一般是简单的待用Semaphore信号量,来解决。DLL初始化时调用CreateSemaphore函数对信号量进行初始化,其原型如下:1 G( D" C5 [4 B8 I. ]
    HANDLE CreateSemaphore( 1 x8 K1 f/ P$ y3 L1 g$ I) P7 I
      LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
    , d( Y- _( T, l5 B- m" }" a                       // pointer to security attributes
    $ |/ F0 z$ @% P) W# ~" W6 L  LONG lInitialCount,  // initial count
    . i, e( B4 K3 e, O$ V& d# ?: l, j5 l& P  LONG lMaximumCount,  // maximum count
    & U  O& \8 \( i/ M  LPCTSTR lpName       // pointer to semaphore-object name   R; p# |. ^: s
    ); 2 ?# y$ A5 C" a4 p* _& Y6 j# K
    对于信号量,它每WaitForSingleObject一次(当然是要进入),其状态值(一个整数)就减1,使用完ReleaseSemaphore其状态值就加1,当其状态值为0时信号量就由有信号变为无信号。利用信号量的这一特性,我们在初始化时将信号量的初始值(第2个参数)设置为限制的线程访问数目。在要限制访问线程数目的函数内部,通过调用WaitForSingleOject获取控制权,并指定一个等待时间(这个由配置文件指定),根据情况超时返回,使用完ReleaseSemaphore释放对占用,让其他线程可以调用这个函数。 7 x5 ]$ Y% e/ n5 l7 O
    4、多进程情况下的多线程安全DLL。
    . t5 Z6 y" {3 x7 d6 o前面3讲了有时候需要对某一函数的访问线程进行限制,而我们知道,DLL是可以被多个进行加载并调用的。那就是说如果我们只对一个进程进行了限制,那么在多进程调用的情况下,这样的限制被轻易攻破。 7 q" B6 T: U# ~
    我们都知道,Semaphore信号量属于内核对象,也就是说其可以被多进程共享访问,也就说,如果我们给一个Semaphore指定了一个名字,在另一个进程中,我们只要调用OpenSemaphore函数用同一名字打开信号量就可以访问了。这样问题就解决了?   }: [6 O8 ~# S% W3 J+ S& {: q
    现实情况是,多进程情况下,一般不是简单的多进程共享一个Semaphore就可以了。多进程间需要互通很多信息。一般的解决办法是,采用共享数据段。
    ! c3 m9 R7 e0 R7 r# X/ ?#pragma data_seg("share") 0 s+ Q6 P9 t9 a$ C
    int share_data;
    & H6 H" f3 |% E5 O#pragma data_seg()
    $ P/ x1 y3 `  D( o) {#pragma comment(linker,"/SECTION:share, RWS")
    . C( y2 I4 d# [# u, ~通过pragam编译器指令生成了一个名叫share的共享数据段,这样对于变量share_data就可以多进程共享的了。如果要多进程间交换数据,只要在data_seg中添加数据定义即可。
    * ~2 H7 P* A- Y在MFC DLL中创建多线程: 5 B5 M$ }$ W. T, O' c
    除了在初始化过程中,只要MFC DLL 使用 TlsAlloc 这样的 Win32 线程本地存储区 (TLS) 函数来分配线程本地存储区,就可以安全地创建多线程。但是,如果 MFC DLL 是使用 __declspec(thread) 分配线程本地存储区,客户端应用程序必须隐式链接到 DLL。如果客户端应用程序显式链接到 DLL,对 LoadLibrary 的调用将不会成功加载此 DLL。 ! j8 v: ]9 M' O- U  `( o
    原因可以参考微软的这篇文章“PRB: Calling LoadLibrary() to Load a DLL That Has Static TLS”,地址:http://support.microsoft.com/kb/118816/en-us
    " l  e. z& M: e) c启动期间创建新的 MFC 线程的 MFC DLL 在由应用程序加载时将挂起。每当在下列对象内部通过调用 AfxBeginThreadCWinThread::CreateThread 创建线程时,就会发生这种情况: % ?8 G2 P6 ~" }; _, H
    9 `1 [* N6 W, H7 S2 p+ b. |/ H
    规则 DLL 中 CWinApp 派生对象的 InitInstance # H" N0 Y* I; c. Q/ h! w
    6 \: Y! o" z" K" ]8 k8 R) h% B
    规则 DLL 中提供的 DllMainRawDllMain 函数。 & y% I- @& V2 K$ G% m8 `1 h! t
    * E7 m% a- [6 r5 t2 u6 \$ c+ h. ?
    扩展 DLL 中提供的 DllMainRawDllMain 函数。& k9 b" Z$ }8 S' F0 R/ X
    为什么呢,原因可参考这篇文章“PRB: Cannot Create an MFC Thread During DLL Startup”,http://support.microsoft.com/kb/142243/en-us
    5 I! ~) z% Z4 F( [1 L3 e4 A) g解决办法:Regular DLLs that create threads should only do so in functions exported from the DLL and called by client applications.The recommended solution for MFC DLLs that need to create a thread when the DLL starts is to add a specific exported initialization function and create the thread in it.(创建多线程的规则dll,只能将启动线程的函数放入导出函数中,由客户端调用执行,不能在dll启动期间自动创建线程。)
    展开全文
  • 在C++代码中要调用C++编写的dll中的一个函数,并传递一个回调函数(很明显,这里应该传递一个委托),C++函数在内部通过一个额外的线程在后台运行,完成工作后通过这个回调函数通知前台的C++代码。。。。 【问题】 ...
  • 最近碰到这么个问题: ...测试的时候发现,多线程的时候,AB有时候工作异常。 问题:如何保证exe分配的同一线程同时调用AB两个dll(配套),即线程1调用A1,B1,线程2调用A2,B2;而不是线程1调用A1,B2... 谢谢
  • 有一个.net服务,服务中开了多线程。其中有一线程会去调用非托管C++DLL,并阻塞等待其返回值。其他线程同步做文件处理、数据状态更新等操作。现在调用非托管C++DLL的线程会因为非托管代码的问题,崩溃。这样整个服务...
  • VC编译选项 多线程(/MT) 多线程调试(/MTd) 多线程 DLL (/MD) 多线程调试 DLL (/MDd)
  • 经过研究发现需要使用TLS技术来进行多线程本地存贮,为每一个线程开辟一块空间来存储C语言生成的dll动态库中需要保存的全局变量,这样来保证多线程调用dll中的方法时,dll中的全局变量相互独立,不受其他线程干扰。...
  • 多线程调用 opencv 图像处理

    千次阅读 2019-06-06 11:56:26
    调用相机视频进行实时处理,原本是基于c++ 的线程池实现的,后来编译成 dll,转用 c# 的线程池实现,Dll 里面的图像的处理过程还是比较的,但是经常会出现 dll 中内存地址访问冲突,通常是程序跑了一段时间之后。...
  • 一个Java对象通过JNI调用DLL中一个send()函数向服务器发送消息,不等服务器消息到来就立即返回,同时把JNI接口的指针JNIEnv *env(虚拟机环境指针),和jobject obj保存在DLL中的变量里.一段时间后,DLL中的消息接收线程...
  • exe上的多线程几年前就弄稳定了,基本没问题。 VB6做的标准DLL给VC和其他语言使用,要么调用时就崩了,要么调用完退出进程时崩了。 今天基本解决这个问题了,一种方法是退出前强制自动结束进程,把崩溃让你看不到...
  • 程序加载了一个dll,之后该程序的个...一个线程调用dll的这个接口之后,这个接口还没有返回,另一个个线程又调用了这个接口,那么dll中,是处理怎么第二个调用的?是让第二个调用等待,还是和第一个接口并行调用?
  • DLL多线程

    万次阅读 2016-04-06 11:02:35
    DLL中可以处理多线程,WIN32对于多线程的支持是操作系统本身提供的一种能力,并不在于用户编写的是哪一类程序。即便是一个控制台程序,我们都可以使用多线程: #include #include void ThreadFun(void) { while...
  • MFC多线程调用UpdateData函数问题

    千次阅读 2014-08-16 10:00:13
    多线程调用UpdateData  2009-12-01 08:53:42| 分类: VS|Window|举报|字号 订阅 ((CSetupDlg*)AfxGetApp()->m_pMainWnd)->UpdateData(0); ——————————————————————...
  • c# 多线程 调用带参数函数

    万次阅读 2011-11-14 16:57:03
    线程操作主要用到Thread类,他是定义在System.Threading.dll下。使用时需要添加这一个引用。该类提供给我们四个重载的构造函数(以下引自msdn)。 Thread (ParameterizedThreadStart) 初始化 Thread 类的新实例,...
  • 多线程调试 DLL (/MDd)

    千次阅读 2015-05-14 15:32:34
    这些选项选择单线程或多线程运行时例程,指示多线程模块是否为 DLL,并选择运行时库的发布版本或调试版本。 选项 说明 /MD 定义 _MT 和 _DLL 以便同时从标准 .h 文件中选择运行时例程的多线程特定版本和 DLL...
  • 调用MSVC CRT的函数_beginthread()或_beginthreadex()来创建线程。 _beginthread 参数和返回值 unsigned long _beginthread( void(_cdecl *start_address)(void *), //声明为void (*start_address)(void *)形式 ,...
  • C# 调用dll文件

    万次阅读 2019-02-28 18:13:55
    动态链接库(也称为DLL,即为“Dynamic Link Library”的缩写)是Microsoft Windows最重要的组成要素之一,打开Windows系统文件夹,你会发现文件夹中有很多DLL文件,Windows就是将一些主要的系统功能以DLL模块的形式...
  • Delphi调用Dll

    2013-12-03 16:34:44
    IsLibrary 可以检测代码是执行在应用程序中还是执行在DLL中,在应用程序中 IsLibrary 总是为 False ,在 DLL中总是为 True 。在 DLL的整个生命周期中,HInstance 包含了库的实例句柄。在DLL中,系统变量 CmdLine ...
  • 如何编写线程安全的DLL

    千次阅读 2007-03-23 17:29:00
    而且这些DLL有时会被多个进程同时调用,这就牵扯到多进程的多线程调用DLL的问题。有点绕口,以下我根据我实践中遇到的问题,分四种情况分享一下我解决此类问题的经验:1、动态库只有一个导出函数。这种情况非常少,...
  • DLL的优点简单的说,dll有以下几个优点:1) 节省内存。同一个软件模块,若是以源代码的形式重用,则会被编译到不同的可执行程序中,同时运行这些exe时这些模块的二进制码会被重复加载到内存中。如果使用dll,则只在...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 68,875
精华内容 27,550
关键字:

多线程调用dll