ブログ

割とコンピュータよりの情報をお届けします。

TakeMe

Windowsで共有メモリの使用WindowsサービスとWindowsアプリケーション間のやり取り

WindowsでWindowsサービスとアプリケーションの間の共有メモリによるデータのやり取りを確認していた。

まずは簡単に,共有メモリの使い方を学習する。
参考にするページはこのページ
とりあえず,CreateFileMappingとMapViewOfFileを使用することでメモリの共有ができる。しかし,これができるのは同じユーザの場合。
Windowsサービスとユーザアプリケーション間のやり取りに苦労する。

そこで次の例を読み込む。
CreateFileMappingの2つ目の引数をNULLではなく設定するのだ。

ここまでできるとサービス側で読み書きできて,アプリケーション側では読み取り専用で開かせたい。

アプリケーション側

#include "stdafx.h"

int main()
{
    TCHAR name[] = _T("Global\\somename");
    DWORD dwSize = sizeof(int);

    HANDLE hSharedMemory = OpenFileMapping(
        FILE_MAP_READ,//FILE_MAP_ALL_ACCESS,
        FALSE,
        name
    );
    int* pMemory = (int*)MapViewOfFile(hSharedMemory, FILE_MAP_READ/*FILE_MAP_ALL_ACCESS*/, NULL, NULL, dwSize);

    for (int i = 0; i < 40; i++)
    {
        //(*pMemory)--;
        _tprintf_s(_T("memory: %d\n"), *pMemory);
        Sleep(500);
    }

    UnmapViewOfFile(pMemory);
    CloseHandle(hSharedMemory);

    return 0;
}

 

サービス側 試行錯誤してみました。

#include "stdafx.h"


int main()
{
    TCHAR name[] = _T("Global\\somename");
    DWORD dwSize = sizeof(int);

    SECURITY_DESCRIPTOR secDesc;
    SECURITY_ATTRIBUTES secAttr;
    InitializeSecurityDescriptor(&secDesc, SECURITY_DESCRIPTOR_REVISION);

    DWORD dwAclSize = 1024;
    SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_LOCAL_SID_AUTHORITY; // SECURITY_WORLD_SID_AUTHORITY (Everyone)でもよい
    PSID pSid = NULL;
    if (!AllocateAndInitializeSid(&SIDAuth, 1,
        SECURITY_WORLD_RID,
        0, 0, 0, 0, 0, 0, 0,
        &pSid))
    {
        return 1;
    }
    PACL pDacl = (PACL)HeapAlloc(GetProcessHeap(), 0, dwAclSize);
    if (pDacl == NULL) {
        return 1;
    }

    // 随意アクセス制御リストを初期化
    InitializeAcl(pDacl, dwAclSize, ACL_REVISION);

    // ACE(アクセス制御エントリ)をDACLに追加
    AddAccessAllowedAce(pDacl, ACL_REVISION, GENERIC_READ, pSid);
    AddAccessDeniedAce(pDacl, ACL_REVISION, GENERIC_WRITE, pSid);

    SetSecurityDescriptorDacl(&secDesc, TRUE, pDacl, FALSE);
    
    secAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    secAttr.bInheritHandle = TRUE;
    secAttr.lpSecurityDescriptor = &secDesc;

    HANDLE hSharedMemory = CreateFileMapping(INVALID_HANDLE_VALUE, &secAttr, PAGE_READWRITE, NULL, dwSize, name);

    LocalFree(pDacl);
    FreeSid(pSid);

    int* pMemory = (int*)MapViewOfFile(hSharedMemory, FILE_MAP_ALL_ACCESS, NULL, NULL, dwSize);

    *pMemory = 0; // Only the administrators process can handle it as fully accessible variable.
    for (int i = 0; i < 40; i++)
    {
        (*pMemory)++;
        _tprintf_s(_T("src_memory: %d\n"), *pMemory);
        Sleep(1000);
    }

    UnmapViewOfFile(pMemory);
    CloseHandle(hSharedMemory);
    return 0;
}

