none
VB.Net と VC++ でのメモリ共有 RRS feed

  • 質問

  • やりたいこと
      VB.NetとVCとで共有メモリを使う。VB.Net側が常に立ち上がっているためCreateFileMappingはVB.Net側で行いたい。

    環境
      VS2013 Comunity VB.Net、VC++

    上記環境でメモリの共有ができません。
    VC側はOpenFileMappingAとOpenFileMappingWの両方試しましたがだめでした。
    ネットでいろいろな情報をもとに試しているのですがよろしくお願いします。

    VB.Net側ソース抜粋(フォームロードにてCreateFileMappingを実行)
          Public Const PAGE_READWRITE As Integer = &H4S
          <DllImport("kernel32", entrypoint:="CreateFileMappingW")> _
          Public Function CreateFileMapping( _
                                          ByVal hFile As Integer, _
                                          ByRef lpFileMappigAttributes As SECURITY_ATTRIBUTES, _
                                          ByVal flProtect As Integer, _
                                          ByVal dwMaximumSizeHigh As Integer, _
                                          ByVal dwMaximumSizeLow As Integer, _
                                          ByVal lpName As String _
                                        ) As System.IntPtr
          End Function

        Private Sub Form1_Load
                ' セキュリティ構造をセット
                Dim SecurityAttribute As SECURITY_ATTRIBUTES
                With SecurityAttribute
                    .bInheritHandle = 0 '' ハンドルの継承しない。
                    .lpSecurityDescriptor = 0 '' セキュリティをセットしない。
                    .nLength = Len(SecurityAttribute) '' 構造体のサイズを設定
                End With

                ' マッピングオブジェクト作成(.Net側がサーバのためこちら側で作成)
                hMap = CreateFileMapping(&HFFFFFFFF, SecurityAttribute, PAGE_READWRITE, 0, 256, "TEST")
        End Sub


    VC++側ソース抜粋
        BOOL CMFCSampleDlg::OnInitDialog()
        {
         CDialogEx::OnInitDialog();
               ・・・中略・・・
         // 共有メモリOpen
         HANDLE hMap = ::OpenFileMappingA(FILE_MAP_READ, true, "TEST");  // hMapが0x000000になる

         return TRUE;  // フォーカスをコントロールに設定した場合を除き、TRUE を返します。
            }

    2015年6月2日 8:05

回答

  • また、今回のようにAとWの両方があるAPIは、今回のような「既定」の動作(マルチバイトに自動変換してAを使用)になるのでしょうか?
    (それともAPI毎にことなる?)

    質問の趣旨からはずいぶん離れてしまいましたがついでですので答えます。またもや大きな勘違いをされています。

    CreateFileMappingWであれCreateFileMappingAであれ、呼び出された先(OS内部)では更に文字コード変換しています。だからこそCreateFileA("a.txt",...)でもCreateFileW(L"a.txt",...)どちらでもファイルを開くことができるわけです。ですので、VB.NET側がCreateFileMappingA(..., "TEST")を呼び出し、VC++側がOpenFileMappingW(..., L"TEST")を呼び出したとしても成功します。

    パターン1,2にとらわれず任意の組み合わせができます、が答えになります。

    その上で、Windows NT系列はOS内部はUnicodeで動作しているので少なくともVB.NETではcharset:=charset.unicodeを使用して文字コード変換のかからない呼び出しをすると効率がいいです。
    # ちなみにWindows 9x系列はANSIで動作していました。

    2015年6月4日 1:53
  • Hongliangさんの指摘通りで特に.NET 4以降であればSystem.IO.MemoryMappedFiles名前空間を使うべきです。それ以外に1点、

    <DllImport("kernel32", entrypoint:="CreateFileMappingW")>

    と宣言されている部分が気になりました。DllImportAttribute.CharSetの既定値はCharSet.Ansiですので、UnicodeバージョンであるCreateFileMappingW()に対してANSI文字列が渡されます。entrypointを明示するよりも自動に任せて、それよりはcharsetを明示すべきです。

    # CreateFileMapping()の第1引数はHANDLE型で、64bitの場合は64bit値となるのでInteger型よりはIntPtr型にすべきとかいろいろ…。
    2015年6月2日 8:51
  • 1.CreateFileMappingの返値は問題なし(それらしい数値)でした
    2.AnyCPUでしたがx86固定に変更しました
    3.実機環境の制約で.Net FrameWork 3.5のため新しいSystem.IO.MemoryMappedFilesが使えませんでした
     (今は4.5環境で開発してしまっていますが後々3.5にする予定....単純に変更したらエラーになったので別途確認中です)
    4.VC側のOpenFileMappingAの戻り値が0になってしまっていました。

    佐祐理さんのご指摘にあったCreateFileMappingの宣言を見直すことでVC側のOpenFileMappingAの戻り値が正常に取れました。

    ありがとうございました。

    2015年6月2日 15:23

