- PR -

親画面(EXE)と子画面(DLL)の作成

投稿者投稿内容
優希
ベテラン
会議室デビュー日: 2003/08/12
投稿数: 92
投稿日時: 2003-08-12 23:28
お世話になります。
親画面(EXE)と子画面(DLL)の作成で、
問題にぶつかってしまいました。
どうか、力をお貸しください。

以下が作ろうとしている詳細です。
(マネージコードにて)

開発環境は、Visual Studio .NET(C++)、Windowsフォームを使って行います。

親画面をFormクラスで生成し、その中にPictureBoxなどで枠を決め、
その中に子画面を表示したいと思っております。
(1親画面に対して、2つの子画面の同時表示などもあるため、
 MDIフォームは使えないかと…)

親画面はEXE形式で作成し、子画面はDLL形式で作成します。
子画面はINIファイルでどの子画面(DLL)を表示するかを設定します。

ここで以下の点が問題になってきます。

1.親画面(EXE)を変更せずに、子画面(DLL)だけ新規作成・修正をしたいと考えており
  EXEのソースでDLLの宣言をどのような方法で行なったら良いのか?
 (要するに、どのDLLのどの関数を呼んで、子画面を起動させるのか
  というのを動的にやりたい、ということです)
 

2.子画面を作成するときに、親画面からPictureBoxなど表示元のコントロールのハンドルを
  渡し、子画面側(DLL)でButtonコントロールなどをAddすると

「追加情報 : あるスレッドで作成されたコントロールに対して、
 別のスレッドのコントロールを親にすることはできません。」

 とエラーになってしまいます。
 子画面の中で自由にコントロールを作成して、ADD()はできないのでしょうか?
 子画面のDLL側は別な人が作る為、使用するコントロールの制限はできません。
 また、子画面がFormだとPictureBoxに追加はできません、よね。

(例)START---------------------------------------------------------------------
 親側(EXE):PictureBox *pic = new PictureBox(); // 子画面格納用PictureBox
    pic->Size = Drawing::Size(1240, 100); // サイズ設定
pic->Location = Point(0, 0); // 位置設定
this->Controls->Add(pic); // Formへ追加
DispDllWin(pic->get_Handle()); // 呼び出し

子側(DLL):
int __stdcall DispDllWin(IntPtr wHndlPic){ // 子画面生成メソッド
// PictureBox生成
PictureBox *parentPic = new PictureBox();
parentPic = dynamic_cast<PictureBox *>
(Control::FromChildHandle(wHndlPic));
// Button生成
Button *btn = new Button();
btn->Text = S"Sample1";
parentPic->Controls->Add(btn); // 追加 ←★ここでエラー★
}
(例)END----------------------------------------------------------------------


3.スレッドを用いて子画面の生成をしているのですが、終了しようとすると
  以下のエラーになります。

「Form1.exe の 0x003ca13d でハンドルされていない例外が発生しました
: 0xC0000005: 場所 0xfffffff0 を読み込み中にアクセス違反が発生しました。」

  正常に終了するのには、どうしたらよいのでしょうか? 


(例)START---------------------------------------------------------------------
※それぞれは抜粋したので、おかしい個所があるかもしれませんが
  それを考慮して見てください。(すべて親画面(EXE)の処理です)

// アプリケーションの起動
int __stdcall WinMain(){
Application::Run(new Form1()); // 実行
return 0;
} ← ★ここでエラー★

// アプリケーションの終了
void Form1:ispose(Boolean disposing){
if(disposing && components){
components->Dispose();
}
__super:ispose(disposing);
}

// ボタン押下時にスレッド生成
void Form1::btnA_MouseDown(Object *sender, MouseEventArgs* e){
Thread th = new Thread(new ThreadStart(this,&Form1::ShowChildWnd));
th->Start();
}

// DLLへアクセス(子画面生成)
void Form1::ShowChildWnd(){
DispDllWin(pic->get_Handle()); // DLL呼び出し
}
(例)END----------------------------------------------------------------------

お分かり頂ける個所だけでも結構ですので、
宜しくお願い致します。
また、詳しいサイト・サンプルコードなども
宜しくお願い致します。
mei
大ベテラン
会議室デビュー日: 2003/04/08
投稿数: 114
投稿日時: 2003-08-13 22:45
こんばんは、meiです。

引用:

