IUnknown ** 指针的指针 释放问题

任明星Ming 2009-10-22 05:06:25
各位朋友:

我初学写COM组件,有个程序需要用DLL来传递COM对象,传递对象的接口被定义为IUnknown ** 指向指针的指针。我写了如下代码,但Release COM对象却出现了错误。关键代码如下(其他代码我已经过滤):

int32 Read_Xml_File(char * XML_Path, LStrHandle Out_Put_File, IUnknown ** lvIUnknown)
{
CComPtr<MSXML::IXMLDOMDocument> spDoc;

HRESULT hr;
IUnknown * pIUnknown;

//一般的IUnknown 指针 pIUnknown
hr = spDoc->QueryInterface(IID_IXMLDOMDocument, (void**)&pIUnknown);

//指向IUnknown 对象的指针 lvIUnknown
hr = pIUnknown->QueryInterface(IID_IXMLDOMDocument,(void**)(&(*lvIUnknown)));

//pIUnknown Release没问题
pIUnknown->Release();

//这里释放就导致程序崩溃了
(*lvIUnknown)->Release();
}

我应该怎么做才能确保Release成功?
...全文
497 34 打赏 收藏 转发到动态 举报
写回复
用AI写文章
34 条回复
切换为时间正序
请发表友善的回复…
发表回复
任明星Ming 2009-10-24
  • 打赏
  • 举报
回复
[Quote=引用 34 楼 icosagon 的回复:]
我不是告诉你了吗!
//这句是为了返回给lvIUnknown双重指针的
//使用lvIUnknown的人要自己调用release,否则内存泄漏。
*lvIUnknown = spDoc.p;
spDoc.p = NULL;

这样就不会内存泄漏
int32 ReleasePointer(IUnknown ** vIUnknown)
{
(*vIUnknown)->Release();
::CoUninitialize();
return 0;
}

但是用vIUnknown指针的人每次调用QueryInterface去得到其它接口指针时,得到的接口指针必须要调用
Release,
而且看你的代码风格,也不是很好,内存管理很乱
[/Quote]

int32 ReleasePointer(IUnknown ** vIUnknown)
{
(*vIUnknown)->Release();
*lvIUnknown = 0;
::CoUninitialize();
return 0;
}
还得再加个零才能把内存地址清空。
多谢你对我提的建议,代码我会去好好改善:)
赵4老师 2009-10-23
  • 打赏
  • 举报
回复
《COM本质论》
icosagon 2009-10-23
  • 打赏
  • 举报
回复
把你代码全贴出来,我给你的不会有内存泄漏
任明星Ming 2009-10-23
  • 打赏
  • 举报
回复
[Quote=引用 28 楼 adlay 的回复:]
如果太搅了暴力点的办法是直接检查 Release 的返回值,反复调用,直到返回 0 为止.
不过最好还是仔细跟踪吧,应该是某处多加了引用.
[/Quote]

好的来点暴力的方法看看
任明星Ming 2009-10-23
  • 打赏
  • 举报
回复
[Quote=引用 26 楼 adlay 的回复:]
如果传进来的 vIUnknown 是 vIUnknown = new IUnknown*; 出来的,在不用的时候加一句 delete vIUnknown; 就可以了.
如果是通过 IUnknown* x; 去 &x 传进来的则不需要释放了.
[/Quote]

我的这个 IUnknown ** vIUnknown 是个双重指针,没有经过初始化。
是直接 *lvIUnknown = spDoc.p; 把智能指针赋值给它的。
就是在反复调用时会出现内存泄漏,加了Release也不行。
www_adintr_com 2009-10-23
  • 打赏
  • 举报
回复
如果太搅了暴力点的办法是直接检查 Release 的返回值,反复调用,直到返回 0 为止.
不过最好还是仔细跟踪吧,应该是某处多加了引用.
任明星Ming 2009-10-23
  • 打赏
  • 举报
回复
[Quote=引用 26 楼 adlay 的回复:]
如果传进来的 vIUnknown 是 vIUnknown = new IUnknown*; 出来的,在不用的时候加一句 delete vIUnknown; 就可以了.
如果是通过 IUnknown* x; 去 &x 传进来的则不需要释放了.
[/Quote]

