tomyamaのブログ

日記・雑記。

シンプルなダイナミックリンクライブラリ(DLL)【3/3】

【1/3】DLLを作成してみる』で作成したDLLを利用するアプリケーションを書いてみる。

 

 

 

繰り返しになりますが、想定しているのは以下のプラットフォーム(開発環境)です。


今回も、単一のC言語ソースで、複数アーキテクチャ向けのアプリケーションとしてビルドします。

 

DLLを利用する方法は、大きく分けて2種類あります。

リンカ実行時にDLLのシンボルを紐づけておく「動的リンク(ダイナミックリンク)」と、ビルド時にはシンボルは未解決のままにしておいて実行時にDLLを明示的にロードする「動的ロード(ダイナミックロード)」です。

 

前回は、「動的リンク」を使ったサンプルを作成しました。

【2/3】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環境向けにビルドして実行する手順

  1. スタートメニューからVisual Studioの『x86 Native Tools Command Prompt for VS 20xx』を起動する。
  2. 以下のバッチファイルを作成して実行する。
    • [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 /Ob0

      rem 動的ロード [ 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

       

  3. 実行結果は以下のようになるはずです。赤字箇所が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.c

      user_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.c

      user_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以外の環境向けにビルドして実行する手順

  1. 適当な端末エミュレータを起動する。
  2. 以下のスクリプトを作成して実行する。
    • [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"
      fi

      gcc $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 -ldl

      if [ "`uname -o`" = "Cygwin" ]; then
        PATH=${LIBDIR}:$PATH       ./dllUser_DynamicLoad_${arch}.exe
      else
        LD_LIBRARY_PATH=${LIBDIR}  ./dllUser_DynamicLoad_${arch}.exe
      fi

       

  3. 実行結果は以下のようになるはずです。赤字箇所がDLLの関数を呼び出した所です。
    • $ ./build_and_run_DynLoad.sh
      CYGWIN_NT-10_0-19044_x86_64_Cygwin
      Hello, world!
      $

       

解説

入力ファイル
プラットフォーム(開発環境) ファイル
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行で記述すること。シェルは、このように書く事で実行時のみ環境変数を変更してくれる。