特に
AddAccessAllowedAce(pDacl, ACL_REVISION, GENERIC_READ, pSid);
AddAccessDeniedAce(pDacl, ACL_REVISION, GENERIC_WRITE, pSid);
の周辺を入れていました。

[参考]

  1. C++で複数プロセスから読み書き可能な共有メモリを作る
  2. 【共有メモリ】アクセスが拒否されました。

≫ 続きを読む

2018/08/16 コンピュータ   TakeMe

open62541のビルドをVisual Studio Express 2017 for Windows Desktopで行うx64版

先にopen62541をVisual Studio Express 2017 for Windows Desktopでビルドする記事を書いたが,x86用しか書いていなかったがx64用のビルドはどうするのか気になっていた。CMakeの-GオプションにWin64を追加するだけだった。

先のページに対して

mkdir build
cd build
cmake .. -G "Visual Studio 15 2017"

としていた部分をいかのように直すだけ。

mkdir build
cd build
cmake .. -G "Visual Studio 15 2017 Win64"

これ自体は巨大なライブラリではなくランタイムを除いてコンパイルするとx86用では1 MBを超えない。x64用は5割程大きく1 MBを超える。

DLLが作りたい場合には-Dオプションを付ける。(順番には注意: オプションは-Dがパスより先である必要があるらしい)

cmake -D BUILD_SHARED_LIBS=1 .. -G "Visual Studio 15 2017 Win64"

≫ 続きを読む

2018/08/12 コンピュータ   TakeMe

C#とCとのやり取りでCから配列変数を渡す例

C#からDllImportする場合について可変長の配列を渡しあうのがつかれた。
DLLから可変長文字列を渡す場合にはstringを渡せばよい。stringとchar*(ただし,最後に\0が付いている文字列に限る)の変換関係は支援がついている。
固定長の配列はDllImportの時の記述で引数型としてByte[]やInt32[]やDouble[] などを書いておけばよい(固定長といっても,呼び出し時に配列サイズが分かっていればよいだけで,配列自体は呼び出し前までにサイズが固定されればよい(決まればよい))。この方法は,.NET Compact Frameworkでも使用できる。ただし,長さが.NET側で分からなくて,呼び出された先のDLLの側で決まる場合には少しトリックが必要である。その場合,DLL側ではSAFEARRAYとして扱うとよさそう。

string型だけ特別簡単になっている。そのほかの型の長さがDLL側で決まる場合には対応が面倒になる。

例えば次のような関数をDLL側に用意することになる。

sample.cpp

// sample.cpp : アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"

// 簡単な呼び出し用サンプル
SAMPLE_API int __stdcall sample(int a, int b)
{
return a + b;
}

// 引数で文字列をDLLから返す例 割と簡単
SAMPLE_API int __stdcall sample2(char** a)
{
    char szSample[] = "TEST1";
    size_t ulSize = strlen(szSample) + sizeof(char);
    (*a) = (char*)::CoTaskMemAlloc(ulSize);
    strcpy_s((*a), ulSize, szSample);
    return 0;
}

// 戻り値でDLLから返す例 最も簡単
SAMPLE_API char* __stdcall sample3()
{
    char szSample[] = "TEST2";
    size_t ulSize = strlen(szSample) + sizeof(char);
    char* pszReturn = NULL;
    
    pszReturn = (char*)::CoTaskMemAlloc(ulSize);
    strcpy_s(pszReturn, ulSize, szSample);
    return pszReturn;
}

// 引数で配列をDLLから返す例 少し難しい
SAMPLE_API int __stdcall sample4(SAFEARRAY*& a)
{
    SAFEARRAY* pWkData = SafeArrayCreateVector(VT_I1, 0, 4);
    char* p;
    HRESULT hr = SafeArrayAccessData(pWkData, (void**)&p);
    p[0] = 1;
    p[1] = 2;
    p[2] = 3;
    p[2] = 4;
    hr = SafeArrayUnaccessData(pWkData);
    a = pWkData;
    return 0;
}