我的这个 IUnknown ** vIUnknown 是个双重指针,没有经过初始化。
是直接 *lvIUnknown = spDoc.p; 把智能指针赋值给它的。
就是在反复调用时会出现内存泄漏,加了Release也不行。
www_adintr_com 2009-10-23
  • 打赏
  • 举报
回复
如果传进来的 vIUnknown 是 vIUnknown = new IUnknown*; 出来的,在不用的时候加一句 delete vIUnknown; 就可以了.
如果是通过 IUnknown* x; 去 &x 传进来的则不需要释放了.
任明星Ming 2009-10-23
  • 打赏
  • 举报
回复

[Quote=引用 21 楼 icosagon 的回复:]
你用了针对COM的智能指针,可以不用在调查询接口的函数了,而且你智能指针和普通COM指针混用,很容易搞错。

C/C++ codeIUnknown* pIUnknown;//lvIUnknown = new (IUnknown*); COM对象和普通对象还是不同的
ErrState= Err_NoError;//Create XML Document Object
::CoInitialize(NULL);
CComPtr<MSXML::IXMLDOMDocument> spDoc;
HRESULT hr;
hr= spDoc.CoCreateInstance(__uuidof(MSXML::DOMDocument));//Load XML File From Pathbool bSuccess=true;
bSuccess= Load_XML(XML_Path, spDoc);//不用查IXMLDOMDocument的接口了,智能指针CoCreateInstance的时候已经有了,作为模板//hr = spDoc->QueryInterface(IID_IXMLDOMDocument, (void**)&pIUnknown);//AddRef无需开发者关心,如果不用智能指针,需要调用release,否则也无需关心//这四条代码是我们讨论的地方//*lvIUnknown = pIUnknown;//(*lvIUnknown)->AddRef();//(*lvIUnknown)->Release();//pIUnknown->Release();
CComBSTR XML_File;if(SUCCEEDED(hr))
{
hr= spDoc->get_xml(&XML_File);
}


File_Content= _com_util::ConvertBSTRToString(XML_File.m_str);
iLength= strlen(File_Content);//这句是为了返回给lvIUnknown双重指针的//使用lvIUnknown的人要自己调用release,否则内存泄漏。*lvIUnknown= spDoc.p;
spDoc.p= NULL;//可有可无,但在智能指针和普通指针混用情况下要小心//spDoc.Release();::CoUninitialize();
[/Quote]

像这样Release vIUnknown 好像不行,还是会内存泄漏
不过程序已经能正常的传递地址了。
我该怎么才能释放这个 IUnknown **双重指针呢??

int32 Test(IUnknown ** vIUnknown)
{

(*vIUnknown)->Release();
*vIUnknown = NULL;
::CoUninitialize();
return 0;
}
icosagon 2009-10-23
  • 打赏
  • 举报
回复
我不是告诉你了吗!
//这句是为了返回给lvIUnknown双重指针的
//使用lvIUnknown的人要自己调用release,否则内存泄漏。
*lvIUnknown = spDoc.p;
spDoc.p = NULL;

这样就不会内存泄漏
int32 ReleasePointer(IUnknown ** vIUnknown)
{
(*vIUnknown)->Release();
::CoUninitialize();
return 0;
}

但是用vIUnknown指针的人每次调用QueryInterface去得到其它接口指针时,得到的接口指针必须要调用
Release,
而且看你的代码风格,也不是很好,内存管理很乱
任明星Ming 2009-10-23
  • 打赏
  • 举报
回复
[Quote=引用 31 楼 icosagon 的回复:]
把你代码全贴出来,我给你的不会有内存泄漏
[/Quote]

