Cで作ったDLLから戻り値(文字列)の取得について

解決


off  2005-09-05 15:14:36  No: 92001  IP: [192.*.*.*]

はじめまして、offと申します。
早速ですが、質問です。

Cで作ったDLLからログインしているユーザ名を取得するPGを作っているのですがうまくユーザ名が取れないのです。

Cのソースは、
TEST_API CHAR* __stdcall UserName(void){
char  lpBuffer[256];
DWORD dwSize ;

dwSize = sizeof(lpBuffer) ;
memset( lpBuffer, '\0', dwSize ) ;
GetUserName(lpBuffer, &dwSize );
return lpBuffer ;
}

VBのソースは、
Private Declare Function UserName Lib "TEST.dll" () As Long
Private Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" (  ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long) 

Private Sub Command1_Click()
Dim p      As Long
Dim b(256) As Byte
Dim s      As String

p = UserName
Call MoveMemory(b(0), ByVal p, 256)

s = StrConv(b(), vbUnicode)
s = Left$(s, InStr(s, vbNullChar) - 1)

MsgBox "現在のユーザ:" & s & "です。"

です。

UserName からはアドレスらしき数値が戻ってきてるのですが、
そのあとのMoveMemoryを呼んでもbには何も入らないのです。

どうすれば、Cからcharの文字列を
VB側のString変数に渡せばよいのでしょうか?

どうか、よろしくお願いします。

編集 削除
9566  2005-09-05 15:22:20  No: 92002  IP: [192.*.*.*]

スタック上に確保されたメモリ領域はDLLを抜けると不定になってしまうんじゃ?

DLLないでGlobalAllocを使用してメモリを確保するか、VBから確保したメモリに
DLL内にて文字列を書き込むといった処理にしてみて下さい。

編集 削除
Blue  2005-09-05 15:39:03  No: 92003  IP: [192.*.*.*]

> スタック上に確保されたメモリ領域はDLLを抜けると不定になってしまうんじゃ?
その通りですね。
一番簡明なのはWinAPIのように、VB側であらかじめ文字列領域を確保して、
C側でその領域に設定する方法があります。

また、String変数をVARIANT*型で受取り、C側でSysAllocString関数で設定しする
方法があります。

いずれにせよ、戻り値でないほうがやりやすいと思います。
(戻り値は成功、失敗等の結果を示す値にしたほうがいいかと)

編集 削除
Blue  2005-09-05 16:03:01  No: 92004  IP: [192.*.*.*]

というか、
> GetUserName(lpBuffer, &dwSize );
をそのままVBから使えるようにすればいいのでは。
TEST_API void __stdcall GetUserName( LPCSTR lpUserName, DWORD dwSize )
・・・

Private Declare Sub GetUserName Lib "TEST.dll" (ByVal username As String, ByRef size As Integer)

Private Sub Command1_Click()
    Dim s As String
    Dim n As Integer

    n = 256
    s = String(n,vbNullChar)

    Call GetUserName(s,n)
    s = Left(s,InStr(s,vbNullChar)-1)
    MsgBox "現在のユーザ:" & s & "です。"
End Sub

編集 削除
Blue  2005-09-05 16:04:13  No: 92005  IP: [192.*.*.*]

> TEST_API void __stdcall GetUserName( LPCSTR lpUserName, DWORD dwSize )
TEST_API void __stdcall GetUserName( LPCSTR lpUserName, DWORD* dwSize )
第2引数はポインタですた。。。orz

編集 削除
off  2005-09-05 16:26:43  No: 92006  IP: [192.*.*.*]

どうもです。

Blueさんのように、
Cは、
TEST_API void __stdcall UserName(LPCSTR lpBuffer, DWORD* dwSize)
にかえ、

VBは、
Private Declare Function UserName2 Lib "ClientInfo.dll" 
(ByVal username As String, ByRef size As Integer) As Long
に変えてみました。

しかし、C側でWinAPIのGetUserNameで文字化けして、
VBではユーザ名をちゃんと取ることができません。

GetUserName((char *)&lpBuffer, dwSize );

これは間違った書き方なのでしょうか?

編集 削除
Blue  2005-09-05 16:31:40  No: 92007  IP: [192.*.*.*]

