2022年5月18日水曜日

WIX で VCRuntime のインストール忘備録

そろそろ VCのRUNTIMEをVisual Studio 2019に移行しとかないと、コンパイルできないライブラリが増えてきたかな?と思いまして、2015 から 2019 に引き上げました。
WIXを使ったインストーラで、2019用のVC runtime のマージモジュールを探したが見つかりません。
ほぇ?と思って検索したらマージモジュールを使用したコンポーネントの再配布に書いてありました。

Visual Studio 2019 以降、Visual C++ 再頒布可能ファイルのマージ モジュールは非推奨です
Visual Studio 2019 以降、Visual C++ 再頒布可能ファイルのマージ モジュールは非推奨です
Visual Studio 2019 以降、Visual C++ 再頒布可能ファイルのマージ モジュールは非推奨です
ふぁ???
という事で、VC Runtime がインストールされているかどうかチェックが必要になりました
Wix per user installer to detect the Visual C++ 2015 Redistributableにチェック方法がありました。StackOverflowさまさまです
How to check if the Microsoft Visual C++ Runtime is installedにも詳細が記述されてましたが、こちらだとサービスパックが追加される毎に対応が必要そうで、ちょっと重たい。

という事で、
<Product ...>
  ...
vc runtime 2015 x86 のインストール状況プロパティ
  <!-- C++ 2015 -->
  <Property Id="CPPRUNTIME2015X86" Secure="yes">
    <RegistrySearch Id="mfc140x86_23026" Root="HKLM" Key="SOFTWARE\Classes\Installer\Dependencies\{74d0e5db-b326-4dae-a6b2-445b9de1836e}" Type="raw" />
    <RegistrySearch Id="mfc140x86_24215" Root="HKLM" Key="SOFTWARE\Classes\Installer\Dependencies\{e2803110-78b3-4664-a479-3611a381656a}" Type="raw" />
  </Property>
vc runtime 2017 x86 のインストール状況プロパティ
  <!-- C++ 2017 -->
  <Property Id="CPPRUNTIME2017X86" Secure="yes">
    <RegistrySearch Id="mfc1416x86" Root="HKCR" Key="Installer\Dependencies\VC,redist.x86,x86,14.16,bundle" Type="raw" />
  </Property>
vc runtime 2019 x86 のインストール状況プロパティ
  <!-- C++ 2019 -->
  <Property Id="CPPRUNTIME2019X86" Secure="yes">
    <?foreach CPPRUNTIMEVERSIONPREFIX in 21;22;23;24;25;26;27;28;29;30;31;32;33;34;35;36;37;38;39;40?>
      <RegistrySearch Id="mfc14$(var.CPPRUNTIMEVERSIONPREFIX)x86" Root="HKCR" Key="Installer\Dependencies\VC,redist.x86,x86,14.$(var.CPPRUNTIMEVERSIONPREFIX),bundle" Type="raw" />
    <?endforeach ?>
  </Property>
vc runtime 2019 x86 がインストールされていないと、インストールを中止する条件の追加
  <!-- インストール時にC++ 2019 Runtime(x86) がインストールされているかチェック -->
  <Condition Message="Microsoft Visual C++ 2019 (x86) Redistributable missing">
    <![CDATA[Installed Or CPPRUNTIME2019X86]]>
  </Condition>
vc runtime 2015 x64 のインストール状況プロパティ
  <!-- C++ 2015 -->
  <Property Id="CPPRUNTIME2015X64" Secure="yes">
    <RegistrySearch Id="mfc140x64_23026" Root="HKLM" Key="SOFTWARE\Classes\Installer\Dependencies\{e46eca4f-393b-40df-9f49-076faf788d83}" Type="raw" />
    <RegistrySearch Id="mfc140x64_24215" Root="HKLM" Key="SOFTWARE\Classes\Installer\Dependencies\{d992c12e-cab2-426f-bde3-fb8c53950b0d}" Type="raw" />
  </Property>
vc runtime 2017 x64 のインストール状況プロパティ
  <!-- C++ 2017 -->
  <Property Id="CPPRUNTIME2017X64" Secure="yes">
    <RegistrySearch Id="mfc1416x64" Root="HKCR" Key="Installer\Dependencies\VC,redist.x64,amd64,14.16,bundle" Type="raw" />
  </Property>
