Можно ли объявить и использовать указатель на функцию без сигнатуры?
От: Klirik  
Дата: 23.11.13 10:07
Оценка:
Задача такая. Есть исходный код с примерно такой конструкцией:

...
#inlcude "mysql.h"
...
mysql_init ( &m_tMysqlDriver );
...



Захотелось отвязаться от явной связи с библиотекой и сделать динамическую загрузку. Для этого сделаны следующие изменения:


...
#include "mysql.h"
...
#if DL_MYSQL

typedef MYSQL *        STDCALL (*xmysql_init)(MYSQL *mysql);
... // тут ещё 14 подобных typedef для других нужных функций
...
class CMysql...
{
...
public:
    bool Init()... // грузит библиотеку; кладёт адреса нужных функций в члены-статики.
    static xmysql_init m_pmysql_init;
...
};

#define sph_mysql_init (*CMysql::m_pmysql_init)
...

xmysql_init CMysql::m_pmysql_init = NULL;
...

CMysql MysqlHoder;

bool InitDynamicMysql()
{
    return MysqlHoder.Init();
}

#else // !DL_MYSQL

#define sph_mysql_init mysql_init
#define InitDynamicMysql() (true)

#endif // DL_MYSQL

...
if (!InitDynamicMysql())
  ... // что-то пошло не так, ругаемся и вылетаем;
 
sph_mysql_init ( &m_tMysqlDriver );
...


В общем — ничего волшебного; обычная рутинная загрузка. Макрос DL_MYSQL определяется configure-скриптом, и позволяет задействовать старый статический вариант без накладных расходов.
Но вот с динамическим вариантом... да, всё работает. Но смущает вот это:

...
typedef MYSQL *        STDCALL (*xmysql_init)(MYSQL *mysql);
...


В смысле — я залез в файл mysql.h и вытащил оттуда прототип mysql_init. Окружил имя (*x...) и получил указатель на функцию (а потом так же 14 раз для остальных использованных функций).
Выглядит коряво, нелогично и избыточно!
Возник вопрос — а нельзя ли как-нибудь объявить указатель на функцию, не цитируя явно её сигнатуру?
Т.е. у нас есть "чёрный ящик" — хедер, где объявлена функция.
Мы знаем имя нужной функции и хотим объявить указатель, в который можно положить адрес на эту функцию, и потом разыменовывать и использовать его как исходную функцию, НЕ цитируя явно её прототип из хедера.
Возможно ли такое?

Как один из вариантов смотрел на такой "велосипед":

#if DL_MYSQL
#define mysql_init (*xmysql_init)
... // другие подобные строки для других нужных функций
#include "mysql.h"
...



Получится, что нужный указатель будет объявлен прямо из хедера, за счёт подстановки стоящего перед ним #define. Но! Это уже не typedef а прямое объявление переменной. В рамках одного исходника работает, но если вынести в хедер и включить несколько раз — получаются множественные объявления.
Также не думал о возможных краевых эффектах (например, что будет, если функция используется в самом хедере, в inline-определении другой функции?).

Что подскажете, господа програмисты? Неужели такой "велосипед" — это единственный способ забороть ситуацию?
Re: Можно ли объявить и использовать указатель на функцию без сигнатуры?
От: Аноним  
Дата: 23.11.13 10:45
Оценка:
Здравствуйте, Klirik, Вы писали:

K>Что подскажете, господа програмисты? Неужели такой "велосипед" — это единственный способ забороть ситуацию?

Если цель — сделать наличие dll-ки опциональным, то проще всего воспользоваться механизмом Delay Loaded DLLs.
Т.е. не надо будет руками грузить указатели — загрзука DLL будет проведена только при первом обращении к функции из хэдера, а не старте приложения.
Естественно проверку наличия DLL (InitDynamicMysql) нужно будет оставить и проводить до первого обращения к функциям.
Re[2]: Можно ли объявить и использовать указатель на функцию без сигнатуры?
От: Klirik  
Дата: 23.11.13 11:22
Оценка:
Здравствуйте, Аноним, Вы писали:
А>Если цель — сделать наличие dll-ки опциональным, то проще всего воспользоваться механизмом Delay Loaded DLLs.

Примерно так. Только в моём случае чаще .so чем .dll (проект кросс-платформенный, и под никсы собирается гораздо чаще, чем под винду). И механизм особенно актуален именно в никсовом случае. MSDN в этом случае, увы, бесполезна.
Re: Можно ли объявить и использовать указатель на функцию без сигнатуры?
От: Alexander G Украина  
Дата: 23.11.13 11:56
Оценка:
Здравствуйте, Klirik, Вы писали:

K>Возник вопрос — а нельзя ли как-нибудь объявить указатель на функцию, не цитируя явно её сигнатуру?


decltype в C++ 11 позволяет объявить что угодно, не цитируя явно тип, включая и указатель на функцию.