麻烦icosagon了:
这是一个DLL接口,用于创建一个IXMLDOMDocument 对象,并把对象传递给IUnknown ** lvIUnknown
我按照您给的方法改了,的确不会出现内存泄漏。
但是把它传递给 lvIUnknown 双重指针后就会出现内存泄漏,请看下面的DLL接口:
int32 Read_Xml_File(char * XML_Path, LStrHandle Out_Put_File, IUnknown ** lvIUnknown)
{
char * File_Content;
char * ErrDescript;

try
{
//Create XML Document Object

ErrState = Err_NoError;
::CoInitialize(NULL);
CComPtr<MSXML::IXMLDOMDocument> spDoc;
HRESULT hr;
hr = spDoc.CoCreateInstance(__uuidof(MSXML::DOMDocument));

//Load XML File From Path

bool bSuccess = true;
bSuccess = Load_XML(XML_Path, spDoc);

CComBSTR XML_File;
hr = spDoc->get_xml(&XML_File);

*lvIUnknown = spDoc.p;
spDoc.p = NULL;

File_Content = _com_util::ConvertBSTRToString(XML_File.m_str);
iLength = strlen(File_Content);


//::CoUninitialize();

if (bSuccess == false)
{
ErrDescript = "Invalid XML File,Please Check Your XML File!";
throw XML_File;
}

}
catch (...)
{
File_Content = ErrDescript;
iLength = strlen(File_Content);
ErrState = Err_LoadError;
}

Get_LV_String(File_Content, Out_Put_File, iLength);
free(Out_Put_File);
return ErrState;
}

这是释放IUnknown ** lvIUnknown的DLL接口:
这里就释放不掉内存,程序能正常运行。
我是不是智能指针没有释放对,但不知道该怎么去释放这个指针的指针
int32 ReleasePointer(IUnknown ** vIUnknown)
{

(*vIUnknown) = NULL;
::CoUninitialize();
return 0;
}
如果我不用这个IUnknown ** 这个双重是不会出现内存泄漏的问题。
但问题是其他编程语言要引用这个DLL所以只能通过双重指针的方式传递过去。一旦用了双重指针就没办法去清空内存了。头疼......
任明星Ming 2009-10-22
  • 打赏
  • 举报
回复
谢谢各位朋友的提醒,我不胜感激,我会再好好的修改我的程序的,直到我成功为止。
特别谢谢icosagon adlay 以及 bourbaki.
我再加把劲好好看看。
icosagon 2009-10-22
  • 打赏
  • 举报
回复
对了还有个问题,::CoUninitialize(); 这句也去掉,否则COM支持库都没了,COM也没法用了
arong1234 2009-10-22
  • 打赏
  • 举报
回复
我怀疑两点:
1。 智能指针的出现弄乱了引用计数导致对象被多释放了一次
2。释放操作在CoUninitialize之后,导致COM库状态不正确
icosagon 2009-10-22
  • 打赏
  • 举报
回复
你用了针对COM的智能指针,可以不用在调查询接口的函数了,而且你智能指针和普通COM指针混用,很容易搞错。

IUnknown * pIUnknown;


//lvIUnknown = new (IUnknown*); COM对象和普通对象还是不同的

ErrState = Err_NoError;

//Create XML Document Object

::CoInitialize(NULL);
CComPtr <MSXML::IXMLDOMDocument> spDoc;
HRESULT hr;
hr = spDoc.CoCreateInstance(__uuidof(MSXML::DOMDocument));

//Load XML File From Path

bool bSuccess = true;
bSuccess = Load_XML(XML_Path, spDoc);

//不用查IXMLDOMDocument的接口了,智能指针CoCreateInstance的时候已经有了,作为模板

//hr = spDoc->QueryInterface(IID_IXMLDOMDocument, (void**)&pIUnknown);

//AddRef无需开发者关心,如果不用智能指针,需要调用release,否则也无需关心
//这四条代码是我们讨论的地方
//*lvIUnknown = pIUnknown;
//(*lvIUnknown)->AddRef();
//(*lvIUnknown)->Release();
//pIUnknown->Release();

CComBSTR XML_File;
if(SUCCEEDED(hr))
{
hr = spDoc->get_xml(&XML_File);
}


File_Content = _com_util::ConvertBSTRToString(XML_File.m_str);
iLength = strlen(File_Content);

//这句是为了返回给lvIUnknown双重指针的
//使用lvIUnknown的人要自己调用release,否则内存泄漏。
*lvIUnknown = spDoc.p;
spDoc.p = NULL;

//可有可无,但在智能指针和普通指针混用情况下要小心
//spDoc.Release();
::CoUninitialize();
www_adintr_com 2009-10-22
  • 打赏
  • 举报
