『【1/3】DLLを作成してみる』で作成したDLLを利用するアプリケーションを書いてみる。
繰り返しになりますが、想定しているのは以下のプラットフォーム(開発環境)です。
今回も、単一のC言語ソースで、複数アーキテクチャ向けのアプリケーションとしてビルドします。
DLLを利用する方法は、大きく分けて2種類あります。
リンカ実行時にDLLのシンボルを紐づけておく「動的リンク(ダイナミックリンク)」と、ビルド時にはシンボルは未解決のままにしておいて実行時にDLLを明示的にロードする「動的ロード(ダイナミックロード)」です。
前回は、「動的リンク」を使ったサンプルを作成しました。
今回は「動的ロード」を使ったアプリケーションを作成してみます。
なお、以前に作成した、DLLのファイル,ソースコードは、『../simple_dll』というディレクトリに置いてあるものとします。
【3/3】DLLを使ってみる(動的ロード版)
目次
ソースコード
[dllUser_DynamicLoad.c]
/****************************************************************//**
* \file dllUser_DynamicLoad.c
*
* \brief DLL(ダイナミックリンクライブラリ)を実行中にロードするサンプルアプリケーション
*
* - 文字コード・改行コード
* - 文字コードは UTF-8(BOM無し)
* - 改行コードは LF
*
* \author tomyama Sep 2022
* \version $Revision$
* \date $Date$
********************************************************************/
/********************************************************************/
/* include files */
/********************************************************************/#include <stdio.h>
// もしDLLのヘッダーファイルが無い場合は使用したい関数のプロトタイプを自前でtypedefする。
#include "simple_dll.h"#include "dllDynLoadFunc.h"
/********************************************************************/
/* defines */
/********************************************************************/#ifndef DLL_NAME
#define DLL_NAME "THE_LIBRARY_NAME_IS_DEFINED_BY_THE_ARGUMENT_OF_THE_COMPILER"
#endif
/********************************************************************/
/* types */
/********************************************************************/
/********************************************************************/
/* function prototype (static) */
/********************************************************************/
/********************************************************************/
/* static value */
/********************************************************************/
/********************************************************************/
/* globals variable */
/********************************************************************/
/********************************************************************/
/* function (global) */
/********************************************************************//********************************************//**
* \brief プログラムのエントリポイント。
*
* \param[in] argc 引数の数。
* \param[in] argv 引数ポインタの配列。
* \return プログラムの終了ステータスを返す。
* \retval 0 正常終了
* \retval 0以外 異常終了
* \author tomyama
* \sa -
* \callgraph
* \callergraph
************************************************/int main( void )
{
dll_handle hDll;
PFN_GETGREETINGMSG pFn_getGreetingMsg;//! - Open the shared object
hDll = dllLoadLibrary( DLL_NAME );
if( hDll == NULL ){
fprintf( stderr, "%s[%d]: %s(): dllLoadLibrary( %s ) = NULL\n", __FILE__, __LINE__, __func__, DLL_NAME );
return -1;
}//! - Resolve the symbol (method) from the object
pFn_getGreetingMsg = ( PFN_GETGREETINGMSG )dllGetSymbol( hDll, "getGreetingMsg" );
if( pFn_getGreetingMsg == NULL ){
fprintf( stderr, "%s[%d]: %s(): dllGetSymbol( %s ) = NULL\n", __FILE__, __LINE__, __func__, "getGreetingMsg" );
dllFreeLibrary( hDll );
return -1;
}//! - Execute shared function
puts( pFn_getGreetingMsg() );//! - Close the object
if( dllFreeLibrary( hDll ) != 0 ){
fprintf( stderr, "%s[%d]: %s(): error: dllFreeLibrary()\n", __FILE__, __LINE__, __func__ );
return -1;
}return 0;
}
/********************************************************************/
/* function (static) */
/********************************************************************/
[dllDynLoadFunc.h]
/****************************************************************//**
* \file dllDynLoadFunc.h
*
* \brief DLL(ダイナミックリンクライブラリ)を動的にロードする関数群
*
* - 文字コード・改行コード
* - 文字コードは UTF-8(BOM無し)
* - 改行コードは LF
*
* \author tomyama Sep 2022
* \version $Revision$
* \date $Date$
********************************************************************/#ifndef _DLL_DYN_LOAD_FUNC_H_
#define _DLL_DYN_LOAD_FUNC_H_
/********************************************************************/
/* include files */
/********************************************************************/#include <stdio.h>
/********************************************************************/
/* defines */
/********************************************************************/
/********************************************************************/
/* types */
/********************************************************************/typedef void* dll_handle;
/********************************************************************/
/* function prototype (static) */
/********************************************************************/extern dll_handle dllLoadLibrary( const char *pDllName );
extern void *dllGetSymbol( dll_handle hDll, const char *pDllSym );
extern int dllFreeLibrary( dll_handle hDll );
/********************************************************************/
/* static value */
/********************************************************************/
/********************************************************************/
/* globals variable */
/********************************************************************/
#endif /* _DLL_DYN_LOAD_FUNC_H_ */
[dllDynLoadFunc.c]
/****************************************************************//**
* \file dllDynLoadFunc.c
*
* \brief DLL(ダイナミックリンクライブラリ)を動的にロードする関数群
*
* - 文字コード・改行コード
* - 文字コードは UTF-8(BOM無し)
* - 改行コードは LF
*
* \author tomyama Sep 2022
* \version $Revision$
* \date $Date$
********************************************************************/
/********************************************************************/
/* include files */
/********************************************************************/#ifdef _WIN32
#include <windows.h> /* LoadLibrary(), FreeLibrary(), GetProcAddress() */
#else
#include <dlfcn.h> /* dlopen(), dlerror(), dlsym(), dlclose() */
#endif#include "dllDynLoadFunc.h"
/********************************************************************/
/* defines */
/********************************************************************/#if 1
#define DBGP(fmt,...)
#else
#define DBGP(fmt,...) \
fprintf(stderr,"[DBG] %s[%d]: %s(): "fmt,__FILE__,__LINE__,__func__,##__VA_ARGS__)
#endif
/********************************************************************/
/* types */
/********************************************************************/
/********************************************************************/
/* function prototype (static) */
/********************************************************************/
/********************************************************************/
/* static value */
/********************************************************************/
/********************************************************************/
/* globals variable */
/********************************************************************/
/********************************************************************/
/* function (global) */
/********************************************************************//********************************************//**
* \brief 共有ライブラリをロードするラッパー関数
*
* \param[in] *pDllName ロードしたい共有ライブラリのファイル名
* \return ロードした共有ライブラリへのハンドルを返す。
* \retval NULL以外 正常終了
* \retval NULL 異常終了
* \author tomyama
* \sa -
* \callgraph
* \callergraph
************************************************/dll_handle dllLoadLibrary( const char *pDllName )
{
dll_handle hDll;#ifdef _WIN32
//! ライブラリの検索パスをコード中に書きたくないのでAddDirectory()は使わない事にした。
//! ライブラリの検索パスの追加は、環境変数「PATH」で行う。
// if( AddDllDirectory( L"C:\\Users\\user\\Documents\\lib" ) == 0 ){
// printf( "AddDllDirectory() error.\n" );
// return NULL;
// }
// // LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : デフォルトのパスとユーザー指定のパスが両方考慮される
// SetDefaultDllDirectories( LOAD_LIBRARY_SEARCH_DEFAULT_DIRS );hDll = ( void * )LoadLibraryA( pDllName );
if( hDll == NULL ){
DBGP( "LoadLibraryA( %s ) = NULL: GetLastError() = %ld\n", pDllName, GetLastError() );
return NULL;
}#else /* _WIN32 */
hDll = dlopen( pDllName, RTLD_LAZY );
if( hDll == NULL ){
DBGP( "dlopen( \"%s\" ) => %s\n", pDllName, dlerror() );
return NULL;
}#endif /* _WIN32 */
DBGP( "dll load ok: %s\n", pDllName );
return hDll;
}
/********************************************//**
* \brief 共有ライブラリからシンボルへのアドレスを取得するラッパー関数
*
* \param[in] hDll ロードした共有ライブラリのハンドル
* \param[in] *pDllSym 取得したいシンボルの名前
* \return 取得したシンボルへのアドレスを返す。
* \retval NULL以外 正常終了
* \retval NULL 異常終了
* \author tomyama
* \sa -
* \callgraph
* \callergraph
************************************************/void *dllGetSymbol( dll_handle hDll, const char *pDllSym )
{
void *pSym;#ifdef _WIN32
pSym = ( void * )GetProcAddress( ( HMODULE )hDll, pDllSym );
if ( pSym == NULL ) {
DBGP( "GetProcAddress( %s ): error\n", pDllSym );
return NULL;
}#else
char *dl_err;pSym = dlsym( hDll, pDllSym );
dl_err = dlerror();
if( dl_err != NULL ){
DBGP( "dlsym( %s ) => %s\n", pDllSym, dl_err );
return NULL;
}#endif
DBGP( "dll get symbol ok: %s\n", pDllSym );
return pSym;
}
/********************************************//**
* \brief 共有ライブラリを開放するラッパー関数
*
* \param[in] hDll ロードした共有ライブラリのハンドル
* \return 関数の終了ステータスを返す。
* \retval 0 正常終了
* \retval 0以外 異常終了
* \author tomyama
* \sa -
* \callgraph
* \callergraph
************************************************/int dllFreeLibrary( dll_handle hDll )
{
#ifdef _WIN32
if( FreeLibrary( ( HMODULE )hDll ) == 0 ){
DBGP( "FreeLibrary(): error\n" );
return -1;
}#else
if( dlclose( hDll ) != 0 ){
DBGP( "dlclose() => %s\n", dlerror() );
return -1;
}#endif
DBGP( "free library ok\n" );
return 0;
}
/********************************************************************/
/* function (static) */
/********************************************************************/
Windows環境向けにビルドして実行する手順
- スタートメニューからVisual Studioの『x86 Native Tools Command Prompt for VS 20xx』を起動する。
- 以下のバッチファイルを作成して実行する。
-
[build_and_run_DynLoad.bat]
set LIBDIR=../simple_dll
set CFLAGS=/nologo /source-charset:utf-8 /execution-charset:shift_jis /Wall /wd4464 /wd4668 /wd4710 /wd4820 /wd4996 /wd5045 /O2 /Ob0rem 動的ロード [ Dynamic Load ]
rem ビルド
cl.exe %CFLAGS% /Fo:dllDynLoadFunc_msvc.o /c dllDynLoadFunc.c
cl.exe %CFLAGS% /I%LIBDIR% /Fo:dllUser_DynamicLoad_msvc.o /c dllUser_DynamicLoad.c /DDLL_NAME=\"simple_dll_msvc.dll\"
link.exe /OUT:dllUser_DynamicLoad_msvc.exe dllDynLoadFunc_msvc.o dllUser_DynamicLoad_msvc.o
rem 実行
echo off
set PATH_ORG=%PATH%
set PATH=%LIBDIR%;%PATH%
echo on
dllUser_DynamicLoad_msvc.exe
echo off
set PATH=%PATH_ORG%
set PATH_ORG=
echo on
-
- 実行結果は以下のようになるはずです。赤字箇所がDLLの関数を呼び出した所です。
-
user_app_DynLoad>build_and_run_DynLoad.bat
user_app_DynLoad>set LIBDIR=../simple_dll
user_app_DynLoad>set CFLAGS=/nologo /source-charset:utf-8 /execution-charset:shift_jis /Wall /wd4464 /wd4668 /wd4710 /wd4820 /wd4996 /wd5045 /O2 /Ob0
user_app_DynLoad>rem 動的ロード [ Dynamic Load ]
user_app_DynLoad>rem ビルド
user_app_DynLoad>cl.exe /nologo /source-charset:utf-8 /execution-charset:shift_jis /Wall /wd4464 /wd4668 /wd4710 /wd4820 /wd4996 /wd5045 /O2 /Ob0 /Fo:dllDynLoadFunc_msvc.o /c dllDynLoadFunc.c
dllDynLoadFunc.cuser_app_DynLoad>cl.exe /nologo /source-charset:utf-8 /execution-charset:shift_jis /Wall /wd4464 /wd4668 /wd4710 /wd4820 /wd4996 /wd5045 /O2 /Ob0 /I../simple_dll /Fo:dllUser_DynamicLoad_msvc.o /c dllUser_DynamicLoad.c /DDLL_NAME=\"simple_dll_msvc.dll\"
dllUser_DynamicLoad.cuser_app_DynLoad>link.exe /OUT:dllUser_DynamicLoad_msvc.exe dllDynLoadFunc_msvc.o dllUser_DynamicLoad_msvc.o
Microsoft (R) Incremental Linker Version 14.32.31332.0
Copyright (C) Microsoft Corporation. All rights reserved.
user_app_DynLoad>rem 実行user_app_DynLoad>echo off
user_app_DynLoad>dllUser_DynamicLoad_msvc.exe
Hello, world!user_app_DynLoad>echo off
user_app_DynLoad>
-
Windows以外の環境向けにビルドして実行する手順
- 適当な端末エミュレータを起動する。
- 以下のスクリプトを作成して実行する。
-
[build_and_run_DynLoad.sh]
#!/bin/sh
LIBDIR=../simple_dll
CFLAGS="-Wall -O2"arch=`uname -mos | tr ' ./' '_'`
echo "$arch"dll_prefix="lib"
dll_ext="so"
if [ "`uname -o`" = "Cygwin" ]; then
dll_prefix=""
dll_ext="dll"
figcc $CFLAGS -o dllDynLoadFunc_${arch}.o -c dllDynLoadFunc.c
gcc $CFLAGS -I${LIBDIR} -o dllUser_DynamicLoad_${arch}.o -c dllUser_DynamicLoad.c -DDLL_NAME=\"${dll_prefix}simple_dll_${arch}.${dll_ext}\"
gcc -o dllUser_DynamicLoad_${arch}.exe dllDynLoadFunc_${arch}.o dllUser_DynamicLoad_${arch}.o -rdynamic -ldlif [ "`uname -o`" = "Cygwin" ]; then
PATH=${LIBDIR}:$PATH ./dllUser_DynamicLoad_${arch}.exe
else
LD_LIBRARY_PATH=${LIBDIR} ./dllUser_DynamicLoad_${arch}.exe
fi
-
- 実行結果は以下のようになるはずです。赤字箇所がDLLの関数を呼び出した所です。
解説
入力ファイル
プラットフォーム(開発環境) | ファイル |
---|---|
WindowsのDLLファイル(Visual Studio) | dllUser_DynamicLoad.c dllDynLoadFunc.h dllDynLoadFunc.c |
LinuxのSOファイル(gcc) | |
CygwinのDLLファイル(gcc) | |
Android(Termux)のSOファイル(clang) |
ビルドと実行の台本(スクリプト)
プラットフォーム(開発環境) | ファイル |
---|---|
WindowsのDLLファイル(Visual Studio) | build_and_run_DynLoad.bat |
LinuxのSOファイル(gcc) | build_and_run_DynLoad.sh |
CygwinのDLLファイル(gcc) | |
Android(Termux)のSOファイル(clang) |
- Cygwinの無い環境でもビルドできるように「Visual Studio」の場合は .bat ファイルにしている。その他の環境ではシェルスクリプト。
- clangの場合は、gccでは無くclangを呼び出す方がより厳密的だが、clangはgccに擬態してくれるように作られているので(少なくとも現時点では)、それに頼ってscriptingしている。
中間生成物: 『dllDynLoadFunc.c』のコンパイル | |
プラットフォーム(開発環境) | コマンド |
---|---|
WindowsのDLLファイル(Visual Studio) | cl.exe %CFLAGS% /Fo:dllDynLoadFunc_msvc.o /c dllDynLoadFunc.c |
LinuxのSOファイル(gcc) | gcc -Wall -O2 -o dllDynLoadFunc_${arch}.o -c dllDynLoadFunc.c |
CygwinのDLLファイル(gcc) | gcc -Wall -O2 -o dllDynLoadFunc_${arch}.o -c dllDynLoadFunc.c |
Android(Termux)のSOファイル(clang) | gcc -Wall -O2 -o dllDynLoadFunc_${arch}.o -c dllDynLoadFunc.c |
- 呼び出すコマンドやオプションスイッチに差異はあるものの、必要な要素(コマンドライン引数)は同じ。
中間生成物: 『dllUser_DynamicLoad.c』のコンパイル | |
プラットフォーム(開発環境) | コマンド |
---|---|
WindowsのDLLファイル(Visual Studio) | cl.exe %CFLAGS% /I../simple_dll /Fo:dllUser_DynamicLoad_msvc.o /c dllUser_DynamicLoad.c /DDLL_NAME=\"simple_dll_msvc.dll\" |
LinuxのSOファイル(gcc) | gcc -Wall -O2 -I../simple_dll -o dllUser_DynamicLoad_${arch}.o -c dllUser_DynamicLoad.c -DDLL_NAME=\"libsimple_dll_${arch}.so\" |
CygwinのDLLファイル(gcc) | gcc -Wall -O2 -I../simple_dll -o dllUser_DynamicLoad_${arch}.o -c dllUser_DynamicLoad.c -DDLL_NAME=\"simple_dll_${arch}.dll\" |
Android(Termux)のSOファイル(clang) | gcc -Wall -O2 -I../simple_dll -o dllUser_DynamicLoad_${arch}.o -c dllUser_DynamicLoad.c -DDLL_NAME=\"libsimple_dll_${arch}.so\" |
- 呼び出すコマンドやオプションスイッチに差異はあるものの、必要な要素(コマンドライン引数)は同じ。
- ソースコードの中で『simple_dll.h』をインクルードしているので、コンパイラのオプションスイッチ(/I, または -I)で、インクルードパスを追加している。
- オプションスイッチ(/D, もしくは -D)で、DLL_NAMEマクロを定義している。DLLのファイル名は『【1/4】DLLを作成してみる』の『解説』で説明した通り、プラットフォームにより異なる。
最終生成物(実行ファイル): リンカで実行ファイルを生成 | |
プラットフォーム(開発環境) | コマンド |
---|---|
WindowsのDLLファイル(Visual Studio) | link.exe /OUT:dllUser_DynamicLoad_msvc.exe dllUser_DynamicLoad_msvc.o |
LinuxのSOファイル(gcc) | gcc -o dllUser_DynamicLoad_${arch}.exe dllUser_DynamicLoad_${arch}.o -rdynamic -ldl |
CygwinのDLLファイル(gcc) | gcc -o dllUser_DynamicLoad_${arch}.exe dllUser_DynamicLoad_${arch}.o -rdynamic -ldl |
Android(Termux)のSOファイル(clang) | gcc -o dllUser_DynamicLoad_${arch}.exe dllUser_DynamicLoad_${arch}.o -rdynamic -ldl |
- Windows以外のプラットフォーム(gcc, clang)では、動的ロードを使用する事を示すオプションスイッチ(-rdynamic)を指定している。また、dl関数( dlopen(), dlerror(), dlsym(), dlclose() )のライブラリ(dl)を組み込んでいる。
- それに対して、Windows(Visual C)では、動的ロードの機能( LoadLibrary(), FreeLibrary(), GetProcAddress() )は他のWin32APIと同様に Kernel32.dll, Kernel32.lib に含まれているので、特に何も指定する必要は無い。
実行: 『dllUser_DynamicLoad_${arch}.exe』を実行 | |
プラットフォーム(開発環境) | コマンド |
---|---|
WindowsのDLLファイル(Visual Studio) | set PATH_ORG=%PATH% set PATH=../simple_dll;%PATH% dllUser_DynamicLoad_msvc.exe set PATH=%PATH_ORG% set PATH_ORG= |
LinuxのSOファイル(gcc) | LD_LIBRARY_PATH=../simple_dll ./dllUser_DynamicLoad_${arch}.exe |
CygwinのDLLファイル(gcc) | PATH=../simple_dll:$PATH ./dllUser_DynamicLoad_${arch}.exe |
Android(Termux)のSOファイル(clang) | LD_LIBRARY_PATH=../simple_dll ./dllUser_DynamicLoad_${arch}.exe |
- Windows, Cygwinは環境変数『PATH』を使ってDLLの場所を指示している。
- Cygwinの場合、動的ロードに限れば、環境変数『PATH』と『LD_LIBRARY_PATH』のどちらでも使える。しかし、動的リンクでは環境変数『PATH』しか効かない。CygwinのDLLは、DLLのフォーマット(PE),命名規則(先頭の lib が不要,拡張子は .dll)からWindowsと類似点が多いので、環境変数もWindowsと合わせて、動的リンク,動的ロード問わず『PATH』を使うようにした方がシンプルで覚えやすいと思います。
- Windows, Cygwin以外のプラットフォームでは環境変数『LD_LIBRARY_PATH』を使っている。
- Linux, Cygwin, Androidのコマンド欄について。このblogの制約で2行になっているように見えるかもしれないが、環境変数の設定と実行ファイルの実行は、1行で記述すること。シェルは、このように書く事で実行時のみ環境変数を変更してくれる。
- 【1/3】DLLを作成してみる
- 【2/3】DLLを使ってみる(動的リンク版)
- 【3/3】DLLを使ってみる(動的ロード版)