精华内容
下载资源
问答
  • 多线程调用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

    展开全文
  • dll中有一个读串口的函数; 我在C#两个线程中均会调用这个串口函数,运行时有冲突; 这是因为调用一个函数的原因?还是调用了一个串口的原因?还是两者都有?该怎么解决呢?求大佬们帮忙
  • 多线程调用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启动期间自动创建线程。)
    展开全文
  • DLL多线程

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

    VC++动态链接库编程之多线程


    在DLL中可以处理多线程,WIN32对于多线程的支持是操作系统本身提供的一种能力,并不在于用户编写的是哪一类程序。即便是一个控制台程序,我们都可以使用多线程:

    #include <stdio.h>
    #include <windows.h>
    void ThreadFun(void)
    {
    while(1)
    {
    printf( "this is new thread/n" );
    Sleep( 1000 );
    }
    }
    int main()
    {
    DWORD threadID;
    CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFun, NULL, 0, &threadID );
    while(1)
    {
    printf( "this is main thread/n" );
    Sleep( 1000 );
    }
    }

    观察程序运行的结果为在控制台窗口上交替输出this is main thread、this is new thread。
    我们来看下面的一个多线程DLL的例子。
    DLL程序提供一个接口函数SendInit,在此接口中启动发送线程SendThreadFunc,在这个线程的对应工作函数中我们使用原始套接字socket发送报文。参考微软出版的经典书籍《Windows核心编程》,我们发现,不宜在DLL被加载的时候(即进程绑定时)启动一个新的线程。
    这个线程等待一个CEvent事件(用于线程间通信),应用程序调用DLL中的接口函数SendMsg( InterDataPkt sendData )可以释放此事件。下面是相关的源代码:


    (1)发送报文线程入口函数

    ///
    //函数名:SendThreadFunc
    //函数功能:发送报文工作线程入口函数,使用UDP协议
    
    DWORD WINAPI SendThreadFunc( LPVOID lpvThreadParm )
    //提示:对于线程函数应使用WINAPI声明,WINAPI被宏定义为__stdcall
    {
    /* 创建socket */
    sendSock = socket ( AF_INET, SOCK_DGRAM, 0 );
    if ( sendSock == INVALID_SOCKET )
    {
    AfxMessageBox ( "Socket创建失败" );
    closesocket ( recvSock );
    }
    /* 获得目标节点端口与地址 */
    struct sockaddr_in desAddr;
    desAddr.sin_family=AF_INET;
    desAddr.sin_port=htons( DES_RECV_PORT ); //目标节点接收端口
    desAddr.sin_addr.s_addr = inet_addr( DES_IP );
    /* 发送数据 */
    while(1)
    {
    WaitForSingleObject( hSendEvent, 0xffffffffL );//无限等待事件发生
    ResetEvent( hSendEvent );
    sendto( sendSock, (char *)sendSockData.data, sendSockData.len, 0, (struct sockaddr*)&desAddr, sizeof(desAddr) );
    }
    return -1;
    }

    (2)MFC规则DLL的InitInstance函数

    /
    // CMultiThreadDllApp initialization
    BOOL CMultiThreadDllApp::InitInstance()
    {
    if ( !AfxSocketInit() ) //初始化socket
    {
    AfxMessageBox( IDP_SOCKETS_INIT_FAILED );
    return FALSE;
    }
    return TRUE;
    }

    (3)启动发送线程

    
    //函数名:SendInit
    //函数功能:DLL提供给应用程序调用接口,用于启动发送线程
    /
    void SendInit(void)
    {
    hSendThread = CreateThread( NULL, 1000, SendThreadFunc, this, 1, &uSendThreadID );
    }

    (4)SendMsg函数

    
    //函数名:SendMsg
    //函数功能:DLL提供给应用程序调用接口,用于发送报文
    /
    extern "C" void WINAPI SendMsg( InterDataPkt sendData )
    {
    sendSockData = sendData;
    SetEvent( hSendEvent ); //释放发送事件
    }
    以上程序仅仅是一个简单的例子,其实在许多工程应用中,我们经常看到这样的处理方式。这个DLL对用户而言仅仅使一个简单的接口函数SendMsg,对调用它的应用程序屏蔽了多线程的技术细节。与之类似,MFC提供的CSocket类在底层自己采用了多线程机制,所以使我们免去了对多线程的使用。


    ————————————


    如何编写线程安全的DLL


    DLL有个共同的特点就是都有一个初始化函数,一个资源释放函数,其他几个函数都是核心功能函数。而且这些DLL有时会被多个进程同时调用,这就牵扯到多进程的多线程调用DLL的问题。有点绕口,以下我根据我实践中遇到的问题,分四种情况分享一下我解决此类问题的经验:
    1、动态库只有一个导出函数。
    这种情况非常少,也是最容易处理的情况。这种情况下编写函数时,只需要考虑不要有冲突的全局数据就可以了。这里的全局数据包括了在堆中分配的数据块和静态全局变量等。如果存在这样的全局数据,那么进程中的不同线程访问这个函数就会造成冲突。
    解决办法也很简单,就是尽量用堆栈(stack)来解决问题。由于堆栈的所有人是线程,所以它必然是线程安全的。当然也要注意避免堆栈溢出。
    我们都知道,如果要在函数再次调用时保留前一次调用的状态,可以使用静态变量。但如果你要保持函数的线程安全,那么静态变量是不能用的,因为静态变量是全局的,是属于进程的,也就是属于进程内线程共享的。所以如果确实需要在同一线程中保持函数的状态,相当于在不同次调用间传递参数,可以考虑使用静态全局线程局部变量,即:
    __declspec( thread ) int tls_i = 1;
    该变量定义就使编译器保证了tls_i是对应于每个线程的,即每个线程都一个tls_i的副本(copy),这样必然就是线程安全的。
    2、动态库导出了多个函数,而且多个函数间存在数据传递。
    就像前面说的,一般DLL都导出多个函数,一个初始化,一个资源释放,其他为核心功能函数。这些函数间极有可能发生数据传递。如果一个初始化函数是在线程A中调用的,而核心功能函数是在线程B中调用的,那么线程A初始化函数的资源就无法对应线程B中的核心功能,此外还有核心功能函数间的数据传递,这样的DLL就不是线程安全的,必然导致错误。
    解决办法是由用户(即使用DLL的人)保证这些导出函数是在一个线程中调用。但这样会很大程度上限制接口的设计和用户的使用自由度。所以最好的方法是函数只管自己的线程安全,不同函数传递数据用动态TLS,线程局部存储。
    比如:我在全局定义了一个变量,用于存储当前线程局部存储的index ID。
    __declspec( thread ) int tls_i = 1;
    当调用分配资源的函数时,调用动态TLS函数TlsAlloc,分配一个ID,将其记录在全局的线程安全的tls_i变量,并通过TlsSetValue函数将数据保存在线程安全的区域;当调用获取资源的函数时,通过TlsGetValue获取资源,处理完成后,调用Tlsfree对TLS index释放,以便新线程占有。
    这样,只要DLL中每个函数保证其局部是线程安全的,函数间传递数据通过TLS(静态和动态),就可以实现整个DLL的线程安全。
    3、限制访问DLL中某一函数的线程数目。
    有时候,对于DLL中的某一个函数的访问线程数目是有限制的,超过了限制其他线程就得等一定的时间,一定的时间过后如果还不能得到执行机会,那就返回超时。这样的设计对用户来说是友好的,而且很实用,有的商业程序确实是按照允许用户访问的通道数目来计价的。
    对DLL中的函数做这样的一个封装,一般是简单的待用Semaphore信号量,来解决。DLL初始化时调用CreateSemaphore函数对信号量进行初始化,其原型如下:
    HANDLE CreateSemaphore(
    LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
    // pointer to security attributes
    LONG lInitialCount, // initial count
    LONG lMaximumCount, // maximum count
    LPCTSTR lpName // pointer to semaphore-object name);对于信号量,它每WaitForSingleObject一次(当然是要进入),其状态值(一个整数)就减1,使用完ReleaseSemaphore其状态值就加1,当其状态值为0时信号量就由有信号变为无信号。利用信号量的这一特性,我们在初始化时将信号量的初始值(第2个参数)设置为限制的线程访问数目。在要限制访问线程数目的函数内部,通过调用WaitForSingleOject获取控制权,并指定一个等待时间(这个由配置文件指定),根据情况超时返回,使用完ReleaseSemaphore释放对占用,让其他线程可以调用这个函数。
    4、多进程情况下的多线程安全DLL。
    现实情况是,多进程情况下,一般不是简单的多进程共享一个Semaphore就可以了。多进程间需要互通很多信息。一般的解决办法是,采用共享数据段。
    #pragma data_seg("share")
    int share_data;
    #pragma data_seg()
    通过pragam编译器指令生成了一个名叫share的共享数据段,这样对于变量share_data就可以多进程共享的了。


    ——————————————


    TLS--线程局部存储


    概念:线程局部存储(Thread Local Storage,TLS)用来将数据与一个正在执行的指定线程关联起来。
    进程中的全局变量与函数内定义的静态(static)变量,是各个线程都可以访问的共享变量。在一个线程修改的内存内容,对所有线程都生效。这是一个优点也是一个缺点。说它是优点,线程的数据交换变得非常快捷。说它是缺点,一个线程死掉了,其它线程也性命不保; 多个线程访问共享数据,需要昂贵的同步开销,也容易造成同步相关的BUG。
    如果需要在一个线程内部的各个函数调用都能访问、但其它线程不能访问的变量(被称为static memory local to a thread 线程局部静态变量),就需要新的机制来实现。这就是TLS。
    线程局部存储在不同的平台有不同的实现,可移植性不太好。幸好要实现线程局部存储并不难,最简单的办法就是建立一个全局表,通过当前线程ID去查询相应的数据,因为各个线程的ID不同,查到的数据自然也不同了。大多数平台都提供了线程局部存储的方法,无需要我们自己去实现:

    linux

    int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
    int pthread_key_delete(pthread_key_t key);
    void *pthread_getspecific(pthread_key_t key);
    int pthread_setspecific(pthread_key_t key, const void *value);

    功能:它主要是为了避免多个线程同时访存同一全局变量或者静态变量时所导致的冲突,尤其是多个线程同时需要修改这一变量时。为了解决这个问题,我们可以通过TLS机制,为每一个使用该全局变量的线程都提供一个变量值的副本,每一个线程均可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。而从全局变量的角度上来看,就好像一个全局变量被克隆成了多份副本,而每一份副本都可以被一个线程独立地改变。
    分类:动态TLS和静态TLS。
    用途:动态TLS和静态TLS这两项技术在创建DLL的时候更加有用,这是因为DLL通常并不知道它们被链接到的应用程序的结构是什么样的。
    1. 如果应用程序高度依赖全局变量或静态变量,那么TLS可以成为我们的救生符。因而最好在开发中最大限度地减少对此类变量的使用,更多的依赖于自动变量(栈上的变量)和通过函数参数传入的数据,因为栈上的变量始终都是与某个特定的线程相关联的。如果不使用此类变量,那么就可以避免使用TLS。
    2. 但是在编写应用程序时,我们一般都知道自己要创建多少线程,自己会如何使用这些线程,然后我们就可以设计一些替代方案来为每个线程关联数据,或者设计得好一点的话,可以使用基于栈的方法(局部变量)来为每个线程关联数据。
    1 动态TLS
    系统中每个进程都有一组正在使用标志(in-use flags),每个标志可以被设为FREE或INUSE,表示该TLS元素是否正在被使用。
    进程中的线程是通过使用一个数组来保存与线程相关联的数据的,这个数组由TLS_MINIMUM_AVAILABLE个元素组成,在WINNT.H文件中该值被定义为64个。也就是说当线程创建时,系统给每一个线程分配了一个数组,这个数组共有TLS_MINIMUM_AVAILABLE个元素,并且将这个数组的各个元素初始化为0,之后系统把这个数组与新创建的线程关联起来。每一个线程中都有它自己的数组,数组中的每一个元素都能保存一个32位的值。在使用这个数组前首先要判定,数组中哪个元素可以使用,这将使用函数TlsAlloc来判断。函数TlsAlloc判断数组中一个元素可用后,就把这个元素分配给调用的线程,并保留给调用线程。要为数组中的某个元素赋值可以使用函数TlsSetValue,要得到某个元素的值可以使用TlsGetValue。

    一般通过调用一组4个API函数来使用动态TLS:TlsAlloc、TlsSetValue、TlsGetValue和TlsFree。
    1)要使用动态TLS,必须先调用TlsAlloc函数:
    DWORD WINAPI TlsAlloc(void);
    这个函数让系统对进程中的位标志进行检索并找到一个FREE标志,然后系统会将该标志从FREE改为INUSE并让TlsAlloc返回该标志在位数组中的索引。一个DLL(或应用程序)通常将这个索引保存在一个全局变量中。由于这个值会在整个进程地址范围内使用,而不是在线程范围内使用,因此这种情况下全局变量是一个更好的选择。
    如果TlsAlloc无法在列表中找到一个FREE标志,那么它会返回TLS_OUT_OF_INDEXES(在WinBase.h中被定义为0xFFFFFFFF)。
    当系统创建一个线程的时候,会分配TLS_MINIMUM_AVAILABLE个PVOID值,将它们都初始化为0,并与线程关联起来。每个线程都有自己的PVOID数组,数组中的每个PVOID可以保存任意值。在能够将信息保存到线程的PVOID数组中之前,我们必须知道数组中的哪个索引可供使用---这就是调用TlsAlloc的目的。TlsAlloc为我们预定了一个索引,如果为2,即TlsAlloc返回值为2,那么无论是进程中当前正在运行的线程,还是今后可能会创建的线程,都不能再使用该索引2了。


    2)为了把一个值放到线程的PVOID数组中,应该调用TlsSetValue函数:

    BOOL WINAPI TlsSetValue(
    __in DWORD dwTlsIndex, //索引值,表示在数组中的具体位置
    __in_opt LPVOID lpTlsValue //要设置的值
    );


    当一个线程调用TlsSetValue函数成功时,它会修改自己的PVOID数组,但它无法修改另一个线程的TLS值。在调用TlsSetValue时,我们应该总是传入前面在调用TlsAlloc时返回的索引。因为Windows为了效率牺牲了对输入值的错误检测。
    3)为了从线程的数组中取回一个值,应该调用函数TlsGetValue:

    LPVOID WINAPI TlsGetValue(
    __in DWORD dwTlsIndex //索引值
    );

    这个函数会返回在索引为dwTlsIndex的TLS元素中保存的值。TlsGetValue只会查看属于调用线程的数组。
    4)当不再需要一个已经预定的TLS元素时,应该调用TlsFree函数:

    BOOL WINAPI TlsFree(
    __in DWORD dwTlsIndex //索引值
    );

    这个函数告诉系统已经预定的这个TLS元素现在不需要了,函数会将进程内的位标志数组中对应的INUSE标志重新设回FREE。此外,函数还会将所有线程中该元素的内容设为0.
    使用动态TLS:
    通常,如果DLL要使用TLS,那它会在DllMain函数处理DLL_PROCESS_ATTACH的时候调用TlsAlloc,在DllMain处理DLL_PROCESS_DETACH的时候调用TlsFree。而TlsSetValue和TlsGetValue的调用则最有可能发生在DLL所提供的其他函数中。而向应用程序中添加TLS的一种方法是直到需要时才添加。
    下面是在应用程序中使用动态TLS的实例代码:

    示例1:

    #include <stdio.h>
    #define THREADCOUNT 4
    DWORD dwTlsIndex;
    VOID ErrorExit(LPSTR);
    VOID CommonFunc(VOID)
    {
    LPVOID lpvData;
    // Retrieve a data pointer for the current thread.
    lpvData = TlsGetValue(dwTlsIndex);
    if ((lpvData == 0) && (GetLastError() != ERROR_SUCCESS))
    ErrorExit("TlsGetValue error");
    // Use the data stored for the current thread.
    printf("common: thread %d: lpvData=%lx\n",
    GetCurrentThreadId(), lpvData);
    Sleep(5000);
    }
    DWORD WINAPI ThreadFunc(VOID)
    {
    LPVOID lpvData;
    // Initialize the TLS index for this thread.
    lpvData = (LPVOID) LocalAlloc(LPTR, 256);
    if (! TlsSetValue(dwTlsIndex, lpvData))
    ErrorExit("TlsSetValue error");
    printf("thread %d: lpvData=%lx\n", GetCurrentThreadId(), lpvData);
    CommonFunc();
    // Release the dynamic memory before the thread returns.
    lpvData = TlsGetValue(dwTlsIndex);
    if (lpvData != 0)
    LocalFree((HLOCAL) lpvData);
    return 0;
    }
    int main(VOID)
    {
    DWORD IDThread;
    HANDLE hThread[THREADCOUNT];
    int i;
    // Allocate a TLS index.
    if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES)
    ErrorExit("TlsAlloc failed");
    // Create multiple threads.
    for (i = 0; i < THREADCOUNT; i++)
    {
    hThread[i] = CreateThread(NULL, // default security attributes
    , // use default stack size
    (LPTHREAD_START_ROUTINE) ThreadFunc, // thread function
    NULL, // no thread function argument
    , // use default creation flags
    &IDThread); // returns thread identifier
    // Check the return value for success.
    if (hThread[i] == NULL)
    ErrorExit("CreateThread error\n");
    }
    for (i = 0; i < THREADCOUNT; i++)
    WaitForSingleObject(hThread[i], INFINITE);
    TlsFree(dwTlsIndex);
    return 0;
    }
    VOID ErrorExit (LPSTR lpszMessage)
    {
    fprintf(stderr, "%s\n", lpszMessage);
    ExitProcess(0);
    }


    示例二:

    #include <windows.h>
    #include <process.h>
     
    // 利用TLS记录线程的运行时间
     
    DWORD g_tlsUsedTime;
    void InitStartTime();
    DWORD GetUsedTime();
     
     
    UINT __stdcall ThreadFunc(LPVOID)
    {
    int i;
     
    // 初始化开始时间
    InitStartTime();
     
    // 模拟长时间工作
    i = 10000*10000;
    while(i--) { }
     
    // 打印出本线程运行的时间
    printf(" This thread is coming to end. Thread ID: %-5d, Used Time: %d \n",
    ::GetCurrentThreadId(), GetUsedTime());
    return 0;
    }
     
    int main(int argc, char* argv[])
    {
    UINT uId;
    int i;
    HANDLE h[10];
     
    // 通过在进程位数组中申请一个索引,初始化线程运行时间记录系统
    g_tlsUsedTime = ::TlsAlloc();
     
    // 令十个线程同时运行,并等待它们各自的输出结果
    for(i=0; i<10; i++)
    {
    h[i] = (HANDLE)::_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, &uId);
    }
    for(i=0; i<10; i++)
    {
    ::WaitForSingleObject(h[i], INFINITE);
    ::CloseHandle(h[i]);
    }
     
    // 通过释放线程局部存储索引,释放时间记录系统占用的资源
    ::TlsFree(g_tlsUsedTime);
    return 0;
    }
     
    // 初始化线程的开始时间
    void InitStartTime()
    {
    // 获得当前时间,将线程的创建时间与线程对象相关联
    DWORD dwStart = ::GetTickCount();
    ::TlsSetValue(g_tlsUsedTime, (LPVOID)dwStart);
    }
     
    // 取得一个线程已经运行的时间
    DWORD GetUsedTime()
    {
    // 获得当前时间,返回当前时间和线程创建时间的差值
    DWORD dwElapsed = ::GetTickCount();
    dwElapsed = dwElapsed - (DWORD)::TlsGetValue(g_tlsUsedTime);
    return dwElapsed;
    }

    下面的实例代码是在DLL中使用TLS的:
    示例三:

    #include <windows.h>
    static DWORD dwTlsIndex; // address of shared memory
     
    // DllMain() is the entry-point function for this DLL.
    BOOL WINAPI DllMain(HINSTANCE hinstDLL, // DLL module handle
    DWORD fdwReason, // reason called
    LPVOID lpvReserved) // reserved
    {
    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 ((dwTlsIndex = 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(dwTlsIndex, lpvData);
    break;
    // The thread of the attached process terminates.
    case DLL_THREAD_DETACH:
    // Release the allocated memory for this thread.





















    展开全文
  • 多线程调用 opencv 图像处理

    千次阅读 2019-06-06 11:56:26
    调用相机视频进行实时处理,原本是基于c++ 的线程池实现的,后来编译成 dll,转用 c# 的线程池实现,Dll 里面的图像的处理过程还是比较的,但是经常会出现 dll 中内存地址访问冲突,通常是程序跑了一段时间之后。...

    背景

    调用相机视频进行实时处理,原本是基于c++ 的线程池实现的,后来编译成 dll,转用 c# 的线程池实现,Dll 里面的图像的处理过程还是比较多的,但是经常会出现 dll 中内存地址访问冲突,通常是程序跑了一段时间之后。

    通过 dll 的 debug,主要报错在图像的 resize/copyTo/clone 函数中,注释掉就没有报错了。

    定位问题

    先看下 copyTo 的实现

    void GpuMat::copyTo(OutputArray dst, InputArray mask) const
    {
        copyTo(dst, mask, Stream::Null());
    }
    

    这个直接用vs 跳转的,是 gpu 版本的定义;调用了以下第一个实现,注释中显示是non-blocking call,非阻塞的;不确定这里是不是线程安全的,通过 debug 定位到的问题就是,偶然会访问 image 的 data 出现访问冲突,读取字符出错…

    搜索了很多关于 opencv 线程安全的处理方式,有建议用 UMat 的矩阵定义方式,但是更改起来太复杂;而且实现也不完全是跟 Mat 等价的。

    最后发现似乎所有的情况都是跟访问 其中一个 Mat 有关,而这个 Mat 是根据 c# 传进来的图转化的,内存可能是跟 c# 是共享的,所以有时候会出错,在函数的入口直接 clone 一份,后面就没有复现这个问题。具体的原因还没完全定位到。

    //! copies those GpuMat elements to "m" that are marked with non-zero mask elements (Non-Blocking call)
    void copyTo(OutputArray dst, InputArray mask, Stream& stream) const;
    
    //! copies those GpuMat elements to "m" that are marked with non-zero mask elements (Blocking call)    
    void copyTo(OutputArray dst, InputArray mask) const;   
    

    补充记录

    c# 传图像到 dll 之后,对图像进行修改,然后在 c# 中直接取该图像即是修改后的图像数据,因为要展示到 wpf 的image 组件中,image 组件的 source 必须是 BitmapImage 类型,网上有 Bitmap 转换到 BitmapImage 的方式,但是有一个取内存的操作,目前我使用了 Emgv 的图像处理,所以读进来的图是 Image<Bitmap, byte>格式,提供一个默认的 img.toJpegData() 的方法,用于获取图像的二进制数据;但是后来发现这个步骤耗时很高,本机测试200ms,这个太夸张了。

    改用网上的转换方式,将 Bitmap 转换成 byte[];速度为40ms+,至少已经好多了。

    // 类似这样
    Bitmap b = new Bitmap( "test.bmp "); 
    MemoryStream ms = new MemoryStream(); 
    b.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp); 
    
    展开全文
  • 调用MSVC CRT的函数_beginthread()或_beginthreadex()来创建线程。 _beginthread 参数和返回值 unsigned long _beginthread( void(_cdecl *start_address)(void *), //声明为void (*start_address)(void *)形式 ,...
  • https://ask.csdn.net/questions/7531883?weChatOA= 但是这样是否存在一个问题就是,多线程程序调用api是否存在冲突,应该如何解决,pimpl类建一个指针吗? 可否仿照该问题给出例子。 #define DLLIMPL #include ...
  • 最近在学习编写c#socket 多线程 异步处理客户端请求的一些东西。客户端发出请求,服务器端根据请求类型,调用不同的dll来进行异步的具体处理,这些都实现了。但是现在发现个问题,在比较多并发的情况下,调用dll处理...
  • [c++]如何编写线程安全的DLL(转帖)

    千次阅读 2010-03-18 08:28:00
    文章收藏, 网上资源, 软件技术, 电脑与网络邢红瑞 发表于 2006-...而且这些DLL有时会被多个进程同时调用,这就牵扯到多进程的多线程调用DLL的问题。有点绕口,以下我根据我实践中遇到的问题,分四种情况分享一下我解
  • 最近在研究多线程,然后突然想到如果两个线程同时访问一个函数的话,要不要加锁呢,加锁怎么加,不加又怎样这样的问题..然后去网上找了些帖子学习学习...... 上面的代码明显执行串了!!!! 函数本身只是代码,代码是...
  • 这里有几点意见:1)在你的情况下,最好只有两个线程:主线程和另一个线程。不过,它也将与三个工作。只是你的主线程只会等待其他线程完成。2)应该显式地join()生成的所有线程。在终止主线程之前...
  • 一、什么是COM组件 ...2、COM组件是以WIN32动态链接库(DLL)或可执行文件(EXE)形式发布的可执行代码组成。 ...6、COM组件不是DLL,只是利用DLL来给组件提供动态链接的能力 ...9、可以供多种语言调用,跨语言
  • 在C#中使用单线程调用DLL非常正常。 为达到充分利用网络资源的目的,[color=#FF0000]用C#在个线程中异步调用该DLL[/color],[color=#FF0000]却出现地址冲突等问题(DLL中的地址冲突)[/color]。 尝试在C#...
  • dll的多进程多线程安全的几种策略

    千次阅读 2013-07-08 10:39:06
    如果存在这样的全局数据,那么进程中的不同线程访问这个函数就会造成冲突。 解决办法也很简单,就是尽量用堆栈(stack)来解决问题。由于堆栈的所有人是线程,所以它必然是线程安全的。当然也要注意避免堆栈溢出
  • 当printf遇上多线程

    万次阅读 2016-08-20 23:16:24
    有很多Linux和Windows程序员在编写多线程程序中使用printf导致的种种问题。我猜测这与printf设计时的不可重入性有关,虽然我在文中用嵌入式的串口打印函数作为类比,可是这两个或许还不是一回事。 如果你遇到了这个...
  • jna 多线程问题

    千次阅读 2015-03-26 11:17:01
    多线程时,在方法上加 synchronized 我在这里将文章整理了一下,重新修改了部分描述和增加了一些重要的说明事项。修改文如下: 问题描述: 一个java对象通过JNI调用DLL中一个send()函数向服务器发送消...
  • 不让它在多线程也好,多进程也好同时调这俩函数。 我试过多进程互斥锁,但是没用,查了下是因为写实拷贝,每个进程都有独立的进程空间(私有的虚拟内存),所以互斥锁并...
  • MFC多线程调用UpdateData函数问题

    千次阅读 2014-08-16 10:00:13
    多线程调用UpdateData  2009-12-01 08:53:42| 分类: VS|Window|举报|字号 订阅 ((CSetupDlg*)AfxGetApp()->m_pMainWnd)->UpdateData(0); ——————————————————————...
  • 而且这些DLL有时会被多个进程同时调用,这就牵扯到多进程的多线程调用DLL的问题。有点绕口,以下我根据我实践中遇到的问题,分四种情况分享一下我解决此类问题的经验: 1、动态库只有一个导出函数。 这种情况非常...
  • 在您面临的问题是,您需要在调用导出的线程上初始化VB6运行时。这包括初始化COM单元(STA)。最简单的方法是从python代码创建VB6类的实例。在在导出函数中“手动”初始化VB6运行时要困难得,并且需要使用自定义类型...
  • 封装多线程模块-线程启动 • 1、CreateThread • 2、线程_启动_句柄() • 3、线程_启动_逻辑() • 4、线程句柄 • 5、线程ID • 6、易语言SHCreateThread 511遇见易语言多线程大漠多线程 SHCreateThread ...
  • 不要在工作线程函数内操作MFC控件,应通过 发消息给主线程的方式,在主线程   中实现。 最近在编写一个基于对话框的MFC程序,在程序中创建了一个子线程,想通过子线程获取和控制主窗口中的一个编辑框显示数据,...
  • C++多线程与智能指针

    千次阅读 2019-10-08 09:10:57
    文章目录C++线程与智能指针线程C++11线程POSIX线程线程属性分离线程调度策略与优先级线程同步条件变量智能指针shared_ptrweak_ptrunique_ptr自定义智能指针部分C++11、14特性nullptr类型推导基于范围的 for 循环...
  • 原文作者:aircraft ... 先讲Linux下(windows下在后面可以直接跳到后面看): 一.线程基本概念 ...前面我们讲过多进程服务器,但我们知道它开销很大,因此我们才引入线程,...线程的创建和上下文切换开销更小且速度...
  • DirectX12(D3D12)基础教程(六)——多线程渲染

    千次阅读 多人点赞 2019-01-14 21:39:37
    2、为什么要多线程渲染 3、多线程 3.1、什么是线程 3.2、进程的主线程 3.3、线程的入口函数 3.4、创建线程 3.5、CreateThread示例 3.6、C/C++创建线程函数(VC版) 3.7、_beginthreadex示例 3.8、线程退出 ...
  • C++:多线程内存管理的思考

    千次阅读 2019-08-07 16:55:33
    用 C++ 写多线程算法,如果程序设计过程中完全不用 new、delete、malloc、free 等动态申请、释放内存,理论上讲,变量内存会完全分配在栈上。如果不用全局变量和指针引用,理论上讲,算法可以实现无锁设计。 但问题...
  • Matlab 多线程解决的一些办法

    万次阅读 多人点赞 2017-07-13 16:03:29
    Matlab 是不支持多线程的,貌似这是一个truth ,但是有些时候是非常需要Matlab能够多任务处理的。 怎么使得Matlab能支持多线程呢,貌似要实现这个有点困难,不过总有一些办法,使得我们在不同的情况下,处于不同的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,585
精华内容 4,234
关键字:

多线程调用dll冲突