優希さんの書き込み (2003-08-12 23:28) より:
1.親画面(EXE)を変更せずに、子画面(DLL)だけ新規作成・修正をしたいと考えており
  EXEのソースでDLLの宣言をどのような方法で行なったら良いのか?
 (要するに、どのDLLのどの関数を呼んで、子画面を起動させるのか
  というのを動的にやりたい、ということです)


「クラスのインスタンス化について」スレッドにあるように、
動的にクラスをロードする方向だと思います。
でも、直接ロードするよりはClassFactoryを作った方がいいかな?

引用:

2.子画面を作成するときに、親画面からPictureBoxなど表示元のコントロールのハンドルを
  渡し、子画面側(DLL)でButtonコントロールなどをAddすると

「追加情報 : あるスレッドで作成されたコントロールに対して、
 別のスレッドのコントロールを親にすることはできません。」

 とエラーになってしまいます。
 子画面の中で自由にコントロールを作成して、ADD()はできないのでしょうか?
 子画面のDLL側は別な人が作る為、使用するコントロールの制限はできません。
 また、子画面がFormだとPictureBoxに追加はできません、よね。


エラーメッセージにある通りだと思います。
コントロールに子が追加できないのではなく、スレッドが違う所為です。
あと、DLLに渡すのが何故、handleになっているのでしょうか?
マネージドでやっているのなら、極力handleは使わない方向だと思いますが。

それから、Formは子にはなれませんし、する必要もないと思うのですが・・・
コントロールのグルーピングにPanelクラスを使えない理由があるのでしょうか?

引用:

3.スレッドを用いて子画面の生成をしているのですが、終了しようとすると
  以下のエラーになります。

「Form1.exe の 0x003ca13d でハンドルされていない例外が発生しました
: 0xC0000005: 場所 0xfffffff0 を読み込み中にアクセス違反が発生しました。」

  正常に終了するのには、どうしたらよいのでしょうか? 


逆に質問なのですが、マルチスレッドを使っているようですが、
排他制御を正しくやっているのにも関わらず異常終了するということでしょうか?
例えば、使っているクラスの中にスレッドセーフでは無いものが混じってませんか?
他にも、スレッド間の同期処理が拙くて、
別スレッドでオブジェクトが削除されたのに、
そのオブジェクトにアクセスしてしまっているということはありません?

個人的には、バックグラウンドで独立して走るようなスレッドはともかく、
オブジェクトへのアクセスが競合するような処理は排他制御など扱いが難しいので、
スレッドの採用は慎重に行った方が良いと思います。

コントロールの作成なら、別スレッドで処理するほど重いとは思えないのですが・・・
優希
ベテラン
会議室デビュー日: 2003/08/12
投稿数: 92
投稿日時: 2003-08-13 23:31
こんばんわ、優希です。
meiさん、ご返答ありがとう御座います。

3つを一度に質問してしまい、
長々しくてすいませんでした。
これからは、ひとつひとつ解決していこうと思います。

まず、[1]ですが、

>「クラスのインスタンス化について」スレッドにあるように、
> 動的にクラスをロードする方向だと思います。

ご返答頂いたスレッドを参考に作成してみたいと思います。
また、違うスレッドで見つけたのですが、
Wrapper dllを使用するっていうのも可能なのかと。。。


次に、[2]ですが、

> コントロールに子が追加できないのではなく、スレッドが違う所為です。

スレッドを同じにしておくためには、
BeginInvoke()メソッドを使う必要があるのでしょうか?
もし必要であれば、このメソッドの使い方(引数に何を渡すのか)を
教えてもらえませんか?
もし違うのであれば、分かる範囲でご教授お願い致します。

それと、

> あと、DLLに渡すのが何故、handleになっているのでしょうか?

とあるので、直接コントロールを渡す方が良いということなのですね。
また、子画面でPanelを使えないということはないので、
グルーピングにPanelを使いたいと思います。


最後に[3]ですが、
ソリューションのプロパティで設定を変えてみたら、
(すいません、箇所は覚えていないです)
エラー終了することはなくなりました。


以上、よろしくお願い致します。
mei
大ベテラン
会議室デビュー日: 2003/04/08
投稿数: 114
投稿日時: 2003-08-14 01:05
引用:

優希さんの書き込み (2003-08-13 23:31) より:
スレッドを同じにしておくためには、
BeginInvoke()メソッドを使う必要があるのでしょうか?



