Why does gpgme_new(&ctx) freeze when called from DLL loaded by a program created with Embarcadero XE2 but doesn’t when run with VS24 unittest?

  Kiến thức lập trình

Created a gpgme decryption/encryption interface with headers and libs from a Gpg4Win installation using Visual Studio 2024 and exported C-style functions to load from within a DLL created using Embarcadero XE2 IDE and a very old compiler.

When I attach the VS24 debugger to my XE2 Unittest client I can step into my runtime loaded DLL which does a gpgme version check to initialize the gpgme library but when I try to create a gpg context my app freezes at gpgme_new(&ctx) and the call never returns.

What I did already:

  • Validating calling conventions.
  • Run a dependency walker.
  • Run process monitor.
  • Generated a verbose debug log from gpgme.
  • Unitstests run from VS24 that load the DLL directly work fine.

I will try to provide minimal code here:

We have an interface (exports.h) where we define the signatures. I will only show the Encrypt signature here.

#ifdef __DLL
    #define __EXPORT_TYPE 
#elif __BORLAND__
    #define __EXPORT_TYPE __declspec(dllimport)
#else
    #define __EXPORT_TYPE
#endif

const unsigned char PGP_KEY_FP_LEN              = 20;
const unsigned char PGP_KEY_ID_LEN              = 8;

typedef struct PGP_SUBKEY
{
   bool     bIsEncryptingKey;
   char     aSubKeyId[PGP_KEY_ID_LEN];
   char*    pPubKeyAlgorithm;
   int      iSubKeyLength;
}sKey;

struct PGP_KEY
{
   char*       pName;
   char*       pEMail;
   char        aFingerPrint[PGP_KEY_FP_LEN];
   char        aKeyID[PGP_KEY_ID_LEN];
   bool        bIsEncryptingKey;
   PGP_SUBKEY* pSubKey;
   char*       pPubKeyAlgorithm;
   int         iKeyLength;
   int         iSubKeyCount;
   int         iFingerprintLen;
   bool        bEnabled;
};

struct PGP_KEYRING
{
   PGP_KEY* pPubKey;
   int      iCount;
};

#ifndef SbpgpconstantsHPP
#pragma option push -b-
enum TSBPGPEncryptionType { etPublicKey, etPassphrase, etBoth };
#pragma option pop

#pragma option push -b-
enum TSBPGPProtectionType { ptNone, ptLow, ptNormal, ptHigh };
#pragma option pop
#endif

//=====================
//Function PGP_Encrypt
//=====================
#define PGP_ENCRYPT_PARAM_LIST                     
(  const void*          MySecretKey,               
   const unsigned int   MySecretKey_Length,        
   const void*          ForeignPublicKey,          
   const unsigned int   ForeignPublicKey_Length,   
   const char*          Passphrase,                
   const char*          ClearFileName,             
   const char*          EncryptedFileName,         
   const char*          SignatureFileName,         
   TSBPGPEncryptionType pgpEncryptionType,         
   TSBPGPProtectionType pgpProtection,             
   unsigned int         pgpAlgorithm,              
   unsigned char        bySignature,               
   PGP_KEYRING*         pUseKeys                   
)
extern "C" __EXPORT_TYPE char* __stdcall PGP_Encrypt PGP_ENCRYPT_PARAM_LIST;
typedef char*     (__stdcall* PGP_ENCRYPT_FUNC) PGP_ENCRYPT_PARAM_LIST;

The VS24 project DllEntryPoint.cpp looks as follows:

#define __DLL // Important. Defines __EXPORT_TYPE (from exports.h) to be dllexport.

#include <stdexcept>
#include <windows.h>
#include <iostream>

#include "exports.h"

#include "utils/fileUtil.h"
#include <gpgme.h>

#include "importresult.h"
#include "gpgManager.h"
#include "context.h"
#include "data.h"

using namespace PGP_Decrypt_DLL; // type definitions from exports.h

GpgME::GpgManager* gpgMngr = NULL;

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
        if (gpgMngr == NULL){
            gpgMngr = new GpgME::GpgManager(); // initializes the gpgme library (see below)
        }
    case DLL_THREAD_ATTACH: break;
    case DLL_THREAD_DETACH: break;
    case DLL_PROCESS_DETACH:
        if (gpgMngr) {
            delete gpgMngr;
            gpgMngr = NULL;
        }
        break;
    }
    return TRUE;
}

extern "C" __EXPORT_TYPE char* __stdcall PGP_Encrypt PGP_ENCRYPT_PARAM_LIST {
    GpgME::Context ctx = gpgMngr->createContext();
    ctx.setPassphrase(Passphrase);

    const GpgME::Data data(static_cast<const char*>(MySecretKey), MySecretKey_Length);

    GpgME::ImportResult imp = ctx.importKeys(data);
    std::shared_ptr<GpgME::Key> key = ctx.getKey(imp.import(0).fingerprint(), true);

    const GpgME::Data plain(ClearFileName);
    const GpgME::Data cipher(EncryptedFileName);
    const GpgME::Data sign(SignatureFileName);

    GpgME::GpgError result = ctx.encrypt(plain, cipher, key);

    if (result.IsError()) {
        return Error(result.what());
    }
    return Ok();
}

context.cpp | constructor of the Context class that freezes the program.

Context::Context() : ctx(NULL)
{
    gpgme_error_t err = gpgme_new(&ctx); // freezes program, never returns when run from the Embarcadero XE2 DLL.
    if (isError(err))
    {
        throw GpgError(err, "Failed to create GPGME context");
    }
}

Upon DllEntry (before the context is created) this method is called which returns a valid gpgme version and initializes the gpgme library:

GpgManager::GpgManager()
{
    const char* locale = "en_US.UTF-8"; // had this NULL before.

    // Initialize the locale environment.
    setlocale(LC_ALL, locale);

    // The installation type is used to prefer a certain GnuPG installation.
    // 0 is ignored.
    // 1 indicates an installation scheme as used by Gpg4win
    // 2 indicates an installation scheme as used by GnuPG Desktop
    gpgme_set_global_flag("inst-type", "1");
    gpgme_set_global_flag("debug", "9;gpgme_debug.log"); // 9 is very verbose.
    //gpgme_set_global_flag("disable-gpgconf", "1");
    //gpgme_set_global_flag("gpgconf-name", "GnuPG/gpgconf");
    //gpgme_set_global_flag("gpg-name", "GnuPG/gpg");
    
    const char* version = gpgme_check_version(NULL);
    if (!version) {
        throw std::runtime_error("Failed to initialize GPGME.");
    }
    std::cout << "GPGME version: " << version << std::endl;

    //gpg_error_t err = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
    //std::cout << "Engine: " << gpgme_get_dirinfo("gpg-name") << std::endl;

    gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, locale));
    // Portability to W32 systems
#ifdef LC_MESSAGES
    gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, locale));
#endif
}

The Embarcadero XE2 project has its own manager (below) which uses the same exports.h definitions and loads the VS24 gpgme implementation above:

GpgDllManager.h

#pragma once
#include <windows.h>
#include <string>
#include "exports.h"
namespace PGP_Decrypt_DLL
{
    class GpgDllManager
    {
        public:
            GpgDllManager();
            GpgDllManager(const std::string& p_dllName);
            ~GpgDllManager();

            bool load();
            bool load(const std::string &p_installDir);
            void unload();

            std::string getLastError() const;

            const char* encrypt PGP_ENCRYPT_PARAM_LIST;

        private:

            std::string m_lastError;

            std::string m_dllname;
            HINSTANCE   m_dllHandle;

            void resetDll();
            void* fetchMethodHandle(std::string p_funcName, bool& p_result);

            // External DLL Functions
            PGP_ENCRYPT_FUNC m_pgpEncrypt;
    };
}

GpgDllManager.cpp

#pragma once
#include "GpgDllManager.h"
namespace PGP_Decrypt_DLL
{
    GpgDllManager::GpgDllManager() :
        m_dllname("GNUPG_Decrypt.dll"),
        m_dllHandle(NULL),
        m_pgpEncrypt(NULL)
    {
        load();
    }

    GpgDllManager::GpgDllManager(const std::string &p_dllName) :
        m_dllname(p_dllName),
        m_dllHandle(NULL),
        m_pgpEncrypt(NULL)
    {
       load();
    }

    GpgDllManager::~GpgDllManager()
    {
        unload();
    }

    std::string GpgDllManager::getLastError() const
    {
        return m_lastError;
    }

    void GpgDllManager::unload()
    {
        resetDll();
    }

    void GpgDllManager::resetDll()
    {
        if (m_dllHandle != NULL) {
            FreeLibrary(m_dllHandle);
            m_dllHandle = NULL;
        }
    }

    bool GpgDllManager::load()
    {
        return load("");
    }

    bool GpgDllManager::load(const std::string& p_installDir)
    {
        bool l_result = true;

        // Sanitize directory separators
        std::string l_dllpath = p_installDir;
        if (!l_dllpath.empty())
        {
            if (l_dllpath[l_dllpath.size() - 1] != '\' && l_dllpath[l_dllpath.size() - 1] != '/')
            {
                l_dllpath += "\";
            }
        }
        l_dllpath += m_dllname;
        m_dllHandle = LoadLibraryA(l_dllpath.c_str());

        if (!m_dllHandle) {

            m_lastError += "Dll not found or permission denied: " + m_dllname + "rn";
            l_result = false;

        } else {
            // Load functions
            m_pgpEncrypt = reinterpret_cast<PGP_ENCRYPT_FUNC>(fetchMethodHandle("PGP_Encrypt", l_result));
        }

        if (!l_result) {
            unload();
        }
        return l_result;
    }

    void* GpgDllManager::fetchMethodHandle(std::string p_funcName, bool& p_result) {
        void* l_procAddress = ::GetProcAddress(m_dllHandle, p_funcName.c_str());
        if (l_procAddress == NULL) {
            l_procAddress = ::GetProcAddress(m_dllHandle, ("_" + p_funcName).c_str());
            if (l_procAddress == NULL) {
                m_lastError += "Function not found: " + p_funcName + "rn";
                p_result = false;
            }
        }
        return l_procAddress;
    }

    const char* GpgDllManager::encrypt PGP_ENCRYPT_PARAM_LIST {
        if (!m_dllHandle) {
            return "Dll not loaded.";
        }
        return m_pgpEncrypt(MySecretKey,
            MySecretKey_Length,
            ForeignPublicKey,
            ForeignPublicKey_Length,
            Passphrase,
            ClearFileName,
            EncryptedFileName,
            SignatureFileName,
            pgpEncryptionType,
            pgpProtection,
            pgpAlgorithm,
            bySignature,
            pUseKeys);
    }
}

The test client in Embarcadero uses the same manager but loads the Embarcadero XE2 DLL itself.

  1. client boots up

  2. client loads PGP_Decrypt.dll and fetches function pointers. (DLL is from XE2 project; exports functions in exports.h)

  3. PGP_Decrypt.dll loads GNUPG_Decrypt.dll and fetchs function pointers (VS24 project; exports function in exports.h)

The test client in VS24 just loads the GNUPG_Decrypt.dll and this way nothing freezes.

Theme wordpress giá rẻ Theme wordpress giá rẻ Thiết kế website

LEAVE A COMMENT