> GetUserName((char *)&lpBuffer, dwSize );
これってどこの処理?

TEST_API void __stdcall UserName(LPCSTR lpBuffer, DWORD* dwSize)
{
    GetUserName((char *)&lpBuffer, dwSize );
}
ってこと?

それならば、
GetUserName( lpBuffer, dwSize );
でいいです。

# > TEST_API void __stdcall UserName(LPCSTR lpBuffer, DWORD* dwSize)
# は
# ハンガリアンに従うならば、
# TEST_API void __stdcall UserName(LPCSTR lpBuffer, DWORD* pdwSize)
# でした。

編集 削除
Blue  2005-09-05 16:36:14  No: 92008  IP: [192.*.*.*]

> GetUserName
って、WinAPIなのね。。。いちいちDLL作らんでもいいような。。。
しかも戻り値あるし。

編集 削除
off  2005-09-05 16:37:56  No: 92009  IP: [192.*.*.*]

そうです。
TEST_API void __stdcall UserName(LPCSTR lpBuffer, DWORD* dwSize)
{
    GetUserName((char *)&lpBuffer, dwSize );
// ここでlpBufferの中身をテキストに出力したら文字化けしてましたです。
}

GetUserName( lpBuffer, dwSize );をビルドすると、
'const char *' 型は 'char *' 型に変換できない
(関数 __stdcall fnGetUserName2(const char *,unsigned long *) )
のエラーが出ます。

編集 削除
Blue  2005-09-05 16:48:21  No: 92010  IP: [192.*.*.*]

>TEST_API void __stdcall UserName(LPCSTR lpBuffer, DWORD* dwSize)
                                  ^^^^^^
型がちがってました。スイマセン。
LPSTRでした。
LPCSTRだとconstになるので変更が利かないのでした。

編集 削除
9566  2005-09-05 16:49:17  No: 92011  IP: [192.*.*.*]

LPCSTR = const char *
なんだから&lpBufferは
const char **
になってしまうでしょ。

編集 削除
Blue  2005-09-05 16:49:54  No: 92012  IP: [192.*.*.*]

波線 おもいっきりずれた。orz
# ここの掲示板はプロポーショナルフォントなのね。(´△` )アァー

編集 削除
9566  2005-09-05 16:51:22  No: 92013  IP: [192.*.*.*]

ん?なんか変なこと書いたかも

編集 削除
off  2005-09-05 16:54:36  No: 92014  IP: [192.*.*.*]

解決しました。

LPCSTRでユーザ名が取れました。

どうも、Blueさん、9566さん、
ありがとうございました。

編集 削除
Blue  2005-09-05 17:09:32  No: 92015  IP: [192.*.*.*]

> LPCSTRでユーザ名が取れました。
ホントでっか?
Cだからconst_cast出来ないとおもうのだが。。。

編集 削除
off  2005-09-05 17:36:09  No: 92016  IP: [192.*.*.*]

Blueさん。

間違えました。
「LPSTRでいけました。」の書き間違えです。

どうも、すみません。

編集 削除
Blue  2005-09-05 22:28:48  No: 92017  IP: [192.*.*.*]

あれから、
> Cで作ったDLLから戻り値(文字列)の取得について
ついて、いろいろとやってみました。

SysAllocStringByteLen関数を使うといとも簡単に出来ました。
(解答した時は知識不足でした。)

__declspec( dllexport ) BSTR UserName()
{
    char  szBuff[ 256 ] = { 0 };
    DWORD dwSize = 256;

    if ( GetUserNameA( szBuff, &dwSize ) )
    {
        return SysAllocStringByteLen( szBuff, dwSize - 1 );
    }
    return NULL;
}

編集 削除
Blue  2005-09-06 01:00:41  No: 92018  IP: [192.*.*.*]

>     return NULL;
NULLじゃないほうがいいかも。

return SysAllocString( L"" );

編集 削除
off  2005-09-06 12:56:56  No: 92019  IP: [192.*.*.*]

Blueさん、こんにちは。

SysAllocStringByteLen関数版ですか?
そんなやり方があったんですね。
早速、DLLに追加してみます。

質問内容とは違うことなのですが、また、質問してもよろしいでしょうか?