回复
可以这样 typedef IUnknown* UnknwonPtr 之后替换掉一个指针就好理解点了:

int32 Read_Xml_File(... , UnknownPtr* pUnknownPtr)
{
...
}

使用这个函数要么:
UnknownPtr x;
Read_Xml_File(..., &x);
或者:
UnknownPtr* px = new UnknownPtr;
Read_Xml_File(..., px);

但是如果:
UnknownPtr* px;
Read_Xml_File(..., px) // px 未初始化就使用,行为未定义。

如果是:
UnknownPtr* px;
Read_Xml_File(..., px)
{
px = new UnknownPtr; // 后面使用的已经初始化了,不会报错,但是修改的是参数的拷贝,不能传递回去的。 这时只能通过 return px; 才能传递回去。
}

如果还有障碍,把 UnknownPtr 换成是一个 int 再来看,应该可以看得明白了。
任明星Ming 2009-10-22
  • 打赏
  • 举报
回复
我想试验下,我的Release能不能把内存清空,不造成内存泄露。最担心内存泄露

[Quote=引用 17 楼 bourbaki 的回复:]
你先addref,立马又release,这是没有意义的动作
[/Quote]
任明星Ming 2009-10-22
  • 打赏
  • 举报
回复
谢谢,我再去了解一下。我估计是不是我释放了对象,那指向对象的指针就变成了野指针,胡乱的指向别的内存地址去了?


[Quote=引用 15 楼 bourbaki 的回复:]
addref一般实现在QueryInterface里面。release并不是真的释放内存,而是把reference count减去1,如果这个count等于0,才真的释放内存。

lz可以看看http://www.codeproject.com/KB/COM/comintro2.aspx,讲得很清楚
[/Quote]
bourbaki 2009-10-22
  • 打赏
  • 举报
回复
你先addref,立马又release,这是没有意义的动作
任明星Ming 2009-10-22
  • 打赏
  • 举报
回复
[Quote=引用 14 楼 adlay 的回复:]
引用 11 楼 renstarone 的回复:
谢谢adlay,但是我改成以下这样:
int32 Read_Xml_File(char * XML_Path, LStrHandle Out_Put_File, IUnknown ** lvIUnknown)
{
  lvIUnknown = new (IUnknown*);
  CComPtr <MSXML::IXMLDOMDocument> spDoc;
 
  HRESULT hr;
  IUnknown * pIUnknown;

      //一般的IUnknown 指针 pIUnknown
  hr = spDoc->QueryInterface(IID_IXMLDOMDocument, (void**)&pIUnknown);

  *lvIUnknown = pIUnknown;
  (*lvIUnknown)->AddRef();
  (*lvIUnknown)->Release();
}
的确程序不会崩溃,内存也不会泄漏,可我的输出的地址就全是:0x00000000了
我需要这个IUnknown ** lvIUnknown给其他程序调用,如果全是0x00000000就没办法调用啦。


你不应该在这个函数里面  new IUnknown* 啊,要在调用的地方 new 好之后传进来:
你可以这样调用:
IUnknown* p;
Read_Xml_File(...., &p);
或者:
IUnknown** pp = new IUnknown*;
Read_Xml_File(...., pp);

如果这样
IUnknown** pp;
Read_Xml_File(...., pp);
就有问题了。

这个还是对指针和 C 按值传递参数的理解了,还不好说清楚,慢慢想想吧。