// 引数で配列をDLLに渡す例
SAMPLE_API int __stdcall sample5(SAFEARRAY*& a)
{
    SAFEARRAY* pWkData = a;
    char* p = 0;
    HRESULT hr = SafeArrayAccessData(pWkData, (void**)&p);
    p[0] = 3;
    p[1] = 2;
    p[2] = 1;
    hr = SafeArrayUnaccessData(pWkData);
    return 0;
}

呼び出し側については例えば

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace ConsoleApp1
{
    class Program
    {
        [DllImport("sample.dll")]
        [return: MarshalAs(UnmanagedType.LPStr)]
        public static extern string sample3();

        [DllImport("sample.dll")]
        public static extern int sample2([Out, MarshalAs(UnmanagedType.LPStr)] out string a);

        [DllImport("sample.dll")]
        public static extern int sample4([Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_I1)] out byte[] a);

        [DllImport("sample.dll")]
        public static extern int sample5([MarshalAs(UnmanagedType.SafeArray)] ref byte[] a);

        static void Main(string[] args)
        {
            string test = sample3();
            Console.WriteLine(test);

            string test2;
            sample2(out test2);
            Console.WriteLine(test2);

            byte[] test3;
            sample4(out test3);

            byte[] test4 = new byte[10];
            sample5(ref test4);

        }
    }
}