9X系、NT系の両方で使えるDLLを作ろうとしているのですが、
VBからDLLの中の関数を呼んだとき、NT系であればNT系にしか無い
APIを使って、そうでなければ、9X系のAPIを使っています。

このDLLを9X系で動かすと、「ファイルが見つかりません」の
エラーが出てしまいます(ロードエラー)。

そこで、NT系のAPIをLoadLibraryで動的に呼ぶように変更したのですが
結果はエラーのままなのです。

こんなとき、9X系、NT系の両方でも使えるようにするにはどうすれば
良いのでしょうか?

どうか、よろしくお願いします。

編集 削除
Blue  2005-09-06 13:11:26  No: 92020  IP: [192.*.*.*]

> 質問内容とは違うことなのですが、また、質問してもよろしいでしょうか?
改めて、新しいスレを建てたほうがよいです。
それと、完全にVBとCで切り分けられて、Cの話になってしまっているので、
ここの掲示板では場違いでしょう。
Visual C++ Q & A 掲示板を使ったほうがよさそう。
http://madia.world.coocan.jp/cgi-bin/Vcbbs/wwwlng.cgi
# VCで開発していないならば、別の掲示板探すのかな。。。
質問の際には、環境(OS,コンパイラの種類)を明示したほうがベターです。


> こんなとき、9X系、NT系の両方でも使えるようにするにはどうすれば
> 良いのでしょうか?
ちなみに、私はわかりません。

編集 削除
あん  2005-09-06 15:58:46  No: 92021  IP: [192.*.*.*]

>こんなとき、9X系、NT系の両方でも使えるようにするにはどうすれば
>良いのでしょうか?
On Error Resume Nextを使ってください。

編集 削除
 2005-09-06 16:19:02  No: 92022  IP: [192.*.*.*]

> >こんなとき、9X系、NT系の両方でも使えるようにするにはどうすれば
> >良いのでしょうか?
> On Error Resume Nextを使ってください。
DLLのロードの失敗はOnErrorでは補足できなかったと思います。
APIを環境ごとにActiveXDLLにラップして、その都度ロードするのを変えてみては?

編集 削除
あん  2005-09-06 16:34:50  No: 92023  IP: [192.*.*.*]

>DLLのロードの失敗はOnErrorでは補足できなかったと思います。

このDLLを9X系で動かすと、「ファイルが見つかりません」の
エラーが出てしまいます(ロードエラー)。

これがVBのON ERRORでは補足できないということですか?

以前、マイクロソフトから
WindowsXP以上からしかないDLLを動かす方法として
(Windows2000でも動くようにする)
On Error Resume Nextを教えてもらったんですが・・

編集 削除
off  2005-09-06 16:35:06  No: 92024  IP: [192.*.*.*]

Blueさん、あんさん、もさん、こんにちは。

VBに関係ない質問を投げてしまってすみません。
別の掲示板で問い掛けてみます。

それと、ActiveXDLLではレジストリに登録しないといけない?のは
避けたいので、別の方法を考えてみます。

どうも、みなさんお騒がせ致しました。

編集 削除
Blue  2005-09-06 16:40:28  No: 92025  IP: [192.*.*.*]

> VBに関係ない質問を投げてしまってすみません。
なんかそうでもないような気がしてきた。

DLLは結局のところ1つしかつくらないのですか?
ある特定の関数を使おうとするとエラーが発生し、それ以外の関数は
正常に使えるのでしょうか?

ちなみに、
>9X系、NT系の両方で使えるDLLを作ろうとしているのですが、
>VBからDLLの中の関数を呼んだとき、NT系であればNT系にしか無い
>APIを使って、そうでなければ、9X系のAPIを使っています。
なんてAPIでしょうか?

編集 削除
 2005-09-06 16:49:12  No: 92026  IP: [192.*.*.*]

>>DLLのロードの失敗はOnErrorでは補足できなかったと思います。
>以前、マイクロソフトから
>WindowsXP以上からしかないDLLを動かす方法として
>(Windows2000でも動くようにする)
>On Error Resume Nextを教えてもらったんですが・・
すみません、
今OnErrorを使ったテストプログラムを作ってみたところ、
ちゃんと補足できる例外が発生していました。