[/Quote]
谢谢你,我再看看去。有点头晕。为了这个问题搞了一个下午。
加载更多回复(15)
一 组件基础 1 软件开发的阶段 1.1 结构化编程 采用自顶向下的编程方式,划分模块 和功能的一种编程方式。 1.2 面向对象编程 采用对象的方式,将程序抽象成类, 模拟现实世界,采用继承、多态的方式 设计软件的一种编程方式。 1.3 面向组件编程 将功能和数据封装成二进制代码,采用 搭积木的方式实现软件的一种编程方式。 2 组件和优点 2.1 组件 - 实际是一些可以执行的二进 制程序,它可以给其他的应用程序、操 作系统或其他组件提供功能 2.2 优点 2.2.1 可以方便的提供软件定制机制 2.2.2 可以很灵活的提供功能 2.2.3 可以很方便的实现程序的分布式 开发。 3 组件的标准 - COM(Component Object Model ) 3.1 COM是一种编程规范,不论任何开发语言 要实现组件都必须按照这种规范来实现。 组件和开发语言无关。 这些编程规范定义了组件的操作、接口的 访问等等。 3.2 COM接口 COM接口是组件的核心,从一定程度上 讲"COM接口是组件的一切". COM接口给用户提供了访问组件的方式. 通过COM接口提供的函数,可以使用组件 的功能. 4 COM组件 4.1 COM组件-就是在Windows平台下, 封装在动态库(DLL)或者可执行文件(EXE) 中的一段代码,这些代码是按照COM的 规范实现. 4.2 COM组件的特点 4.2.1 动态链接 4.2.2 与编程语言无关 4.2.3 以二进制方式发布 二 COM接口 1 接口的理解 DLL的接口 - DLL导出的函数 类的接口 - 类的成员函数 COM接口 - 是一个包含了一组函数指针 的数据结构,这些函数是由组件实现的 2 C++的接口实现 2.1 C++实现接口的方式,使用抽象类 定义接口. 2.2 基于抽象类,派生出子类并实现 功能. 2.3 使用 interface 定义接口 interface ClassA { }; 目前VC中,interface其实就是struct 3 接口的动态导出 3.1 DLL的实现 3.1.1 接口的的定义 3.1.2 接口的实现 3.1.3 创建接口的函数 3.2 DLL的使用 3.2.1 加载DLL和获取创建接口的函数 3.2.2 创建接口 3.2.3 使用接口的函数 4 接口的生命期 4.1 问题 在DLL中使用new创建接口后,在用户 程序使用完该接口后,如果使用delete 直接删除,会出现内存异常. 每个模块有自己的内存堆(crtheap) EXE - crtheap DLL - crtheap new/delete/malloc/free默认情况 下都是从自己所在模块内存堆(crtheap) 中分配和施放内存.而各个模块的 这个内存堆是各自独立.所以在DLL中 使用new分配内存,不能在EXE中delete. 4.2 引用计数和AddRef/Release函数 引用计数 - 就是一个整数,作用是 表示接口的使用次数 AddRef - 增加引用计数 +1 Release - 减少引用计数 -1, 如果 当引用计数为0,接口被删除 4.3 使用 4.3.1 创建接口 4.3.2 调用AddRef,增加引用计数 4.3.3 使用接口 4.3.4 调用Release,减少引用计数 4.4 注意 4.4.1 在调用Release之后,接口指针 不能再使用 4.4.2 多线程情况下,接口引用计数 要使用原子锁的方式进行加减 5 接口的查询 5.1 每个接口都具有唯一标识 GUID 5.2 实现接口查询函数 QueryInterface 6 IUnknown 接口 6.1 IUnknown是微软定义的标准接口 我们实现所有接口就是继承这个接口 6.2 IUnknown定义了三个函数 QueryInterface 接口查询函数 AddRef 增加引用计数 Release 减少引用计数 7 接口定义语言 - IDL(Interface Definition Language ) 7.1 IDL和MIDL IDL - 定义接口的一种语言,与开发 语言无关. MIDL.EXE - 可以将IDL语言定义接口, 编译成C++语言的接口定义 7.2 IDL的基础 import "XXXX.idl" [ attribute ] interface A : interface_base { } 7.2.1 Import 导入,相当于C++的 #include 7.2.2 使用"[]"定义区域,属性描述 关键字 1) object - 后续是对象 2) uuid - 定义对象GUID 3) helpstring - 帮助信息 4) version - 版本 5) point_default - 后续对象 中指针的默认使用方式 比如: uniqune - 表示指针可以 为空,但是不能修改 7.2.3 对象定义 1) 父接口是IUnknown接口 2) 在对象内添加函数,函数定义必须 是返回 HRESULT. HRESULT是32位整数,返回函数是否 执行成功,需要使用 SUCCESSED和 FAILED宏来判断返回值.

64,683

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