vc runtime 2019 x64 のインストール状況プロパティ
  <!-- C++ 2019 -->
  <Property Id="CPPRUNTIME2019X64" Secure="yes">
    <?foreach CPPRUNTIMEVERSIONPREFIX in 21;22;23;24;25;26;27;28;29;30;31;32;33;34;35;36;37;38;39;40?>
      <RegistrySearch Id="mfc14$(var.CPPRUNTIMEVERSIONPREFIX)x64" Root="HKCR" Key="Installer\Dependencies\VC,redist.x64,amd64,14.$(var.CPPRUNTIMEVERSIONPREFIX),bundle" Type="raw" />
    <?endforeach ?>
  </Property>
vc runtime 2019 x64 がインストールされていないと、インストールを中止する条件の追加
  <!-- インストール時にC++ 2019 Runtime(x64) がインストールされているかチェック -->
  <Condition Message="Microsoft Visual C++ 2019 (x64) Redistributable missing">
    <![CDATA[Installed Or CPPRUNTIME2019X64]]>
  </Condition>
vc runtime 2010 x64 のマージモジュールを追加
  <Directory Id="System64Folder">
   <!-- VC100 ATL Runtime -->
   <Merge Id="Microsoft_VC100_ATL_x64.msm" Language="0" DiskId="1" SourceFile="C:/Program Files (x86)/Common Files/Merge Modules/Microsoft_VC100_ATL_x64.msm" />
   <Merge Id="Microsoft_VC100_CRT_x64.msm" Language="0" DiskId="1" SourceFile="C:/Program Files (x86)/Common Files/Merge Modules/Microsoft_VC100_CRT_x64.msm" />
   <Merge Id="Microsoft_VC100_MFCLOC_x64.msm" Language="0" DiskId="1" SourceFile="C:/Program Files (x86)/Common Files/Merge Modules/Microsoft_VC100_MFCLOC_x64.msm" />
   <Merge Id="Microsoft_VC100_MFC_x64.msm" Language="0" DiskId="1" SourceFile="C:/Program Files (x86)/Common Files/Merge Modules/Microsoft_VC100_MFC_x64.msm" />
   <Merge Id="Microsoft_VC100_OpenMP_x64.msm" Language="0" DiskId="1" SourceFile="C:/Program Files (x86)/Common Files/Merge Modules/Microsoft_VC100_OpenMP_x64.msm" />
  </Directory>
vc runtime 2010 x86 のマージモジュールを追加
  <Directory Id="SystemFolder">
      <!-- VC100 ATL Runtime -->
      <Merge Id="Microsoft_VC100_ATL_x86.msm" Language="0" DiskId="1" SourceFile="C:/Program Files (x86)/Common Files/Merge Modules/Microsoft_VC100_ATL_x86.msm" />
      <Merge Id="Microsoft_VC100_CRT_x86.msm" Language="0" DiskId="1" SourceFile="C:/Program Files (x86)/Common Files/Merge Modules/Microsoft_VC100_CRT_x86.msm" />
      <Merge Id="Microsoft_VC100_MFCLOC_x86.msm" Language="0" DiskId="1" SourceFile="C:/Program Files (x86)/Common Files/Merge Modules/Microsoft_VC100_MFCLOC_x86.msm" />
      <Merge Id="Microsoft_VC100_MFC_x86.msm" Language="0" DiskId="1" SourceFile="C:/Program Files (x86)/Common Files/Merge Modules/Microsoft_VC100_MFC_x86.msm" />
      <Merge Id="Microsoft_VC100_OpenMP_x86.msm" Language="0" DiskId="1" SourceFile="C:/Program Files (x86)/Common Files/Merge Modules/Microsoft_VC100_OpenMP_x86.msm" />
  </Directory>
vc runtime 2010 x64 と x86 のマージモジュールを参照
  <Feature ...>
      <!-- VC10 runtime -->
      <MergeRef Id="Microsoft_VC100_ATL_x64.msm" />
      <MergeRef Id="Microsoft_VC100_CRT_x64.msm" />
      <MergeRef Id="Microsoft_VC100_MFCLOC_x64.msm" />
      <MergeRef Id="Microsoft_VC100_MFC_x64.msm" />
      <MergeRef Id="Microsoft_VC100_OpenMP_x64.msm" />
      <!-- VC10 runtime -->
      <MergeRef Id="Microsoft_VC100_ATL_x86.msm" />
      <MergeRef Id="Microsoft_VC100_CRT_x86.msm" />
      <MergeRef Id="Microsoft_VC100_MFCLOC_x86.msm" />
      <MergeRef Id="Microsoft_VC100_MFC_x86.msm" />
      <MergeRef Id="Microsoft_VC100_OpenMP_x86.msm" />
  </Feature>
  ...
</Product>
というように対応できそうです。

2022年5月13日金曜日