でも存在しないときどうやって分岐させればいいのかな…

編集 削除
off  2005-09-06 17:09:51  No: 92027  IP: [192.*.*.*]

Blueさん、もさんこんにちは。
私の環境では、OnErrorでエラーを引っ掛けることが出来ました。
エラー番号48:ファイルがみつかりません。TEST.DLL

Blueさん、そうです、DLLは1つしか作らず、
それをサーバにEXEと同じフォルダに置いて、
どのプラットフォームからでも利用できるアプリを作ろうとしています。

以前は、VBでActiveXDLLを作っていたのですが、
各端末にレジストリ登録をしなければならなかったので、
今回はCでDLLを作って挑戦しています。

他のAPIでも同様にエラーになります。これは単純にDLLが
端末に存在しないからだと思います。

因みに動かないAPIは、WTSQuerySessionInformationです。
9X系にはこのAPIが入っているWTSAPI32.DLLがないのです。

----------------------------------------------------------
もさん、「存在しないときどうやって…」っていうのは、
本当にDLLがないのかロードできないのか判断できない
ってことなのでしょうか?

編集 削除
Blue  2005-09-06 17:18:12  No: 92028  IP: [192.*.*.*]

> WTSQuerySessionInformation
のみを使おうとすると、その関数だけエラーが発生するのですね?

> そこで、NT系のAPIをLoadLibraryで動的に呼ぶように変更したのですが
> 結果はエラーのままなのです。
ということなので、WTSQuerySessionInformationを直接使わなくても
(LoadLibrary + GetProcAddressでWTSQuerySessionInformationの関数のアドレスにしても)
エラーがでるのですね?

編集 削除
 2005-09-06 17:20:56  No: 92029  IP: [192.*.*.*]

>もさん、「存在しないときどうやって…」っていうのは、
>本当にDLLがないのかロードできないのか判断できない
>ってことなのでしょうか?
いえ、DLLファイルが無い、DLLにエントリが無いといった例外は補足できました。
しかし例外を補足したとして、
回復して代わりに何をするのかが思い浮かびません。
Cだと明示的にリンクできるのですけどね…

編集 削除
off  2005-09-06 17:21:05  No: 92030  IP: [192.*.*.*]

>のみを使おうとすると、その関数だけエラーが発生するのですね?
そうです。

>ということなので、WTSQuerySessionInformationを直接使わなくても
>(LoadLibrary + GetProcAddressでWTSQuerySessionInformationの関数のアド>レスにしても)
>エラーがでるのですね?

そうです、呼ばなくてもエラーになリます。

編集 削除
off  2005-09-06 17:31:11  No: 92031  IP: [192.*.*.*]

もさん、勘違いをしてすみません。

>しかし例外を補足したとして、
>回復して代わりに何をするのかが思い浮かびません。
>Cだと明示的にリンクできるのですけどね…

例外で引っかかってくれれば、
エラーMSGを出して、その内容をログに出力するだけ。

あとは、DBなどを正常に切断させるように組めば
それだけでOKです(そんなに大きなシステムじゃないので)。

編集 削除
Blue  2005-09-06 17:58:33  No: 92032  IP: [192.*.*.*]

> そうです、呼ばなくてもエラーになリます。
LoadLibrary + GetProcAddressの実際のソースが見たいかも。

# ここまでくるとやっぱりCの話なのかなぁ。。。と
# 焦点は、9X系、NT系 をどこて判定するかがVBでできるかC側でできるかなのかなぁ。

編集 削除
魔界の仮面弁士  2005-09-06 18:11:41  No: 92033  IP: [192.*.*.*]

> それと、ActiveXDLLではレジストリに登録しないといけない?のは
最近のOSに限定するなら、SxSにてこの制限を回避できる可能性があります。
http://www.microsoft.com/japan/msdn/windows/windowsxp/sidexsidewinxp.asp

> いえ、DLLファイルが無い、DLLにエントリが無いといった例外は補足できました。
> しかし例外を補足したとして、
> 回復して代わりに何をするのかが思い浮かびません。
そもそも9x系ならば、WTSQuerySessionInformationを呼ぶ必要自体が無いの
ですから、事前にOSの確認処理を行っておけば、そのような回復処理を
考慮しなくて済むような気もしますが……(そういう話ではないのかな?)

