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.
-
client boots up
-
client loads PGP_Decrypt.dll and fetches function pointers. (DLL is from XE2 project; exports functions in exports.h)
-
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.