返された変数test3などはちゃんとガーベージコレクションの対象になっているようだ(メモリさえ十分にあれば多少のオーバーヘッドはあるにしろDLL側でSAFEARRAYを作ってC#に配列を渡した場合はC#の普通の配列のようにふるまうようだ。(もちろん,メモリ領域の実態はコピーされてそう見えているかもしれないが))。

ところで,sample4()の引数をSAFEARRAY*& aにしている部分は別にSAFEARRAY** aでもLPSAFEARRAY* aよい。ただし,最後に以下のようにするなど帳尻だけ合わせておく必要がある(ポインタ変数へのポインタ変数なのかポインタ変数へのアドレス(ポインタ)なのか)。

(*a) = pWkData;

≫ 続きを読む

2018/08/11 コンピュータ   TakeMe

Microsoft Visual Studio Express 2017 for Windows Desktop でDLL作成して使用

いろいろやってみて
Microsoft Visual Studio Express 2017 for Windows Desktop でDLL作成したらそれを使う際に注意があった。

具体的には「呼び出し規約」の設定である。

Visual C++で呼び合っている間にはあまり気にしていない。

標準的にdllを作ると呼び出し規約__cdeclなのかな
ただし,Windows APIなどは__stdcallなので,ここで作ったdllの関数は例えば.NETからDllImportで使う場合 呼び出し規約に合わせて設定を調整する必要がある。

__stdcallなら標準的に呼び出せるので__stdcallの方がよいかもしれない。

プロジェクトのプロパティから設定してもよいが,戻り値の型指定の後に__stdcallまたは__cdeclを書いた方がよいかもしれない。
プロジェクトのプロパティを設定するのは,別に間違いではない。ただ,自分で使う分には問題ないが,DLLを他の人が使うときにヘッダファイルに__stdcall/__cdeclを書いていないと呼び出し規約を特定する必要が出てしまう。

といってもx64では__stdcallなど無視されるらしい。

≫ 続きを読む

2018/08/08 コンピュータ   TakeMe

open62541のビルドをVisual Studio Express 2017 for Windows Desktopで行う

OPC-UAの続き。python-opcuaは簡単なアプリでもPythonをインストールする必要があることに問題を感じてopen62541を使ってみようかと思った。

準備としてはCMakeとPythonとVisual Studioをインストール。出来ればgitもあるとよいが必須ではない。この時,Visual StudioはExpressでよい。

また,CMakeはインストーラでインストールしてもよいし,zipファイルを解凍したものでもよい。そして,PythonもWinPythonで構わない。それから,open62541をGitHubからダウンロードしてくる。

CMakeなどをzipファイルから展開していた場合には,コマンドプロンプトを開き以下を実行する。Pathは適宜調整してください。(事前にWinPythonプロンプトからpip install sixを実行しておく必要があるかもしれない)

set "PATH=C:\WinPython-64bit-2.7.13.1Zero\python-2.7.13.amd64;C:\cmake-3.12.0-win32-x86\bin;%PATH%"
"C:\Program Files (x86)\Microsoft Visual Studio\2017\WDExpress\VC\Auxiliary\Build\vcvars32.bat"

そのダウンロードしたファイルのフォルダ(zipファイルをダウンロードしたらその展開したフォルダ)へ移動して以下を実行しておく

mkdir build
cd build
cmake .. -G "Visual Studio 15 2017"

これでソリューションファイルが完成する。なお,出来上がったソリューションファイルを開いてソリューションのビルドを実行すればDebug用のビルドが完了するはず。

Release用のビルドは日本語環境では警告が表示される。標準では,警告をエラーにしてしまうように設定されているのでビルドに失敗する。open62541-objectプロジェクトのプロパティで「C/C++」「警告をエラーとして扱う」を「いいえ(/WX-)」とすれば一応ビルドはできるようになる。

出来上がったopen62541.libファイルはws2_32.libにも依存しているので使用時には注意。

基本的には参考を確認しながら使っていくが,標準のポート番号4840を気にする場合には,例えば以下のようにすれば,4841にポートが切り替わる。

//UA_ServerConfig *config = UA_ServerConfig_new_default();
UA_ServerConfig *config = UA_ServerConfig_new_minimal(4841, NULL);;

≫ 続きを読む

2018/08/07 コンピュータ   TakeMe

Microsoft Visual Studio Express 2017 for Windows Desktop でDLL作成

Visual Studio Professionalならテンプレートがついていてすぐできるのだが,Expressではdll作成のテンプレートがないので自分で設定しないといけない。

(なお,いったんプロジェクトを作ってしまうとExpressでも使える。)

まずは,コンソールアプリを作るテンプレートを開く。

プロジェクトのプロパティを開き,「C/C++」「プリプロセッサ」「プリプロセッサの定義」を見ると「_CONSOLE」が入っているはずなので削除。

おそらく
_WINDOWS
_USRDLL
TEST_EXPORTS
を追加しないといけない。(TEST_EXPORTSは例えば)

「全般」の構成の種類はダイナミック ライブラリ(.dll)にしなければならない。

例えば,ヘッダファイルは以下のようにする。

#pragma once

#ifdef TEST_EXPORTS
#define TEST_API extern "C" __declspec(dllexport)
#else
#define TEST_API extern "C" __declspec(dllimport)
#endif

TEST_API int test(void);

このヘッダファイルは関数を定義するソースの先頭かstdafx.hで読ませておく必要がある。そして本体ソースは以下のようになる。

#include "stdafx.h"

TEST_API int test(void) {
return 1;
}

プロジェクトのプロパティの[C/C++][詳細設定]の呼び出し規約も注意する必要がある。

≫ 続きを読む

2018/08/06 コンピュータ   TakeMe

python-opcuaのnodeidのつけ方に苦労した

最近 自分で作ったアプリとほかのソフトのつなぎでOPC UAに対応を迫られる場面があって,node opcuaを使用したが,通信が問題なく続いている間は問題がないが,いったん不具合があったときに再接続に失敗する不具合が解消せずpython-opcuaに乗り換えることになった。ところが,サンプルを見ても"ns=2;i=3"などのnodeid?のつけ方がわからずに苦労した。

さんざん調べていたら,偶然
myvar2 = myobj.add_variable("ns=2;i=3", "takeme", True)
などとすると良いことが分かった。

server_minimal.pyのサンプルから以下のようにすれば変更できる。

    myvar = myobj.add_variable(idx, "MyVariable", 6.7)
から
    myvar = myobj.add_variable("ns=2;i=3", "MyVariable", 6.7)
へ変更

ただし,上の書式でコールして勝手に追加されるnodeidとの重複には十分に注意しなければならない。

先にadd_variable("ns=2;i=3", "MyVariable",  value)の書式で指定したidは後からadd_variable(idx, "MyVariable",  value)の書式で指定したときには重複しないように避けられる。逆の順番でコールした場合には重複は避けられない。"ns=2;i=xxx"で固定したいものを先にコールして固定しないといけない。

クライアントソフトによっては名前を見ずにnodeidで一意性を確認している場合があるので,サブスクライブの設定の時についていたnodeidは使い続ける限り,ずーっと維持されなくてはならない。サーバが再起動しても同じnodeidが付かないと別の変数と認識されてしまう。

≫ 続きを読む

2018/08/05 コンピュータ   TakeMe

CSSのtransitionが効かない項目もある

CSSのtransitionなるものを調べていたら一部の項目は効かないことが分かった。

CSSにいつの間にかtransitionなるものが追加されていた。

アニメーションが非常に簡単に実装できるようになった。

しかし,あらゆる項目がtransitionでアニメーションされるわけではないようだ。この例での,line-heightは遷移前と遷移後それぞれに値の定義がないとアニメーションにならない。rightとかcenterとかの遷移もアニメーションにならないみたい(瞬時に適用される)。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
}
.container {
  width: 100%;
  height: 100%;
  filter: drop-shadow(4px 4px 10px rgba(0,0,0,0.8));
}