編集 削除
off  2005-09-06 18:15:00  No: 92034  IP: [192.*.*.*]

Cのソースです。
int fnWTSQuerySessionInformation(LPSTR lpBuffer, int WTSInfo){

typedef BOOL (WINAPI *LPWTSQUERYSESSIONINFORMATION)(HANDLE, DWORD, int, LPTSTR*, DWORD* );

DWORD dwProccessId ;
DWORD SessionId ;
BOOL  rtncd ;
LPSTR ppBuffer ;
DWORD pBytesReturned ;
  
dwProccessId = GetCurrentProcessId();
  
rtncd = ProcessIdToSessionId( dwProccessId, &SessionId ) ;
  
// DLLをロード
HINSTANCE hLib = LoadLibrary("wtsapi32.dll");
if(!hLib) {
  FreeLibrary(hLib);  return 1;
}

// アドレスを取得
LPWTSQUERYSESSIONINFORMATION lpWTSAPIEntryPoint; // ポインタ
lpWTSAPIEntryPoint = (LPWTSQUERYSESSIONINFORMATION)GetProcAddress(hLib, "WTSQuerySessionInformationA");

if(!lpWTSAPIEntryPoint) {
    FreeLibrary(hLib);
    return 1;
}

// 呼び出し
BOOL bRet = (*lpWTSAPIEntryPoint)(0, SessionId, WTSInfo, &ppBuffer, &pBytesReturned );

FreeLibrary(hLib);

strcpy(lpBuffer, ppBuffer); //ここにユーザ名が入る

fclose(fp);
return 0;  
  
}

TEST_API int __stdcall fnUserName(LPSTR lpBuffer, DWORD* dwSize){

if( GetSystemMetrics( SM_REMOTESESSION ) )
  lfnWTSQuerySessionInformation( lpBuffer, WTSUserName );
else
  GetUserName(lpBuffer, dwSize );

return 0;
}

です。NT系か9X系かでは判断せずに、
ターミナルサービス上で動いているかでやっています。

編集 削除
あん  2005-09-06 18:51:55  No: 92035  IP: [192.*.*.*]

>Cだと明示的にリンクできるのですけどね…
VBでもできると思いますが・・
VBではAPIを呼ぶところでDLLをリンクするので
そのAPIを呼ばなければリンクしない!

編集 削除
 2005-09-06 19:11:51  No: 92036  IP: [192.*.*.*]

あんさん。先ほどのレスありがとうございました。
危うくDLLの読み込み時の例外を補足出来ないと思い続けていくところでした;

>>Cだと明示的にリンクできるのですけどね…
>VBでもできると思いますが・・
>VBではAPIを呼ぶところでDLLをリンクするので
>そのAPIを呼ばなければリンクしない!
たしかに、VB内部では呼び出し時に動的に解決をしているようですね。
リンクに失敗した後の回復方法として、Cで扱うような明示的リンクを想像したのですが…

編集 削除
Blue  2005-09-07 09:35:50  No: 92037  IP: [192.*.*.*]

> そもそも9x系ならば、WTSQuerySessionInformationを呼ぶ必要自体が無いの
> ですから、事前にOSの確認処理を行っておけば、そのような回復処理を
> 考慮しなくて済むような気もしますが……(そういう話ではないのかな?)
私もこれがいいと思います。(VB側でGetVersionEx関数あたりを使うのかな)

編集 削除
off  2005-09-07 10:13:41  No: 92038  IP: [192.*.*.*]

おはようございます。

>そもそも9x系ならば、WTSQuerySessionInformationを呼ぶ必要自体が無い…

呼び方が2パターンありまして、

ひとつは、C/S環境でDLLをロードし、ユーザ名を取得。
このときは、GetUserNameを呼ぶ。

もうひとつは、WTS、RDP環境で、ユーザ名(接続元)を取得。
このときは、WTSQuerySessionInformationを呼ぶ。

NT系は両方できたのですが、
C/S環境の9x系のとき、どうしてもロードエラーが回避ないのです。

編集 削除