COM+环境下ADO的Connection和Recordset是否无法跨进程使用? (200分)

  • 主题发起人 主题发起人 solaray
  • 开始时间 开始时间
S

solaray

Unregistered / Unconfirmed
GUEST, unregistred user!
使用Delphi编写一个ActiveX DLl
Dll是发布一个接口从外界获取一个Connection
然后在内部创建一个Recordset使用这个外部的Connection

ASP代码:
代码:
Set CN = Server.CreateObject("ADODB.Connection")
CN.CursorLocation = 3
CN.Open DB_STRING

Set ConnObj = Server.CreateObject("ZmConnTest.Conn") 

ConnObj.Connection = CN
response.Write (ConnObj.Connection.Connectionstring)
ConnObj.Open '此处错误
Set ConnObj = Nothing
结果发现在ASP调用COM下运行正常,一旦将组件放入COM+即报错:

[red]0x800A0BB9 "Arguments are of the wrong type, are out of acceptable range,or are in conflict with one

another."[/red]

我的分析:
ADO的Connection以及Recordset无法跨进程使用
本例中的Connection由ASP创建,位于一个叫做IIS Out-Of-Process Pooled Applications的COM+程序内(某个Dllhost.exe进程)

在COM环境下,我的组件是进程内dll,和该ASP - COM+程序位于同一个dllhost.exe内,所以正常

一旦部署入COM+成为一个独立的Application,组件位于另外一个独立的Dllhost.exe内
所以出错了

请问我的分析正确吗?


另外我现在的解决办法是写一个另外的ConnectionFactory组件,跟随我的组件部署于同一个Application内提供

connection,就运行正常了。
请问还有无更好的办法?

附上全部代码:
代码:
unit ZmConnImpl;

{$WARN SYMBOL_PLATFORM OFF}

interface

uses
  ComObj, ActiveX, AspTlb, ZmConnTest_TLB, StdVcl,ADODB,ADODB_TLB
    ,Variants;

type
  TConn = class(TASPMTSObject, IConn)
  public
    procedure Initialize; override;
    destructor Destroy;override;
  protected
    function Get_Connection: Connection15; safecall;
    procedure Set_Connection(const Value: Connection15); safecall;
    function Get_test: WideString; safecall;
    procedure Set_test(const Value: WideString); safecall;
    procedure Open; safecall;
    function Get_Recordset: Recordset15; safecall;
    procedure Set_Recordset(const Value: Recordset15); safecall;
  private
    FIConn : Connection15;
    FIRecordset : Recordset15;
    FORecordset : OleVariant;
  end;

implementation

uses ComServ;

procedure TConn.Initialize;
begin
  inherited;
  FIRecordset := coRecordset.Create;
end;

destructor TConn.Destroy;
begin
  FIRecordset := nil;
  FIConn := nil;
  inherited Destroy;
end;

function TConn.Get_Connection: Connection15;
begin
  Result := FIConn;
end;

procedure TConn.Set_Connection(const Value: Connection15);
begin
  FIConn := Value;
end;

function TConn.Get_test: WideString;
begin
  Result := FIRecordset.Fields.Item['us_name'].Value;
end;

procedure TConn.Set_test(const Value: WideString);
begin

end;

procedure TConn.Open;
var
  vSource , vConn :OleVariant;
begin
  vSource := 'SELECT * FROM t_users';
  vConn := FIConn;
  FIRecordset.Open(vSource ,vConn ,adOpenDynamic,adLockOptimistic,adCmdText);
end;

function TConn.Get_Recordset: Recordset15;
begin
  Result := FIRecordset;
end;

procedure TConn.Set_Recordset(const Value: Recordset15);
begin
  FIRecordset := Value;
end;

initialization
  TAutoObjectFactory.Create(ComServer, TConn, Class_Conn,
    ciMultiInstance, tmApartment);
end.
 
Delphi COM编程的一个BUG
由于工作需要,得在一个DELPHI写的普通DLL中来调用一个VC写的进程内COM组件来实现功能。DLL很顺利就编写成功,但是在使用一个EXE程序来调试这个DLL时,DLL即总是报异常错误。,由于DLL所调用的这个使用VC写的这个COM组件在EXE中调用调试完全通过,所以其出错的可能性极小。我又非常的仔细检查了个用DELPHI写的DLL,也没有发现错误,却发现这个异常使用try和except还是可以拦住的,也就是说肯定是DELPHI本向所产生的异常。这就奇怪了,难道是DELPHI本身的问题造成的这种情况,我不敢肯定,所以只得从VCL源码一级来对这个程序进行除错。

在DLL中是COM对象是通过调用DELPHI的RTL函数CreateComObject来建立的,于是我先从这个函数入手开始调试。这个RTL函数很简单,只有一行代码:

OleCheck(CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER or

CLSCTX_LOCAL_SERVER, IUnknown, Result));

即使用Windows API函数CoCreateInstance函数来建立并返回COM组件的实例。这个API函数外面套了一层OleCheck函数,Delphi RTL函数OleCheck的作用是如果CoCreateInstance没有返回S_OK即COM组件建立成功,那么OleCheck将产生一个异常。我程序当中的异常会不会是这OleCheck函数调用所报出来的呢,于是我试着将OleCheck去掉,让把CoCreateInstance函数把其返回值显示出来。我试着运行修改过后的程序,呵呵CoCreateInstnace函数所返回的值果然不对,是2147746288十六进制数是800401F0。在MSDN中查得此值表示的含意是CoInitialize函数没有被调用。CoInitialize是COM组件的初始化函数,是DELPHI封装COM时绝对应该负责调用的东西,难道这次的错误真是由于没有调用CoInitialize函数所造成的,我半信半疑。试着将CoInitialize函数的调用加到了DLL单元的initialization部分,将CoUninitialize函数加到了DLL单元的finalization部分。再次使用外部EXE来调试这个DLL,果然不出错了。

错误是找到了,但是错误是怎样造成的呢。经过进一步的查找。发现错误出在Delphi的ComObj单元initialization部分。此处有以下一段代码:

if not IsLibrary then

begin

SaveInitProc := InitProc;

InitProc := @InitComObj;

end;

这段代码判断当前程序是否是一个DLL(通过IsLibrary变量来判断)如果不是的话,则将初始化的过程设为InitComObj函数。而DELPHI执行调用CoInitialize函数的操作正是在这个InitComObj函数中调用的,难快会出错。最终究其原因可能是宝兰的开发人员只注意到了ActiveX Library这类进程内COM框架的组件出始化的情况,而忘记了还可能有在普通DLL中调用COM组件来实现功能的情况造成了上述的错误。至此问题全部解决,不过笔者还想多说两句,就笔者认为对COM的支持是DELPHI做得比较差的地方之一,像这类错误在VC当中就根本不会出现的,真的是很希望宝兰的开发人员能在DELPHI6中把DELPHI对COM的支持做得更好,使DELPHI变得更加的完美。

看一下这个,对你有帮助没有???
 
不行,还是一样

我的问题不在于呼叫CoInitialize
而是COM下面通过,COM+下面不能通过呀
我猜测是不是不能跨进程导致的
 
你为什么不去李唯的Delphi 5.X ADO_MTS_COM+高级程序设计篇中找找,如果不行的话,那里面至少因该有说明吧
 
找了一下,没有相关的说明
他里面说的较少Web架构的COM+程序设计
关于那个0x800A0BB9 的错误我查过MSDN,大意是在原来既可以传递对象又可以传递字符串的参数现在只能接受对象而导致的

另外我用VB写了个类似的Dll,出现一样的错误
其实我现在也找到了解决办法了,似乎是有点钻牛角尖了,呵呵。
 
看看http://www.thedelphimagazine.com/samples/comthread/comthreading.htm,可能跟这个有关。
另外为什么不直接传递连接字符串,传接口代价很高。
 
to xeen:
传递字符串的方法当然可行
但是传递字符串有个问题是导致每个类的实例内部去创建一个connection了
这样当使用人数多的时候就成问题了

另外还有就是事务控制问题,假如每个实例的connection不同,进行事务控制可能不好办

其实我现在的使用一个独立的ConnectionFactory的做法我还想到一个好处就是
在我的ConnectionFactory类内部实现一个链接池
使用过的链接可以不丢弃,保存在一个TInterfaceList里面
由Connection请求的时候把空闲的Connection放出去

不过实现起来会比较难就是了

你的那篇文章我得花点时间看看
 
Xeen的文章的确提到这个问题

里面说到假如要在直接传递Interface,两边必须在同一个[red]Apartment[/red]内才行
而所谓的Apartment我的理解就是一些因为某个线程和它所呼叫的其他组件的线程组成的集合。

既然我的组件进入com+后已经跨了进程,那么必然跨了apartment。所以调用失败了
但是文章里面提到的抛出的错误应该是RPC_E_WRONG_THREAD ,我现在抛出的错误却不是这个,还是没有确定

另外,里面提到两个解决这种跨Apartment的接口传递问题的方法
分别是[red]CoMarshalInterThreadInterfaceInStream[/red]和一个叫做[red]GIT[/red](全局接口表)的咚咚
有没有有使用过的经验呢?

请大家继续讨论


 
没人讨论,给xeen分。
 
接受答案了.
 

Similar threads

S
回复
0
查看
3K
SUNSTONE的Delphi笔记
S
S
回复
0
查看
2K
SUNSTONE的Delphi笔记
S
I
回复
0
查看
583
import
I
后退
顶部