.sample {
  transform: rotate(0deg);
  clip-path: circle(40% at center);
  background: skyblue;
  position:absolute;
  transition: 2s;
}

.sample720 {
  transform: rotate(720deg);
  clip-path: circle(40% at center);
  background: skyblue;
  position:absolute;
  transition: 2s;
}

.rotate0 {
  background: cornsilk;
  transform: rotate(0deg);
  transition: 2s;
  position:absolute;
  text-align:center;
  vertical-align:middle;
  line-height: 200px;
}

.rotate360 {
  background: skyblue;
  transform: rotate(360deg);
  transition: 2s;
  position:absolute;
}

</style>
</head>
<body>
<div class="container">
  <div class="sample" style="left:2vw;top:5vh;margin-top:6vh;width:200px;height:200px;text-align:center;vertical-align:middle;line-height: 200px;">TEST1</div>
  <div class="rotate0" style="left:20vw;top:10vh;width:200px;height:200px;">TEST2</div>
</div>
<script>
function Transition() {
  var element = document.querySelector("div.rotate0");
  if (element) {
    element.className = "rotate360";
  } else {
    element = document.querySelector("div.rotate360");
    element.className = "rotate0";
  }
  
  element = document.querySelector("div.sample");
  if (element) {
    element.className = "sample720";
  } else {
    element = document.querySelector("div.sample720");
    element.className = "sample";
  }
  
  return element;
}

setInterval(Transition, 2000);
</script>
</body>
</html>
TEST

≫ 続きを読む

2018/08/04 コンピュータ   TakeMe

plotly.jsを使ってみた

plotly.jsはオープンソースになっていると聞いて使ってみた。

ヒートマップで砂嵐を表現してみた。

この例はかなりCPUの負荷が大きい。

しかし,簡単に一応動かせるレベルで仕上がる。

(layoutのheightは指定しなければ勝手に決められる。)