#include "stdio.h"

// из внешнего заголовка
int LibraryFunction(int a, int b);

// типа GetProcAddress
// (использую такой AnyFunction вместо void* чтобы скомпилирвалось без ворнингов в онлайн компиляторе)
typedef void(*AnyFunction)();
AnyFunction GetProcAddress(const char* );

int main()
{
  typedef decltype(LibraryFunction) * LibraryFunctionPtr; // вот оно

  LibraryFunctionPtr libraryFunction;

  libraryFunction = reinterpret_cast<LibraryFunctionPtr>(GetProcAddress("LibraryFunction"));

  libraryFunction(2, 5);
}

AnyFunction GetProcAddress(const char* );

int LibraryFunctionImpl(int a, int b)
{
  printf("hello hello %d %d\n", a, b);
  return 0;
}

AnyFunction GetProcAddress(const char* )
{
   return reinterpret_cast<AnyFunction>(LibraryFunctionImpl);
}


Эмуляция decltype в до-С++11 — Boost.Typeof
Русский военный корабль идёт ко дну!
Re[3]: Можно ли объявить и использовать указатель на функцию без сигнатуры?
От: Аноним  
Дата: 24.11.13 18:42
Оценка:
Здравствуйте, Klirik, Вы писали:

K>Примерно так. Только в моём случае чаще .so чем .dll (проект кросс-платформенный, и под никсы собирается гораздо чаще, чем под винду).

упс про никсы ничего скзать не могу но вы погуглите на эту тему, скорее всего есть что-то похожее — у меня вот что нашлось за 15 секунд.
Re: Можно ли объявить и использовать указатель на функцию без сигнатуры?
От: Кодт Россия  
Дата: 25.11.13 07:42
Оценка:
Здравствуйте, Klirik, Вы писали:

K>Мы знаем имя нужной функции и хотим объявить указатель, в который можно положить адрес на эту функцию, и потом разыменовывать и использовать его как исходную функцию, НЕ цитируя явно её прототип из хедера.

K>Возможно ли такое?

Можно похимичить с .lib-файлами.
Т.е. сделать библиотеку, реализующую интерфейс mysql, внутри которой будут санки, полностью подконтрольные тебе, а не загрузчику.
#include <mysql.h>

decltype(mysql_init)* g_mysql_init = NULL; // или инициализировать указателем на код-ловушку

MYSQL* mysql_init(MYSQL* p)
{
  return g_mysql_init(p);
}

......

HMODULE g_dll;

void load_mysql()
{
  g_dll = LoadLibrary("mysql.dll");
  g_mysql_init = (decltype(g_mysql_init)) GetProcAddress(g_dll, "mysql_init");
  .....
}

И линковать именно её, а не оригинальную libmysql.lib.
Перекуём баги на фичи!
Re[2]: Можно ли объявить и использовать указатель на функцию без сигнатуры?
От: Klirik  
Дата: 25.11.13 10:10
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Можно похимичить с .lib-файлами.

К>Т.е. сделать библиотеку, реализующую интерфейс mysql, внутри которой будут санки, полностью подконтрольные тебе, а не загрузчику.

Да, про такое тоже думал. Вся проблема в том, что оно всё как-то нетривиально и многословно. По сути получится вместо дублирования из хедера объявления нужных функций (с указателем вместо имени) писать полностью свой "велосипед", что примерно то же самое.

Хочу найти способ сделать это кратко и красиво . К тому же там не только mysql, а ещё несколько подобных "толстых до зависимостей" либ. И весь смысл фичи — сделать так, чтобы пакет при установке не тянул за собой как зависимости половину из известных либ.

Пока что ничего проще подмены объявления в хедере через предшествующий #define не получилось.
Re[3]: Можно ли объявить и использовать указатель на функцию без сигнатуры?
От: Кодт Россия  
Дата: 25.11.13 10:26
Оценка:
Здравствуйте, Klirik, Вы писали:

K>Хочу найти способ сделать это кратко и красиво . К тому же там не только mysql, а ещё несколько подобных "толстых до зависимостей" либ. И весь смысл фичи — сделать так, чтобы пакет при установке не тянул за собой как зависимости половину из известных либ.


Хочешь скачивать-устанавливать зависимости при первом обращении? По-моему, это плохая идея.
Даже микрософтовский инсталлятор, который такую фичу поддерживает (я уж не знаю, как именно это реализовано, но видимо, на очень высоком уровне), — больше бесит, чем помогает. Захотел формулу вставить, а вместо этого выпрыгивает мастер установки и начинает мозг канифолить...
Так что отдайся менеджерам пакетов (под виндами — соответствующим редискам), пусть зависимости единожды встанут в систему, и всё.
Перекуём баги на фичи!
Re[3]: Можно ли объявить и использовать указатель на функцию без сигнатуры?
От: uzhas Ниоткуда  
Дата: 25.11.13 10:53
Оценка:
Здравствуйте, Klirik, Вы писали:

K>Хочу найти способ сделать это кратко и красиво . К тому же там не только mysql, а ещё несколько подобных "толстых до зависимостей" либ. И весь смысл фичи — сделать так, чтобы пакет при установке не тянул за собой как зависимости половину из известных либ.


не уверен, что поможет, но все же:
http://www.codeproject.com/Articles/70302/Redirecting-functions-in-shared-ELF-libraries
для винды вам уже предложили windows way — delay load механизм + хук

по поводу копирования сигнатур: чем не выход? можно кодогенератором+парсером (на любом скриптовом языке) всех их вытащить из .h и нагенерить в другие .h
Re[4]: Можно ли объявить и использовать указатель на функцию без сигнатуры?
От: Klirik  
Дата: 25.11.13 11:19
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Хочешь скачивать-устанавливать зависимости при первом обращении? По-моему, это плохая идея.


Не, идея как раз противоположная. Благополучно "взлетать" вне зависимости от наличия конкретных либ, работа с которыми не востребована в конкретном юзкейсе. И при попытке обращения давать вразумительный отлуп на тему "а поставь-ка сперва пакет mysql-client. И попробуй ещё раз". Если юзеру это нафиг не сдалось (вообще он собирался заюзать unixodbc, просто случайно в конфиг затесался кусок про mysql) — он это прочитает и сделает правильно.
Re[5]: Можно ли объявить и использовать указатель на функцию без сигнатуры?
От: Кодт Россия  
Дата: 25.11.13 12:20
Оценка:
Здравствуйте, Klirik, Вы писали:

K>Не, идея как раз противоположная. Благополучно "взлетать" вне зависимости от наличия конкретных либ, работа с которыми не востребована в конкретном юзкейсе. И при попытке обращения давать вразумительный отлуп на тему "а поставь-ка сперва пакет mysql-client. И попробуй ещё раз". Если юзеру это нафиг не сдалось (вообще он собирался заюзать unixodbc, просто случайно в конфиг затесался кусок про mysql) — он это прочитает и сделает правильно.


Тогда — устроить маленький управляемый dll hell: положить библиотеку-заглушку в дальний угол, чтобы она проигрывала существующей реальной библиотеке, но на безрыбьи попадалась загрузчику, и при вызове её функций ругалась.
Перекуём баги на фичи!
Re: Можно ли объявить и использовать указатель на функцию без сигнатуры?
От: Klirik  
Дата: 30.11.13 13:19
Оценка:
Напишу, на чём остановились по итогу.

Во-первых:

K>Как один из вариантов смотрел на такой "велосипед":


K>
K>#if DL_MYSQL
K>#define mysql_init (*xmysql_init)
K>... // другие подобные строки для других нужных функций
K>#include "mysql.h"
K>...
K>


оказался не очень хорош. Во-первых — нужно чтобы хедер был только в одной единице трансляции. Иначе получаются множественные определения. Ради этого уже приходится делать довольно много подготовительной работы.
Во-вторых, posgresql с его немудрёными extern int FooFunction(...) в хедере тут же полностью нокаутировал "велосипед". Покуда когда extern int (*xFoofunction)(...), конечно, позволяет пользоваться xFooFunction в коде, но вот саму переменную, увы, уже не определяет.

Однако благодаря ссылке на Boost.Typeof (спасибо Alexander G!) я посмотрел, как сделано там и раскопал специфичное (для целевой платформы) расширение компилятора.

#if DL_UNIXODBC
#if defined(__INTEL_COMPILER) || defined(__ICL) || defined(__ICC) || defined(__ECC) || defined(__GNUC__)

// use non-standard compiler extension __typeof__
// it allow to declare pointer to the function without using it's declaration
typedef __typeof__ ( SQLFreeHandle ) *xSQLFreeHandle;
typedef __typeof__ ( SQLDisconnect ) *xSQLDisconnect;
... ещё куча объявлений
#else // compilers which are not known about __typeof__ support
// declarations below are directly copy-pasted from sql.h and sqlext.h,
// and then (*x...) is placed around the function names.
// In mostly cases this code will not be used, and the declarations
// from previous block will be used instead.
#warning Be sure that the unixodbc function signatures are the same \
as in sql.h and sqlext.h Correct the code below if this is not so.
typedef SQLRETURN  SQL_API (*xSQLFreeHandle)(SQLSMALLINT HandleType, SQLHANDLE Handle) //NOLINT
typedef SQLRETURN  SQL_API (*xSQLDisconnect)(SQLHDBC ConnectionHandle); //NOLINT
... ещё куча копипастов
#endif
#endif


Рассчёт на то, что макросы DL_ в результате работы configure-скрипта будут в ходу только на *nix системах, где практически везде gcc, который поддерживает __typeof__
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.