IPv4 でマルチキャストのパケットを送出する際、デフォルトのままでは TTL が 1 になってしまう。ルータ越えの必要がある場合は以下のようにして、送信に使用するソケットのオプションを変更する。(下記は TTL を 32 に変更した例)
// #include <Winsock2.h> int ttl = 32; int result = setsockopt (socket, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&ttl, sizeof ttl); if (SOCKET_ERROR == result) { // エラー処理は省略... }
C# の場合はこんなの。
// using System.Net.Sockets; int ttl = 32; socket.SetSocketOption (SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, ttl);
TCP サーバや UDP アプリなどの設定画面に登場する、バインドするネットワークアドレスを選択するコンボボックスです。はい、ものすごく限定的な Tips です。ネットワークインターフェースの列挙方法の一例、かな?
BOOL CXXXDialog::InitNetworkCombo (void)
{
char szHostName [MAX_PATH];
ZeroMemory (szHostName, sizeof szHostName);
m_ComboNetwork.Clear ();
// このコンピュータのホスト名を取得する
int nRet = gethostname (szHostName, sizeof szHostName);
if (SOCKET_ERROR == nRet)
{
// エラー処理
}
else
{
// ホスト名よりホストの情報を取得する
struct hostent* pHostEnt = gethostbyname (szHostName);
if (!pHostEnt)
{
// エラー処理
}
else
{
// INADDR_ANY を追加する
int nIndex = m_ComboNetwork.AddString (_T ("すべてのネットワーク"));
m_ComboNetwork.SetItemData (nIndex, INADDR_ANY);
// 取得したアドレスのリストをコンボボックスに追加する
for (int nCount = 0; pHostEnt->h_addr_list [nCount]; nCount ++)
{
struct in_addr Address;
memcpy (&Address, pHostEnt->h_addr_list [nCount], sizeof (struct in_addr));
nIndex = m_ComboNetwork.AddString (inet_ntoa (Address));
m_ComboNetwork.SetItemData (nIndex, Address.S_un.S_addr);
}
// ループバックを追加する
nIndex = m_ComboNetwork.AddString (_T ("127.0.0.1"));
m_ComboNetwork.SetItemData (nIndex, INADDR_LOOPBACK);
return TRUE;
}
}
return FALSE;
}
アイコン型のウインドウを作成したかったのです。それには、アイコンのマスク部分を取得して、マスクの不透明部分の形のウインドウリージョンを作って設定します。
リージョン作成部分は続きの方にかいときますが、ウインドウプロシージャはこんな感じ。
LRESULT CALLBACK WindowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_PAINT:
PAINTSTRUCT PaintStruct;
HDC hDC = BeginPaint (hWnd, &PaintStruct);
DrawIcon (hDC, 0, 0, m_hIcon);
EndPaint (hWnd, &PaintStruct);
break;
case WM_MOVE:
SetWindowRgn (hWnd, m_hRgn, TRUE);
break;
default:
break;
}
return DefWindowProc (hWnd, uMsg, wParam, lParam);
}
説明が前後しますが、ウインドウ作成時にウインドウに描画するアイコンを m_hIcon 仁読み込んであります。同時に、そのアイコンハンドルを渡して後述の CreateRgnFromIcon (HICON) を呼びだし、m_hRgn にアイコンのマスク部分の形のリージョンを得てあります。では、その CreateRgnFromIcon 関数です。
タイトルバーのないウインドウを、ウインドウ内の任意の位置をドラッグして移動できるようにする方法です。
以下のように、WM_LBUTTONDOWN メッセージをすり替えて、タイトルバー上でマウスボタンが押下されたようにだましてしまうとうまくいきます。
LRESULT CALLBACK WindowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_LBUTTONDOWN:
ReleaseCapture ();
SendMessage (hWnd, WM_NCLBUTTONDOWN, HTCAPTION, 0);
return 0;
default:
break;
}
return DefWindowProc (hWnd, uMsg, wParam, lParam);
}
ウインドウを最大化した後、非表示にする。
SendMessage (hWnd, WM_SYSCOMMAND, SC_MAXIMIZE); ShowWindow (hWnd, SW_HIDE);
次に表示すると、ウインドウは最大化状態のままだ。
ShowWindow (hWnd, SW_SHOW);
ここで問題です。ウインドウを非表示のままでリストア(元のサイズに戻す、つまり最大化状態を解除)するにはどうしたらいいでしょう。
// 元のサイズに戻るが、同時にウインドウが表示されてしまう ShowWindow (hWnd, SW_RESTORE); // これも SendMessage (hWnd, WM_SYSCOMMAND, SC_RESTORE); // これも WINDOWPLACEMENT Placement; ZeroMemory (&Placement, sizeof (WINDOWPLACEMENT)); Placement.length = sizeof (WINDOWPLACEMENT); GetWindowPlacement (hWnd, &Placement); Placement.showCmd = SW_RESTORE; SetWindowPlacement (hWnd, &Placement);
意外と難しい。最終的に、私が思いついたのはこんな方法。
DWORD dwStyle = GetWindowLong (hWnd, GWL_STYLE); dwStyle &= ~WS_MAXIMIZE; SetWindowLong (hWnd, GWL_STYLE, dwStyle);
この場合、最大化時のウインドウ位置とサイズがそのままでリストアされてしまう。非表示のままリストアを厳密にシミュレートするには、あらかじめ GetWindowPlacement で、リストア時のウインドウ矩形を取得しておき、リストア後に MoveWindow してあげないといけない。
ダイアログのデフォルトボタンを動的に変更したい、あるいは、一時的にデフォルトでなくしたい、などを行うには、ダイアログに対して DM_SETDEFID メッセージを送出し、デフォルトにするボタンの ID を渡す。MFC だと、CDialog::SetDefID がそれにあたる。しかし、それだけではボタンの周囲の黒枠が更新されないんである。Enter キーを押下すると、確かにデフォルトボタンが変更されていることが分かるが、表示上は更新されていない。SendMessage で送出したあと Invalidate したり、UpdateWindow したりしてもだめだったので、メッセージ受信と非同期に「デフォルト」という属性を更新しているのかもしれない。
あれこれやってみたところ、以下のようにフォーカスを強制的にセットしてやる以外のうまい方法は見つけられなかった。
// フォーカスのあるコントロールを取得しておく CWnd *pWndFocus = GetFocus (); // ボタンをデフォルトに SetDefID (IDC_BUTTON1); // 一旦フォーカスをセットする GotoDlgCtrl (&m_Button1); // 以前にフォーカスのあったコントロールに戻る if (pWndFocus) { GotoDlgCtrl (pWndFocus); }
もともと IDC_BUTTON1 にフォーカスがあったとしても、この方法でうまくいくようだ。ちなみに、単にデフォルト属性をはずす場合は、WPARAM を 0 にして DF_SETDEFID するのでいけるようだ。(SetDefID (0); ってこと)
PathAddExtension という関数がある。パスの最後に拡張子を追加するというもの。
BOOL PathAddExtension(
LPTSTR pszPath,
LPCTSTR pszExtension
);
戻り値は、拡張子を追加した場合は TRUE、以外は FALSE が返る。
注意しなきゃなんないのは
Remarks
If there is already a file extension present, no extension will be added. If the pszPath points to a NULL string, the result will be the file extension only. If pszExtension points to a NULL string, an ".exe" extension will be added.
すでに拡張子が付いてる場合は、この関数はさらに拡張子を付けることをしない。つまり、ファイル名にピリオドを含みたい場合には不適だ("MyAppVer1.1.exe"とかさ)。ユーザに入力されたファイル名に拡張子を付けるような場合に使うのはやめた方がいい。
Windows Xp になって、ファイル名に使用できない文字を入力したときのメッセージが修正されているのに気がついた。
以前は、カンマとセミコロンが、実際にはファイル名に使用できるにもかかわらず、使用できない文字として表示されていたのは有名な話だ。
ところで、shlwapi.dll には、ファイル名に使用できる文字かどうかを判断する関数がある。PathGetCharType だ。
Win32API の TransparentBlt 関数の解説に
SetStretchBltMode 関数で iStretchMode モードの値として設定される BLACKONWHITE と WHITEONBLACK は、TransparentBlt 関数では COLORONCOLOR に変換されます。
とある。が、じっさいには、 HALFTONE も COLORONCOLOR に変換されているように見える。そう書いてあるリソースがどこにもないのだけれど。カラーキーを指定したければ自分でマスクするか、GDI+ を使うのがよさげ。
TTM_SETMAXTIPWIDTH を送る。忘れないようにメモメモ。
lResult = SendMessage( // returns LRESULT in lResult (HWND) hWndControl, // handle to destination control (UINT) TTM_SETMAXTIPWIDTH, // message ID (WPARAM) wParam, // = 0; not used, must be zero (LPARAM) lParam // = (LPARAM) (INT) iWidth; );
CDC のメンバ関数に、FillSolidRect ってのがある。私の知る限り、SDK には対応する関数がないのだが、引数が RECT と色だけで、ブラシを作成したりする必要がないので、ちょっと矩形領域を塗りつぶすのに重宝している。
ところで、この関数の実装は、普通に考えたら、こう?
void CDC::FillSolidRect( LPCRECT lpRect, COLORREF clr )
{
ASSERT_VALID(this);
ASSERT(m_hDC != NULL);
CBrush SolidBrush;
if ( SolidBrush.CreateSolidBrush( clr ) ) // 指定色のブラシを作成
{
FillRect( lpRect, &SolidBrush );
SolidBrush.DeleteObject(); // 作ったブラシは削除
}
}
でも、デバッグのときにソースを見ると、実はこうなっている。
SDK で矩形塗りつぶしをするときにも、わざわざブラシを作らなくてもできるってこと。
後ろで動いてるアプリを手前に持ってくる関数です。いつも作るうえに、毎回調べないと作れないので載せておきます。
ちなみに、ウインドウが「常に」最前面に表示されるようにするには、SetWindowPos 関数で HWND_TOPMOST を渡します。でもこれはググればごろごろサンプル出てくるんですけどね。
私は今まで、VBでウインドウの配置を決めるのに、デスクトップのサイズを下のようにして得ていた。
Dim lngRet As Long
Dim udtRcDesktop As RECT
'' デスクトップ全体の矩形を取得します
lngRet = GetWindowRect( GetDesktopWindow(), udtRcDesktop )
でも、これだとタスクバーを含んじゃうんだよね。で、SystemParametersInfo関数を使うと、タスクバーを含まない、デスクトップの作業領域の矩形を得ることができます。
'' デスクトップの作業領域の矩形を取得します
lngRet = SystemParametersInfo( SPI_GETWORKAREA, 0&, udtRcDesktop, 0& )
VB6のリストボックスは、項目が長くても横スクロールできない。横スクロールバーを出すには、LB_SETHORIZONTALEXTENT メッセージを送る必要がある。
項目追加するたびに横スクロールバーを調整するのはこんな感じ
Option Explicit Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long Private Const LB_GETHORIZONTALEXTENT As Long = &H193 Private Const LB_SETHORIZONTALEXTENT As Long = &H194& '------------------------------------------------------------------ 'プロシージャ名: :AddItemAndSetScrollBar '説明: :リストボックスに項目を追加し、必要に応じて横スクロールバーを表示する '引数: :pListBox I リストボックス ' :pItem I 追加する文字列 ' :pIndex I 追加位置 '------------------------------------------------------------------ Private Sub AddItemAndSetScrollBar(pListBox As VB.ListBox, pItem As String, Optional pIndex As Variant) Dim sngTextWidth As Single Dim lngCurPixels As Long Dim lngNewPixels As Long '' ここは説明は不要でしょう Call pListBox.AddItem(pItem, pIndex) '' 現在の幅を取得 lngCurPixels = SendMessage(pListBox.hWnd, LB_GETHORIZONTALEXTENT, 0&, ByVal 0&) '' テキストの幅から必要なスクロール幅を計算 '' 計算方法は適宜修正のこと '' リストボックスのフォントとフォームのフォントが同じならこんな感じで求められる With pListBox.Parent sngTextWidth = .TextWidth(pItem) '' 文字列の幅 lngNewPixels = .ScaleX(sngTextWidth, .ScaleMode, vbPixels) '' ピクセルに変換 End With '' 余白分を足す '' ここでは適当に4ピクセル足してるが、システム標準の値とかあるのかしら '' GetSystemMetrics( SM_CXEDGE ) * 2 あたりがに合わしておくといいかも lngNewPixels = lngNewPixels + 4& '' 現在の幅がちっちゃければ設定します。 If (lngNewPixels > lngCurPixels) Then Call SendMessage(pListBox.hWnd, LB_SETHORIZONTALEXTENT, lngNewPixels, ByVal 0&) End If End Sub
VB6 で、MakeSureDirectoryPathExists 関数の動きをシミュレートしたものを作ったので、おいときます。
VB6 より Windows API を利用する場合に、APIビューアは便利ですが、しばしば宣言等に誤りがあります。
今回は、フォントファミリの列挙を行う EnumFontFamiliesEx 関数を使おうとして、ENUMLOGFONTEX 構造体の宣言をコピーしたのですが、
' API ビューアの宣言
Private Type ENUMLOGFONTEX
elfLogFont As LOGFONT
elfFullName(LF_FULLFACESIZE) As Byte
elfStyle(LF_FACESIZE) As Byte
elfScript(LF_FACESIZE) As Byte
End Type
のようになっていました。WinGDI.h には
// WinGDI.h (Microsoft Platform SDK February 2003) Line:1220 #if(WINVER >= 0x0400) typedef struct tagENUMLOGFONTEXA { LOGFONTA elfLogFont; BYTE elfFullName[LF_FULLFACESIZE]; BYTE elfStyle[LF_FACESIZE]; BYTE elfScript[LF_FACESIZE]; } ENUMLOGFONTEXA, FAR *LPENUMLOGFONTEXA; typedef struct tagENUMLOGFONTEXW { LOGFONTW elfLogFont; WCHAR elfFullName[LF_FULLFACESIZE]; WCHAR elfStyle[LF_FACESIZE]; WCHAR elfScript[LF_FACESIZE]; } ENUMLOGFONTEXW, FAR *LPENUMLOGFONTEXW; #ifdef UNICODE typedef ENUMLOGFONTEXW ENUMLOGFONTEX; typedef LPENUMLOGFONTEXW LPENUMLOGFONTEX; #else typedef ENUMLOGFONTEXA ENUMLOGFONTEX; typedef LPENUMLOGFONTEXA LPENUMLOGFONTEX; #endif // UNICODE #endif /* WINVER >= 0x0400 */
となっていますので、VB6 では、
' 正解
Private Type ENUMLOGFONTEX
elfLogFont As LOGFONT
elfFullName(LF_FULLFACESIZE - 1&) As Byte
elfStyle(LF_FACESIZE - 1&) As Byte
elfScript(LF_FACESIZE - 1&) As Byte
End Type
のようにするのが正しいです。
サービスプログラムのデバッグがしたくて、 ServiceMain と、Handler 関数をエクスポートしてみた。
// MyService.def
EXPORTS
ServiceMain
Handler
さらに、テストプロを書いてみる
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#define MODULE_NAME "MyService.exe"
#define FUNCNAME_MAIN "ServiceMain"
#define FUNCNAME_HANDLER "Handler"
int main(
int argc,
char **argv )
{
HMODULE hModule = NULL;
char szCurrent[MAX_PATH];
char szModule[MAX_PATH];
DWORD dwRet = 0;
LPSERVICE_MAIN_FUNCTION lpfnServiceMain = NULL;
LPHANDLER_FUNCTION lpfnHandler = NULL;
ZeroMemory( szCurrent, sizeof szCurrent );
dwRet = GetCurrentDirectory( sizeof szCurrent, szCurrent );
if ( !dwRet ) {
fprintf( stderr, "GetCurrentDirectory err -->(%d)\n", GetLastError() );
}
else {
ZeroMemory( szModule, sizeof szModule );
wsprintf( szModule, "%s\\%s", szCurrent, MODULE_NAME );
hModule = LoadLibrary( szModule );
if ( !hModule ) {
fprintf( stderr, "LoadLibrary err -->(%d)\n", GetLastError() );
}
else {
lpfnServiceMain = (LPSERVICE_MAIN_FUNCTION)GetProcAddress( hModule, FUNCNAME_MAIN );
if ( !lpfnServiceMain ) {
fprintf( stderr, "GetProcAddress(ServiceMain) err -->(%d)\n", GetLastError() );
}
else {
lpfnHandler = (LPHANDLER_FUNCTION)GetProcAddress( hModule, FUNCNAME_HANDLER );
if ( !lpfnHandler ) {
fprintf( stderr, "GetProcAddress(Handler) err -->(%d)\n", GetLastError() );
}
else {
// とりあえず Handler をコールしてみた
lpfnHandler( SERVICE_CONTROL_CONTINUE );
}
}
FreeLibrary( hModule );
}
}
return 0;
}
結果は・・・呼び出せた。そんで動けば面白かったんだけど、サービスモジュール側でログ出力の処理がアクセスヴァイオレーションになってしまった。まあ、そううまくはいかないよね。一応、失敗談として。