heatmap以外にたくさん格好の良いチャートが作れるらしい。
heatmapでreact()を実行する場合には注意が必要。dataのオブジェクトの変更を見て更新しているようだ。
つまり,下の例では,data = [   {     zmin:0,     zmax:10,     z: z,      type: 'heatmap'   } ];としているがこの,zのオブジェクトの参照を変えずにzの要素だけを変更してreact('mydDiv', data)など実行した場合には表示の更新が後回しにされる可能性が高い。
変更を監視して必要があれば更新するのだが,どうも変更監視のコードの作り込みが簡易的に(よりコンピュータに負荷をかけないように)作られているようだ。

<!DOCTYPE html>
<html>
<head>
<script src="js/plotly.min.js"></script>
</head>
<body>
<div id="myDiv"></div>
<script>
var layout = {
  colorbar: {
    autorange: false,
    range: [0, 10]
  },
  height: 600
};

var z = [];
for (var i = 0; i < 250; i++) {
  var zz = [];
  for (var j = 0; j < 120; j++) {
    zz.push(10 * Math.random());
  }
  z.push(zz);
}
var data = [
  {
    zmin:0,
    zmax:10,
    z: z, 
    type: 'heatmap'
  }
];

Plotly.newPlot('myDiv', data, layout);

setInterval(function() {
  var z = [];
  for (var i = 0; i < 250; i++) {
    var zz = [];
    for (var j = 0; j < 120; j++) {
      zz.push(10 * Math.random());
    }
    z.push(zz);
  }
  var data = [
    {
      zmin:0,
      zmax:10,
      z: z,
      type: 'heatmap'
    }
  ];
  Plotly.react('myDiv', data, layout);
}, 500);

</script>
</body>
</html>

≫ 続きを読む

2018/08/03 コンピュータ   TakeMe

Node.jsでtcpサーバとクライアント作成の試行

前の記事でSocket.ioばかり使用していたが,とりあえずsocketでもよいのではないかと思って調べていたら,参考になりそうな記事があった。

参考をもとに,ほとんど構造を変えないように注意しながらサーバ側とクライアント側を作成させてもらった。

サーバ側 こっちは作りやすい。

var net = require('net');
var port = 3000;
var server = net.createServer(function (connection) {
  console.log('server: created');
  connection.on('data', function(data){
    connection.write('server: Repeating: ' + data);
  });
  connection.on('close', function(){
    console.log('server: client closed connection');
  });
  connection.on('error', function(){
    console.log('server: client made error');
  });
}).listen(port);
console.log('listening on port 3000');

クライアント側はもしNode.jsなら以下のようになるが,本来socket.connect()にはcallbackを渡すことができる。そこでwrite()を入れていたが,このプログラムはいったん接続が切れたら再接続を図ろうとしている。ところが,connect()に渡したcallbackは次に接続ができるようになった時に1回実行されるので,ずーっと接続ができない状態であるとき接続が始まると,一気に大量のパケットが流れ出す。非常に問題の多いコードになっているので一応コメントアウトしている。

最も目的はPythonなどとの接続であろうからこのクライアント側コードは使わなくてもよい。

var net = require('net');

var port = 3000;
var client = new net.Socket();

client.setEncoding('utf8');

var connect = function () {
  client.connect(port, 'localhost'/*, function() {
    console.log('client: connected to server');
    //client.write('connect');
  }*/);
}

connect();

process.stdin.resume();
process.stdin.on('data', function (data) {
  client.write(data);
});

client.on('data', function (data) {
  console.log('client: ' + data);
});

client.on('close', function () {
  console.log('client: connection is closed');
  setTimeout(connect, 6000);
});

client.on('error', function () {
  console.log('client: made error');
});

もっともこのコードでも,connect()の対応は不完全で接続が確立できないまま何度もconnect()の実行を繰り返すと次第にメモリ使用量が増えていく。

クライアント側でNode.jsで再接続を実装する場合には,node-net-reconnectなどの使用を検討された方がよいかもしれない。

≫ 続きを読む

2018/07/29 コンピュータ   TakeMe