Windows DLL x86とx64で異なる関数名調整の備忘録

AESのDLLをビルドしていて、x64でビルドしたものは素直に利用できるのにx86でビルドしたものを利用しようとするとLNK2019: _aes_encrypt_key が見つからないというリンクエラーになってしまった。
test_lib.exe のソースを見ると
#ifdef  AES_MODES

    fn->fn_test_align = (g_talign*)GetProcAddress(h_dll, etad_name);
    fn->fn_mode_reset = (g_reset*)GetProcAddress(h_dll, eres_name);
    fn->fn_ecb_enc = (g_enc1*)GetProcAddress(h_dll, ecbe_name);
    fn->fn_ecb_dec = (g_dec1*)GetProcAddress(h_dll, ecbd_name);
    fn->fn_cbc_enc = (g_enc2*)GetProcAddress(h_dll, cbce_name);
    fn->fn_cbc_dec = (g_dec2*)GetProcAddress(h_dll, cbcd_name);
    fn->fn_cfb_enc = (g_enc3*)GetProcAddress(h_dll, cfbe_name);
    fn->fn_cfb_dec = (g_enc3*)GetProcAddress(h_dll, cfbd_name);
    fn->fn_ofb_cry = (g_enc3*)GetProcAddress(h_dll, ofb_name);
    fn->fn_ctr_cry = (g_enc4*)GetProcAddress(h_dll, ctr_name);

    if(   !fn->fn_mode_reset || !fn->fn_test_align
       || !fn->fn_ecb_enc || !fn->fn_ecb_dec
       || !fn->fn_cbc_enc || !fn->fn_cbc_dec
       || !fn->fn_cfb_enc || !fn->fn_cfb_dec
       || !fn->fn_ofb_cry || !fn->fn_ctr_cry )
       ok = 0;
#endif
と、DLLを LoadLibrary した後で、GetProcAddress により関数のアドレスを取得し、呼び出すという事をしていた。
で、名前の定義は aestst.h に記述されており、x86 と x64 で切り替えている。
#if defined( _WIN64 )

#define gt_name             "aes_init"
#define ek_name128          "aes_encrypt_key128"
#define ek_name192          "aes_encrypt_key192"
#define ek_name256          "aes_encrypt_key256"
#define ek_name             "aes_encrypt_key"
#define eb_name             "aes_encrypt"
#define dk_name128          "aes_decrypt_key128"
#define dk_name192          "aes_decrypt_key192"
#define dk_name256          "aes_decrypt_key256"
#define dk_name             "aes_decrypt_key"
#define db_name             "aes_decrypt"

#define etad_name           "aes_test_alignment_detection"
#define eres_name           "aes_mode_reset"
#define ecbe_name           "aes_ecb_encrypt"
#define ecbd_name           "aes_ecb_decrypt"
#define cbce_name           "aes_cbc_encrypt"
#define cbcd_name           "aes_cbc_decrypt"
#define cfbe_name           "aes_cfb_encrypt"
#define cfbd_name           "aes_cfb_decrypt"
#define ofb_name            "aes_ofb_crypt"
#define ctr_name            "aes_ctr_crypt"

#else

#define gt_name             "_aes_init@0"
#define ek_name128          "_aes_encrypt_key128@8"
#define ek_name192          "_aes_encrypt_key192@8"
#define ek_name256          "_aes_encrypt_key256@8"
#define ek_name             "_aes_encrypt_key@12"
#define eb_name             "_aes_encrypt@12"
#define dk_name128          "_aes_decrypt_key128@8"
#define dk_name192          "_aes_decrypt_key192@8"
#define dk_name256          "_aes_decrypt_key256@8"
#define dk_name             "_aes_decrypt_key@12"
#define db_name             "_aes_decrypt@12"

#define etad_name           "_aes_test_alignment_detection@4"
#define eres_name           "_aes_mode_reset@4"
#define ecbe_name           "_aes_ecb_encrypt@16"
#define ecbd_name           "_aes_ecb_decrypt@16"
#define cbce_name           "_aes_cbc_encrypt@20"
#define cbcd_name           "_aes_cbc_decrypt@20"
#define cfbe_name           "_aes_cfb_encrypt@20"
#define cfbd_name           "_aes_cfb_decrypt@20"
#define ofb_name            "_aes_ofb_crypt@20"
#define ctr_name            "_aes_ctr_crypt@24"

#endif
なるほど、だから x64 ではリンクエラーにならず、x86ではリンクエラーになったのだ。
x86 と x64 でマングリングの名前が違う事に自分も苛ついてはいました。
def ファイルに alias 指定できないの???と思って調べたら、ありました。
EXPORTS
alias=function_name
としてやれば良かったようです。
という事で、リンクエラーになった x86 用のビルドだけ以下のdefファイルを適用するようにしました。
LIBRARY		Aes