優希さんのやりたいことを、まだ掴めてないのですが、
スレッドや非同期実行じゃなくて、
普通に関数を呼ぶ訳にはいかないのでしょうか?

私がイメージしていたのは、

メインのFormを作る人と、
Formの中をデザインする人(こちらは複数人)で作業分担をする。
モジュール構成は例えば、

メインのフォーム:Main.exe
PictureBoxの中にコントロールを作るモジュール:Sub1.DLL,Sub2.Dll...
で、Main.exeは設定ファイルによって、Sub1.Dll,Sub2.Dllを切り替えて使う。

ここまで合ってますか?

次に私が言っていた、ClassFactoryというのは、
Main.exe -> ClassFactory.dll ->Sub1.Dll or Sub2.Dll
という構成にするということです。
#Main.exeからはSub1.Dll,Sub2.Dllは見えなくする。

例えばこんな感じです。

↓ClassFactory.dllのつもり。
コード:

// MyClassFactory.h

#pragma once

using namespace System;
using namespace System::Windows::Forms;
using namespace System::Drawing;

// MyClassFactory.dll

// メインから見えるのはインタフェースだけ
public __gc __interface IFormBuilder {
public:
void Build(PictureBox* pb);
};

// Buttonを追加するクラス(本来はSub1.dllとかにある)
public __gc class ButtonFormBuilder : public IFormBuilder {
public:
void Build(PictureBox* pb);
};

void ButtonFormBuilder::Build(PictureBox* pb) {
pb->Controls->Add(new Button());
}

// TextBoxを追加するクラス(本来はSub2.dllとかにある)
public __gc class TextBoxFormBuilder : public IFormBuilder {
public:
void Build(PictureBox* pb);
};

void TextBoxFormBuilder::Build(PictureBox* pb) {
TextBox* tb = new TextBox();
tb->Location = Point(0, 100);
pb->Controls->Add(tb);
}


public __gc class MyClassFactory
{
public:
static IFormBuilder* GetBuilder(String* name);

};

IFormBuilder* MyClassFactory::GetBuilder(String* name) {
// nameは設定ファイルから読み込まれたクラスの文字列表現
if (name->Equals(S"Button")) {
// 本当はAssembly.LoadFromあたりでSub1.dllを読み込む
return new ButtonFormBuilder();
}
else if (name->Equals(S"TextBox")) {
// 本当はAssembly.LoadFromあたりでSub2.dllを読み込む
return new TextBoxFormBuilder();
}
return 0;
}


本当は3つのDLL(ClassFactory.dll,Sub1.dll,Sub2.dll)になっているのですが、
例なので手抜き。

で、メインのフォームからは、

↓Main.exeのつもり。
コード:

namespace MyFormApp
{
using namespace System;
using namespace System::ComponentModel;
using namespace System::Collections;
using namespace System::Windows::Forms;
using namespace System::Data;
using namespace System::Drawing;

public __gc class Form1 : public System::Windows::Forms::Form
{
public:
Form1(void)
{
InitializeComponent();
}

protected:
void Dispose(Boolean disposing)
{
if (disposing && components)
{
components->Dispose();
}
__super::Dispose(disposing);
}
private: System::Windows::Forms::PictureBox * pictureBox1;

private:
System::ComponentModel::Container * components;

void InitializeComponent(void)
{
this->pictureBox1 = new System::Windows::Forms::PictureBox();
this->SuspendLayout();
//
// pictureBox1
//
this->pictureBox1->Location = System::Drawing::Point(8, 8);
this->pictureBox1->Name = S"pictureBox1";
this->pictureBox1->Size = System::Drawing::Size(272, 248);
this->pictureBox1->TabIndex = 0;
this->pictureBox1->TabStop = false;
//
// Form1
//
this->AutoScaleBaseSize = System::Drawing::Size(5, 12);
this->ClientSize = System::Drawing::Size(292, 266);
this->Controls->Add(this->pictureBox1);
this->Name = S"Form1";
this->Text = S"Form1";
this->Load += new System::EventHandler(this, Form1_Load);
this->ResumeLayout(false);

}
private: System::Void Form1_Load(System::object * sender, System::EventArgs * e)
{
// 設定ファイルから読み込むDLLの種類を取得
// 例なのでButton固定ですが・・・
String* dllType = S"Button";

// dllTypeに対応するクラスを取得
// dllのダイナミックロードはClassFactoryが担当
IFormBuilder* fb = MyClassFactory::GetBuilder(dllType);
// ロードされたクラスをIFormBuilderインタフェース経由で呼ぶ
fb->Build(pictureBox1);
}

};
}