すべての返信

    1. VB側でCreateFileMappingの返値を確認していますか?
    2. VBのターゲットプラットフォームはx64/x86/AnyCPUいずれでしょうか?
    3. VB側はSystem.IO.MemoryMappedFiles名前空間下のクラスを使えば、アンマネージDLLを直接使う必要はなくなりますが。
    4. 「できません」「だめでした」具体的にどこでNGが出ているのでしょうか。ステップ実行でそれぞれの変数の値の確認はされていますか?

    とりあえずこの辺を。

    2015年6月2日 8:29
  • Hongliangさんの指摘通りで特に.NET 4以降であればSystem.IO.MemoryMappedFiles名前空間を使うべきです。それ以外に1点、

    <DllImport("kernel32", entrypoint:="CreateFileMappingW")>

    と宣言されている部分が気になりました。DllImportAttribute.CharSetの既定値はCharSet.Ansiですので、UnicodeバージョンであるCreateFileMappingW()に対してANSI文字列が渡されます。entrypointを明示するよりも自動に任せて、それよりはcharsetを明示すべきです。

    # CreateFileMapping()の第1引数はHANDLE型で、64bitの場合は64bit値となるのでInteger型よりはIntPtr型にすべきとかいろいろ…。
    2015年6月2日 8:51
  • 1.CreateFileMappingの返値は問題なし(それらしい数値)でした
    2.AnyCPUでしたがx86固定に変更しました
    3.実機環境の制約で.Net FrameWork 3.5のため新しいSystem.IO.MemoryMappedFilesが使えませんでした
     (今は4.5環境で開発してしまっていますが後々3.5にする予定....単純に変更したらエラーになったので別途確認中です)
    4.VC側のOpenFileMappingAの戻り値が0になってしまっていました。

    佐祐理さんのご指摘にあったCreateFileMappingの宣言を見直すことでVC側のOpenFileMappingAの戻り値が正常に取れました。

    ありがとうございました。

    2015年6月2日 15:23
  • ご指摘ありがとうございます。解決できました。

    <DllImport("kernel32", entrypoint:="CreateFileMappingW")>
    を以下に変更することでVC側でも見れるようになりました。
    <DllImport("kernel32", entrypoint:="CreateFileMapping")>

    VB側がマルチバイト、VC側がUNICODEという意識はあったので、
    VC側はVB側に合わせてOpenFileMappingAを使っていたのですが、
    なぜかVB側でCreateFileMappingWをつかってしまっていました(なんでこんなことしたのか謎ですが)。

    ありがとうございました。

    2015年6月2日 15:29
  • 解決したようですが、誤解があるため指摘します。

    entrypointのデフォルト値はメソッド名ですので、メソッド名をCreateFileMappingとするのであればあえて宣言する必要はありません。

    またVBはマルチバイトではありません。正確にはVB6まではマルチバイトでしたがVB.NET以降はUnicodeを使用しています。その上でDllImportした関数の呼び出しの際には(既定では)マルチバイトに変換してCreateFileMappingAを呼び出します。charset:=charset.unicodeと宣言するとUnicodeのままCreateFileMappingWを呼び出すようになります。

    2015年6月3日 0:33
  • 再度のご指摘ありがとうございます。

    これを知らなかったです。
    「(既定では)マルチバイトに変換してCreateFileMappingAを呼び出します」

    以下のように修正し動作は問題ありませんでした。

    VC側:
     OpenFileMappingを使用(内部ではOpenFileMappingWを使用)
     
    VB.Net側:
     宣言を以下に変更
       <DllImport("kernel32", CharSet:=CharSet.Unicode)> _
        Public Function CreateFileMapping( _
                                          ByVal hFile As System.IntPtr, _
                                          ByRef lpFileMappigAttributes As SECURITY_ATTRIBUTES, _
                                          ByVal flProtect As Integer, _
                                          ByVal dwMaximumSizeHigh As Integer, _
                                          ByVal dwMaximumSizeLow As Integer, _
                                          ByVal lpName As String _
                                        ) As System.IntPtr


    対応として以下の2通りあるという認識でよろしいでしょうか?

     パターン1:
       VC側:OpenFileMappingAを使用
       VB.Net側:CharSetはセットしないで既定の
            CreateFileMappingAが動作するようにする
     パターン2:
       VC側:OpenFileMappingWを使用
       VB.Net側:CharSetにUnicodeをセットして、
            CreateFileMappingWが動作するようにする

    どちらが良いということはないかと思いますが、
    パターン2の方がVC側のソースがスッキリするのでこちらを使いたいと思います。
    一般的にはどちらを使うのでしょうか?

    また、今回のようにAとWの両方があるAPIは、今回のような「既定」の動作(マルチバイトに自動変換してAを使用)になるのでしょうか?
    (それともAPI毎にことなる?)

    お手数ですがよろしくお願いいたします。

    2015年6月4日 1:18
  • また、今回のようにAとWの両方があるAPIは、今回のような「既定」の動作(マルチバイトに自動変換してAを使用)になるのでしょうか?
    (それともAPI毎にことなる?)

    質問の趣旨からはずいぶん離れてしまいましたがついでですので答えます。またもや大きな勘違いをされています。

    CreateFileMappingWであれCreateFileMappingAであれ、呼び出された先(OS内部)では更に文字コード変換しています。だからこそCreateFileA("a.txt",...)でもCreateFileW(L"a.txt",...)どちらでもファイルを開くことができるわけです。ですので、VB.NET側がCreateFileMappingA(..., "TEST")を呼び出し、VC++側がOpenFileMappingW(..., L"TEST")を呼び出したとしても成功します。

    パターン1,2にとらわれず任意の組み合わせができます、が答えになります。

    その上で、Windows NT系列はOS内部はUnicodeで動作しているので少なくともVB.NETではcharset:=charset.unicodeを使用して文字コード変換のかからない呼び出しをすると効率がいいです。
    # ちなみにWindows 9x系列はANSIで動作していました。

    2015年6月4日 1:53
  • 何度も申し訳ないです。

    一番最初のうまくいかなかったのはVB側でCharSetを指定しないでWを使っていたため、
    UNICODEからマルチバイトに自動変換された文字をWに渡していたので予期しない動作になっていた。

    VB.NetとVC側の両方ともAPIインタフェースに沿った正しくエンコードした引数を使えば
    最終的なOS内部での結果が同じになるので、
    どの組み合わせでもよいと理解しました(双方で合わせる必要があると思っていました)。

    わかっているようで全然わかっていない部分で勉強になりました。
    お手数をおかけいたしました。ありがとうございます。

    2015年6月4日 8:05