EXPORTS
   _aes_init=_aes_init@0
   _aes_encrypt_key128=_aes_encrypt_key128@8
   _aes_encrypt_key192=_aes_encrypt_key192@8
   _aes_encrypt_key256=_aes_encrypt_key256@8
   _aes_encrypt_key=_aes_encrypt_key@12
   _aes_encrypt=_aes_encrypt@12
   _aes_decrypt_key128=_aes_decrypt_key128@8
   _aes_decrypt_key192=_aes_decrypt_key192@8
   _aes_decrypt_key256=_aes_decrypt_key256@8
   _aes_decrypt_key=_aes_decrypt_key@12
   _aes_decrypt=_aes_decrypt@12

   _aes_test_alignment_detection=_aes_test_alignment_detection@4
   _aes_mode_reset=_aes_mode_reset@4
   _aes_ecb_encrypt=_aes_ecb_encrypt@16
   _aes_ecb_decrypt=_aes_ecb_decrypt@16
   _aes_cbc_encrypt=_aes_cbc_encrypt@20
   _aes_cbc_decrypt=_aes_cbc_decrypt@20
   _aes_cfb_encrypt=_aes_cfb_encrypt@20
   _aes_cfb_decrypt=_aes_cfb_decrypt@20
   _aes_ofb_crypt=_aes_ofb_crypt@20
   _aes_ctr_crypt=_aes_ctr_crypt@24
こうする事で、見事にLNK2019を回避する事ができました。
今まで、やり方がわからなくて、Delphi 用 DLL には序数で指定したりと、大変だったんですよね。

2022/06/29 追記:
bigtypes.h に
#ifndef RETURN_VALUES
#  define RETURN_VALUES
#  if defined( DLL_EXPORT )
#    if defined( _MSC_VER ) || defined ( __INTEL_COMPILER )
#      define VOID_RETURN    __declspec( dllexport ) void __stdcall
#      define INT_RETURN     __declspec( dllexport ) int  __stdcall
#    elif defined( __GNUC__ )
#      define VOID_RETURN    __declspec( __dllexport__ ) void
#      define INT_RETURN     __declspec( __dllexport__ ) int
#    else
#      error Use of the DLL is only available on the Microsoft, Intel and GCC compilers
#    endif
#  elif defined( DLL_IMPORT )
#    if defined( _MSC_VER ) || defined ( __INTEL_COMPILER )
#      define VOID_RETURN    __declspec( dllimport ) void __stdcall
#      define INT_RETURN     __declspec( dllimport ) int  __stdcall
#    elif defined( __GNUC__ )
#      define VOID_RETURN    __declspec( __dllimport__ ) void
#      define INT_RETURN     __declspec( __dllimport__ ) int
#    else
#      error Use of the DLL is only available on the Microsoft, Intel and GCC compilers
#    endif
#  elif defined( __WATCOMC__ )
#    define VOID_RETURN  void __cdecl
#    define INT_RETURN   int  __cdecl
#  else
#    define VOID_RETURN  void
#    define INT_RETURN   int
#  endif
#endif
と定義されていて、x64は呼び出し規約が異なるので問題なく動作するが、x86で利用する場合は利用する側で
DLL_IMPORT をプリプロセッサの定義に追加しないと、コールスタックが破壊されて、よくわからないエラーになる
尚、aes.dll を使った dll を書きたい場合は、DLL_EXPORT や DLL_IMPORT といった汎用的な定義で判別していると、定義がバッティングするので、AES_DLL_EXPORT と言った定義に変更した方が良いと思う。できれば、DLL_IMPORT は定義しなくても AES_DLL_EXPORT が定義されていなかったら、利用する側のコードだと判別できるので、このマクロの書き方は改良した方が良いかな?

もうひとつ、vsyasm の -f オプションが Win32 ではなく win32 しか認識しないので、
vsyasm.props ファイルを修正する
...
      <CommandLineTemplate Condition="'$(Platform)'=='Win32'">"$(YASM_PATH)"vsyasm.exe -Xvc -f win32 [AllOptions] [AdditionalOptions] [Inputs]</CommandLineTemplate>
      <CommandLineTemplate Condition="'$(Platform)'!='Win32'">"$(YASM_PATH)"vsyasm.exe -Xvc -f $(Platform) [AllOptions] [AdditionalOptions] [Inputs]</CommandLineTemplate>
...