개발/WIN32-MFC

DLL 시리즈 #8 멀티스레드 세이프한 방법

-=HaeJuK=- 2024. 9. 12. 15:55
NONAME을 이용한 멀티스레드 세이프한 방법

NONAME을 이용한 멀티스레드 세이프한 방법

1. 멀티스레드 환경에서 발생할 수 있는 문제

  • 동시 접근 문제: 여러 스레드가 동일한 자원에 동시에 접근할 경우, 데이터 불일치 또는 충돌이 발생할 수 있습니다.
  • 메모리 관리 문제: 객체의 생성과 해제를 DLL 내부에서 처리하지 않으면 메모리 누수나 충돌이 발생할 수 있습니다.
  • 전역 변수 사용: 전역 변수에 여러 스레드가 접근할 경우 동기화가 필요합니다.

2. 스레드 안전성을 확보하는 방법

  • 객체의 독립성 유지: 각 스레드가 독립적으로 객체를 사용하고, 공유 자원에 접근할 때는 동기화를 적용합니다.
  • 동기화 메커니즘 사용: mutex, critical section 등 동기화 메커니즘을 사용하여 스레드 간 충돌을 방지합니다.
  • DLL 내부에서 객체 관리: 객체의 생성과 해제를 DLL 내부에서 처리하여 일관된 메모리 관리가 가능합니다.

3. 예제 코드

1) DLL 내 클래스 정의 및 스레드 안전성 확보

먼저, DLL 내부에서 클래스를 정의하고, mutex를 사용하여 스레드 안전성을 보장합니다.


// MyDLL.cpp
#include <windows.h>
#include <iostream>
#include <mutex>

// 전역 뮤텍스 선언 (스레드 동기화를 위한)
std::mutex g_mutex;

// 간단한 클래스 A 정의
class A {
public:
    A() {
        std::cout << "A 생성자 호출됨!" << std::endl;
    }
    ~A() {
        std::cout << "A 소멸자 호출됨!" << std::endl;
    }
    void DoSomething() {
        std::cout << "A::DoSomething 호출됨!" << std::endl;
    }
};

// 클래스 포인터 반환 함수
extern "C" __declspec(dllexport) A* CreateClassA() {
    std::lock_guard<std::mutex> lock(g_mutex);  // 스레드 동기화
    return new A();
}

// 클래스 포인터 해제 함수
extern "C" __declspec(dllexport) void DeleteClassA(A* pA) {
    std::lock_guard<std::mutex> lock(g_mutex);  // 스레드 동기화
    delete pA;
}

    

2) DEF 파일 설정 (NONAME 방식으로 export)

DLL의 DEF 파일을 사용하여 함수 이름 대신 Ordinal 값으로 함수들을 export합니다.


LIBRARY MyDLL
EXPORTS
    CreateClassA @1 NONAME
    DeleteClassA @2 NONAME

    

3) 멀티스레드 애플리케이션에서 DLL 호출

멀티스레드 환경에서 DLL의 클래스를 호출하고, 각 스레드가 안전하게 클래스를 사용한 후 해제하는 코드입니다.


// main.cpp
#include <windows.h>
#include <iostream>
#include <thread>

class A;
typedef A* (*CreateClassA)();
typedef void (*DeleteClassA)(A*);

// 스레드에서 실행될 함수
void threadFunction(CreateClassA createClassA, DeleteClassA deleteClassA) {
    A* pA = createClassA();  // 클래스 A의 인스턴스 생성
    if (pA) {
        pA->DoSomething();   // 스레드에서 클래스 A의 메서드 호출
        deleteClassA(pA);    // 클래스 A의 인스턴스 해제
    }
}

int main() {
    // DLL 로드
    HMODULE hModule = LoadLibrary("MyDLL.dll");
    if (!hModule) {
        std::cerr << "DLL 로드 실패" << std::endl;
        return 1;
    }

    // Ordinal 값으로 함수 가져오기
    CreateClassA createClassA = (CreateClassA)GetProcAddress(hModule, MAKEINTRESOURCE(1));  // @1
    DeleteClassA deleteClassA = (DeleteClassA)GetProcAddress(hModule, MAKEINTRESOURCE(2));  // @2

    if (createClassA && deleteClassA) {
        // 5개의 스레드를 생성하고 각각 클래스 A 인스턴스를 생성하고 사용
        std::thread threads[5];
        for (int i = 0; i < 5; ++i) {
            threads[i] = std::thread(threadFunction, createClassA, deleteClassA);
        }

        // 모든 스레드 종료 대기
        for (auto& th : threads) {
            th.join();
        }
    }

    // DLL 언로드
    FreeLibrary(hModule);
    return 0;
}

    

4. 보안 및 관리 측면

  • 메모리 관리: 객체의 생성과 해제를 DLL 내부에서 처리하여 메모리 충돌과 누수를 방지할 수 있습니다.
  • 스레드 안전성: 동기화 메커니즘을 사용하여 여러 스레드가 동시에 DLL 자원에 접근할 때도 안전하게 동작합니다.
  • 보안 강화: NONAME 기술을 사용하여 함수 이름 대신 Ordinal 값으로 접근함으로써 함수 이름을 숨기고 보안을 강화할 수 있습니다.

5. 결론

NONAME 기술을 사용하여 DLL에서 멀티스레드 환경에서도 안전하게 클래스를 생성하고 관리할 수 있습니다. 동기화 메커니즘을 통해 스레드 간 충돌을 방지하고, 메모리 관리를 DLL 내부에서 처리하여 메모리 누수를 방지합니다. 이 방법은 보안을 중요시하는 환경에서 효과적일 수 있습니다.

반응형