こんな感じで想像していたので、
スレッドとかBeginInvokeが出てくるのが、イマイチ分かってません。
優希さんはどんな処理を行いたいのでしょうか?


[ メッセージ編集済み 編集者: mei 編集日時 2003-08-14 08:15 ]
優希
ベテラン
会議室デビュー日: 2003/08/12
投稿数: 92
投稿日時: 2003-08-14 10:55
こんにちわ。
早速のご回答、ありがとうございます。


> メインのFormを作る人と、
> Formの中をデザインする人(こちらは複数人)で作業分担をする。
> モジュール構成は例えば、
> メインのフォーム:Main.exe
> PictureBoxの中にコントロールを作るモジュール:Sub1.DLL,Sub2.Dll...
> で、Main.exeは設定ファイルによって、Sub1.Dll,Sub2.Dllを切り替えて使う。
> ここまで合ってますか?

はい。大体のところは合っています。
ただ、「Sub1.DLL,Sub2.Dll...」は
単なる[ボタン]や[テキストボックス]だけではなく、
それ自身も画面、というような作りなんです。
dllのある関数を呼ぶと、PictureBoxの中に画面が表示されるような。
ですので、
MyClassFactory::GetBuilder()
において、
設定ファイルから読み込むDLLの種類(名前)というのは、動的になるのです。
言い換えれば、その名称は子画面を作る人が自由に決められるし、
数も自由に増やせる、というものなので、
固定文字列を持っておいて比較するのは、難しいかと思うのです。
(言いたいことが伝わりづらいかな…(^^)

あと、
「子画面の追加・修正があっても、親画面は修正しない」ということなので、
DLLを追加したからといって、EXEも修正するっていうのは、したくないのです。
方法としては、すべてのDLLでは関数名は共通に作ってもらって
DLL名称でどのDLLの関数を呼ぶか判定、というのが良いのかと考えていますが…。



> 次に私が言っていた、ClassFactoryというのは、
> Main.exe -> ClassFactory.dll ->Sub1.Dll or Sub2.Dll
> という構成にするということです。
> #Main.exeからはSub1.Dll,Sub2.Dllは見えなくする。

私もこのような処理の仕方を考えております。
昨日、考えて見たんですが、
以下のような感じでやりたいのです。
~~~~~~

1.メインフォームを起動する。
2.設定ファイルから「DLL名称」を取得する。(例:Sub1.dll)
3.一旦、中間のDLL(ClassFactory.dll)を呼ぶ。⇒ソース参照
4.中間のDLL(ClassFactory.dll)から、指定DLLを呼ぶ。

コード:
///
/// ClassFactory.dllのメソッドです
/// DLL名称を取得して、そのDLLへアクセスします。
///
int __stdcall SelectDll(String *strDllName, String *funcName, PictureBox *pic){

	int rtnValue = 0;
	typedef UINT (CALLBACK* ReadFile)(PictureBox*);

	// DLL名称
	strDllName = strDllName->Trim();
	CString chrDllName(strDllName);	// String* ⇒ char* へ変換
	HINSTANCE hInst = ::LoadLibrary(chrDllName);	// DLL取得

	// 関数名称
	funcName = funcName->Trim();
	CString chrFuncName(funcName);	// String* ⇒ char* へ変換
	ReadFile fFile = (ReadFile)::GetProcAddress(hInst, chrFuncName); // 該当関数の先頭アドレス取得

	// 関数の有無
	if(fFile != NULL){
	    // 指定DLLへアクセス
	    fFile(pic);
	}else{
	    rtnValue = -1; // エラー
	}
	::FreeLibrary(hInst);

	return rtnValue;
}



…これってマネージコードではないですよね!?
それと、ふと思ったのですが、
そもそも中間のDLLを呼ばずとも、
この↑の処理をEXEで処理することもできますよね!?
中間のDLLを呼ぶことで、何か利点ってありましたっけ?



> こんな感じで想像していたので、
> スレッドとかBeginInvokeが出てくるのが、イマイチ分かってません。

これも説明せずに、すいません。
上記でも記述しました通り、
子画面でも[ボタン]や[テキストボックス]などいろんな
コントロールを入れてひとつの「画面」として表示します。
だから、入力処理や更新処理など時間がかかる作業が入るのかなぁっと思っているので、
スレッド形式にした方が効率があがるのかと。
間違いでしたら、ご指摘ください。



知識不足の部分も多いのですが、
いろいろご教授お願い致します。
mei
大ベテラン
会議室デビュー日: 2003/04/08
投稿数: 114
投稿日時: 2003-08-14 23:28
こんばんは、meiです。

優希さんのやりたいことは大体掴めました。
問題は大きく分けて2点。

・実行時に読み込むDLLを変更可能にする
・パフォーマンスアップの為にスレッドを使う

まずは、前者の方から。

本当はサンプルをC++で書いてみようと思ったのですが、
Managed C++は苦手なのでC#にしてしまいました。
#前回のが、初めてのManaged C++だったりします(笑)。
C++のキーワードは似ているので理解出来ると思いますが、
もし不明なところがあったら聞いて下さい。

構成ですが、
ISub.dll : PictureBoxの中をデザインするクラスのインタフェース。
Sub1.dll : ISubインタフェースを実装するデザイン方法1のクラス。
Sub2.dll : ISubインタフェースを実装するデザイン方法2のクラス。
Main.exe : Formのあるメインモジュール

各DLLの関連は、
Main.exe - ISub.dll
Sub1.dll - ISub.dll
Sub2.dll - ISub.dll
つまり、Main.exeとSub1.dll,Sub2.dllは、
ISub.dllのインタフェース経由でしかやり取りしません。

まずは、ISub.dllの中身
コード:
using System;
using System.Windows.Forms;

public interface ISub
{
	void Build(PictureBox pb);
}


単純なインタフェースのみです。

次にSub1.dll。
コード:
using System;
using System.Windows.Forms;

public class Sub : ISub {
	public Sub() {
	}
	public void Build(PictureBox pb) {
		// 色々なコントロールを追加する。
		// サンプルなので、ここではボタンのみ。
		pb.Controls.Add(new Button());
	}
}



更にSub2.dll
コード:
using System;
using System.Windows.Forms;

public class Sub : ISub {
	public Sub() {
	}
	public void Build(PictureBox pb) {
		// 色々なコントロールを追加する。
		// サンプルなので、ここではテキストボックスのみ。
		pb.Controls.Add(new TextBox());
	}
}



Sub1.dllとSub2.dllの両方に同じクラス名Subが作られています。

最後にMain.exe。
コード:
Assembly asm = Assembly.LoadFrom(@"..\..\..\Sub1\bin\debug\Sub1.dll");
// Assembly asm = Assembly.LoadFrom(@"..\..\..\Sub2\bin\debug\Sub2.dll");
ISub sub = asm.CreateInstance("Sub") as ISub;
sub.Build(pictureBox1);


DLLロード部分のみですが、異なるDLLから同一名のクラスを生成し、
ISubインタフェースにキャストしています。
Dll名(Sub1.dll,Sub2.dll)とクラス名(Sub)をファイルから読み込むようにすれば、
実行時にDllが差し替えられますよね。

優希さんの例では、アンマネージドなDLLを作っていましたが、
可能なら上の例のように、全部マネージドにしてみてはどうでしょうか?


次にスレッドの件です。

引用:

優希さんの書き込み (2003-08-14 10:55) より:
上記でも記述しました通り、
子画面でも[ボタン]や[テキストボックス]などいろんな
コントロールを入れてひとつの「画面」として表示します。
だから、入力処理や更新処理など時間がかかる作業が入るのかなぁっと思っているので、
スレッド形式にした方が効率があがるのかと。
間違いでしたら、ご指摘ください。



結論を先に言うと、スレッドにしても必ずしも効率は上がりません。
パフォーマンスアップという点からすると、

・処理が速くなる
・レスポンスが良くなる

が、考えられますが、両者は同じ意味を指しているわけではありません。

例えば、データの検索処理を考えた場合、
・10秒間待たされて、一瞬で表示される
・1秒で応答が返るが、描画が20秒続く。

前者の方が処理は速いですが、最初のレスポンス自体は後者が良いです。

では、スレッドを使うことにより、どのような効果があるでしょうか。

まず、処理の実行時間の短縮。
コレについては、時間が掛かっている処理がディスクアクセスや、
ネットワーク通信などCPUに負荷が掛かっていない場合に、
空いているCPUを有効に使えるので処理速度アップが期待できます。
しかし、メインスレッドで微分方程式を解きながら別スレッドで円周率を求めるとか、
両方ともフルにCPUを使っていそうな場合は、
スレッド切り替えのオーバーヘッド分、遅くなってしまいます。

優希さんの場合、スレッドによる処理速度の向上はありそうでしょうか?


次に、レスポンスに対する効果ですが、
計算など重い処理をスレッドに任せてることで、
メインスレッドはすぐにユーザの要求を受ける準備が出来ます。
また、計算をキャンセル可能にしたりする場合にも有効です。

ただ、計算が終わらない内にユーザに操作(データベース更新とかアプリケーションの終了とか)されて問題はありませんでしょうか?
この辺の考慮に手間が掛かるかも知れません。

それと、別スレッド間でコントロールを作ってやり取りするのは出来ないので、
「計算して、その後コントロールを作成する」が一つの処理になっていたら、
別スレッドに計算処理をやらせ、メインスレッドでは計算が進む毎に、
コントロールを作成する、というようにコントロールの作成と、
時間が掛かる計算処理を分解してやる必要があると思います。


回答になっているか心配ですが、どうでしょうか?
優希
ベテラン
会議室デビュー日: 2003/08/12
投稿数: 92
投稿日時: 2003-08-15 07:08
優希です。
meiさん、ありがとうございます。
返事が遅くなってすいません。

まずは、
> ・実行時に読み込むDLLを変更可能にする
について。

C#もC++も、似ているので、早速試してみたいと思います。
ここで質問なのですが、
以前にも書かれておりましたが
Handleを使わないでコントロール(PictureBox *pic)で
[Main⇒ISub⇒Sub](前回の例では)と引数を渡すと、
エラーになってしまいます。
(エラー内容は、今は把握しておりません。すいません。)
Handleを使うと、うまくいくのです。
これは、コーディングの仕方が間違っているのでしょうか?
渡し方は、ふつうにコントロールのインスタンスを渡して、
子画面でも使おうとしているのですが。



続いて、
> ・パフォーマンスアップの為にスレッドを使う
について。

> スレッドによる処理速度の向上はありそうでしょうか?

メインスレッドでは、特にCPUに負荷が掛かる処理は行わないので、
別スレッドに分けたときには、処理があがりそうな感じなのですが。


> 別スレッド間でコントロールを作ってやり取りするのは出来ないので、

そうなんですかぁ。
同一スレッド間内でやるしかないのですね。
そこら辺を検討してみます。


お忙しいところ、申し訳ありませんが、
宜しくお願いします。
mei
大ベテラン
会議室デビュー日: 2003/04/08
投稿数: 114
投稿日時: 2003-08-15 08:04
おはようございます。

引用:

優希さんの書き込み (2003-08-15 07:08) より:
以前にも書かれておりましたが
Handleを使わないでコントロール(PictureBox *pic)で
[Main⇒ISub⇒Sub](前回の例では)と引数を渡すと、
エラーになってしまいます。


これですが、ひょっとして呼んでいるDLLがアンマネージドになってて、
その所為じゃないでしょうか?(想像なので違ってたらすみません)
何か問題がありそうな部分が分かったら教えて下さい。

引用:

メインスレッドでは、特にCPUに負荷が掛かる処理は行わないので、
別スレッドに分けたときには、処理があがりそうな感じなのですが。


念の為、簡易テストとしてスレッド有りバージョンと無しバージョンを作って、
パフォーマンスを確認してみた方が良いかもしれません。
折角、苦労してスレッド対応して、効果が少なかったら残念ですし。

引用:

> 別スレッド間でコントロールを作ってやり取りするのは出来ないので、

そうなんですかぁ。
同一スレッド間内でやるしかないのですね。
そこら辺を検討してみます。


私の表現が悪くて「スレッド間でコントロールへのアクセスが出来ない」
と、読めそうだったので念のため。

例えばA,Bというスレッドがあって、
Aでフォームを作ってBでパネルを作ってくっつけることは出来ないですが、
Aでフォームもパネルも作って、A,Bからアクセスするのは出来るハズです。

蛇足だったら、すみません。

スキルアップ/キャリアアップ(JOB@IT)