NeroCOM, C#, QueryInterface/casting failing and COM apartment threading models etc

vbimport

#1

Hello all,

Since upgrading to Nero 6.6.0.13 I am having problems with NeroCOM from my C# app whereby I get either odd casting exceptions or exceptions to the effect of “QueryInterface for interface NEROLib.INeroDrive6 failed” etc. It’s running code that worked fine on 6.6.0.12 (apart from the bug that was fixed in 6.6.0.13 w.r.t. NeroCOM expiring and entering demo mode).

In this particular situation I’m referring to, I’m using both WaitForMedia and EjectCD. My application is such that the COM objects are created in the GUI thread but are passed to & used by another background thread that e.g. does the WaitForMedia etc. on the NeroDrive object. In other words, the user selects a drive and the GUI “waits” while the background thread does various processing operations with the associated drive. Now the issue is that as soon as the background thread tries to invoke one of those methods on the NeroDrive object the above-mentioned exceptions result.

I have done some experimentation and I have come to the following conclusion: Things seem to work if you decorate Main with [STAThread] and you use the COM objects created (e.g. NeroDrive) from the thread that created them. As soon as you use [MTAThread] (I tried this just in case) or you use the COM objects from a thread other than the one that created them, things break. Now, I’m no expert on COM at all and maybe I’m missing something, but it look to me as if from 6.6.0.12 -> 6.6.0.13 something has changed that has resulted in COM objects being “limited” to the thread that created them. Is this possible?

I would really really appreciate some brilliant suggestions to help me out… If this is a design change on Ahead’s part and not a bug, and if it’s going to be like this from now on then OK I’ll have to look for another way of doing this, but just maybe it’s a bug…?

Thanks,
Martin


#2

Update: I might be wrong but the stuff I’ve just tried now seems to indicate that calling methods on these COM objects only work when invoked from the main (GUI) thread… which means that it’s alltogether impossible to chat to NeroCOM from another thread…

???


#3

Hi,
In your separate thread code, add the following code:
System.Threading.Thread.CurrentThread.ApartmentState = System.Threading.ApartmentState.STA;
This will switch your thread to STA.
Regards,
Ianier Munoz
http://www.chronotron.com


#4

Thanks for the idea, but that doesn’t seem to have helped… :frowning:


#5

I appear to be having a similar (related?) problem using Visual Basic (Visual Studio 2005 beta 1).

I have a textbox on my form which I want to use to display log messages, called txtStatus. In the event handler for neroDrive.OnAddLogLine I have code like thos:

txtStatus.Text &= msg

(msg is the String that is passed to the event handler) and when I run my application I get an error stating that txtStatus cannot be accessed because the thread is not the one that created it. I can MsgBox the msg and see it’s data, but when I try to access txtStatus from within the event handler I crash.

I tried the suggestion above about setting the ApartmentState of the thread (the first statement in the event handler) but it did not solve the problem.

Any ideas on how I can access my GUI controls from within the event handlers in VB.NET 2005? Thanks.

  • Bob

#6

Hi. Sorry, this is not the same problem. In .NET you can only access GUI-related objects (e.g. the Buttons, Labels etc.) from the thread that created those objects. In order for another thread to access them (which is what happens when an async Nero operation runs in the background) you have to use the Form.Invoke “protocol”. It works very well but it can be a bit of mission to do especially if you just want to simply set a Label.Text value or something.


#7

If there’s a Nero developer or forum moderator our there reading this, please please please could you comment on the original issue. I really need to know whether there is something (new) I’m missing or whether something important has changed w.r.t. the threading in the COM interface and objects created by it.

:bow: :bow: :bow:


#8

I’m not familiar w/ nero SDK, but in COM in order to pass a COM pointer from one thread to another you have to do something like CoMarshalInterThreadInterfaceInStream, and CoGetInterfaceAndReleaseStream. You might have to do COM interop for those APIs.


#9

I’m not a C#/VB developer, but from what the NeroAPI documentation states

  1. Callbacks must be thread-safe, since they might be called from a different thread. This underlines what mvockerodt wrote in reply to the VB-issue of BobLafleur.

  2. “NeroOpenDevice”: “In general, an application may not access devices from multiple threads simultaneously.” It is a question of interpretation what “simultaneously” means here, since we can consider access simultaneously even if 2 threads just open a device.

Moreover, as OLE/COM Object Viewer revealed, the Nero interfaces are almost all threading model=both, so they should be thread-safe. Nevertheless, I’d give the idea of marshalling interface access a try.

Is your primary thread running in a STA?


#10

Hello,

The “default” for C# applications is to make the primary thread STA; I left it like that. Everything is thread-safe where appropriate so that’s not an issue. Sadly (or not) I do not really wish to delve into the complexities of COM interop at this stage of my life. My deadlines required me to find another solution and I eventually resorted to coordinating the operation from the background thread as is required for various other reasons, and this thread has to “send events” to the GUI thread [essentially via Form.Invoke and some async queueing] to tell it what/when to do anything involving a Nero object. Yes it’s not exactly as clean a solution as if I just use the objects directly, but it does fortunately work very well.

The issues that I still can’t understand are: (1) It all worked prior to 6.6.0.13(12?), and (2) even if the object is created in another thread that same thread cannot use it. Only the main GUI thread can use it. Bizarrrrre.

Well, I have a work-around and my application is now complete so any other comments won’t necessarily help me now per se, but I would still be interested to see them…


#11

Hi,

I’m just a beginner in writing C#/.NET applications, but I need to work with NeroCOM. I loaded the project “NeroFiddlesCOM.NET” into my Visual Studio .NET 2003, but at first some of the references didn’t work. Namely, the NeroLIB and NeroVisionAPI references needed to be changed to Interop.NeroLIB and Interop.NeroVisionAPI, which could be find in the project’s current directory.

Next, I tried to compile and run the project but at runtime, I get this following error:

System.InvalidCastException: QueryInterface for interface NEROLib.INero5 failed.
at NEROLib.NeroClass.GetDrives(NERO_MEDIA_TYPE lVal)
at NeroFiddlesCOM.NET.MainForm.OnLoad(EventArgs e) in d:\work
ero api
erosdk-1.06
erocom\samples
erofiddlescom.net\main.cs:line 65

Maybe someone can tell me what this is about and direct me to a fully-working app which uses NeroCMD. In my application, I only need NeroCMD to perform backup of several files on a CD each day (so you can imagine that it’s quite a simple task). Thank you very much!

I’m using :
Windows 2000 Professional, Service Pack 4
Visual Studio .NET 2003
.NET Framework v1.1
Nero 6.0.0.0
Nero SDK 1.06

I’m sorry for posting to this thread, but I’m doing this because people who seem to have gotten things working have posted here.

Andrei Ismail


#12

Do you have Nero properly installed?


#13

iandrei, I didn’t actually solve the original issue but I did manage to work around it. Have you tried a later update of Nero? e.g. 6.6.0.13?


#14

Hello,

Thank you for your reply. After posting here, I came up with the idea of updating Nero, and I did it. Now the example is working, but it doesn’t contain what interests me.

Can anyone tell me how to use the NeroFile and NeroFolder classes in order to import all previous sessions on a CD and burn a new one with the contents of a local folder? A source file would be greatly appreciated! I tried to google it, but it seems that very few people are using the Nero API. Also, the existence of enough free space on the CD is also an issue (I’m not sure this can be determined without any user interaction). Can anyone help ?


#15

Take a look at http://club.cdfreaks.com/showthread.php?t=140830#post1050629 for a sample of import.


#16

Hello,

Thank you for your prompt and very helpful messages. Thanks to your really-well-written source-code posted on this forum, and to the NeroFiddlesCOM.NET sample, I have figured out a way to import the tracks in C# (I’m using C#), but now I have the following question:

After I have imported the tracks, if I want to make some modifications to the previously written files and/or add new ones how do I do it? My “solutions” is the following: after I load the old tracks into the file/folder tree structure provided by Nero, I alter the structure accordingly (for example delete the files I wanted to delete, add the ones I wanted to add, etc) and after that, try to burn the new ISO track!

My code is the following:


using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Data;
using NEROLib;

namespace Test_Nero
{
	/// <summary>
	/// Summary description for Form1.
	/// </summary>
	public class frmTestNero : System.Windows.Forms.Form
	{
		/// <summary>
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;

        private NEROLib.NeroClass          nClass = new NEROLib.NeroClass();
        private NEROLib.NeroDrivesClass    nDrives;
        private NEROLib.NeroISOTrackClass  nISOTrack = new NeroISOTrackClass();
        private NEROLib.NeroFolderClass    nRootFolder = new NeroFolderClass();
        private NEROLib.NeroFileClass      nFile = new NeroFileClass();
        private NEROLib.NeroDrive          nDrive;

        private NEROLib._INeroDriveEvents_OnDoneCDInfoEventHandler evOnDoneCDInfo = null;
        private System.Windows.Forms.Button button1;
        private NEROLib._INeroDriveEvents_OnDoneImportEventHandler evOnDoneImport = null;

		public frmTestNero()
		{
			InitializeComponent();
            
            nDrives = (NEROLib.NeroDrivesClass) nClass.GetDrives(NEROLib.NERO_MEDIA_TYPE.NERO_MEDIA_CD);
            nDrive  = (NEROLib.NeroDrive) nDrives.Item(0);

            evOnDoneCDInfo = new _INeroDriveEvents_OnDoneCDInfoEventHandler(nDrive_OnDoneCDInfo);
            nDrive.OnDoneCDInfo += evOnDoneCDInfo;
            nDrive.CDInfo(NEROLib.NERO_CDINFO_FLAGS.NERO_READ_CD_TEXT);
        }

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if (components != null) 
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

		#region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
            this.button1 = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(28, 34);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(232, 24);
            this.button1.TabIndex = 0;
            this.button1.Text = "Import everything && write one extra file";
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // 
            // frmTestNero
            // 
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
            this.ClientSize = new System.Drawing.Size(272, 151);
            this.Controls.Add(this.button1);
            this.Name = "frmTestNero";
            this.Text = "Testing the Nero API";
            this.ResumeLayout(false);

        }
		#endregion

		/// <summary>
		/// The main entry point for the application.
		/// </summary>
		[STAThread]
		static void Main() 
		{
			Application.Run(new frmTestNero());
		}

        private void nDrive_OnDoneCDInfo(INeroCDInfo pCDInfo)
        {
            NEROLib.NeroCDInfo nCDInfo = (NeroCDInfo)pCDInfo; 
            nDrive.OnDoneImport += new _INeroDriveEvents_OnDoneImportEventHandler(nDrive_OnDoneImport);

            evOnDoneImport = new _INeroDriveEvents_OnDoneImportEventHandler(nDrive_OnDoneImport);            
            for (int i = 0; i < nCDInfo.Tracks.Count; i++)
            {
                nDrive.OnDoneImport += evOnDoneImport;
                nDrive.ImportIsoTrack(i, NEROLib.NERO_IMPORT_ISO_TRACK_FLAGS.NERO_IMPORT_ISO_ONLY);
            }

        }

        private void nDrive_OnDoneImport(ref bool Ok, ref NeroFolder Folder, ref NeroCDStamp CDStamp)
        {
            nDrive.OnDoneImport -= evOnDoneImport;
            nRootFolder.Folders.Add(Folder);
        }

        private void button1_Click(object sender, System.EventArgs e)
        {
            nFile.Name = "europafm.m3u";
            nFile.SourceFilePath = @"C:\europafm.m3u";
            nRootFolder.Files.Add(nFile);

            nISOTrack.RootFolder = nRootFolder;
            nISOTrack.BurnOptions =        NEROLib.NERO_BURN_OPTIONS.NERO_BURN_OPTION_CREATE_ISO_FS;
            try
            {
                nDrive.BurnIsoAudioCD("", "TEST", false, nISOTrack, null, null,                    NEROLib.NERO_BURN_FLAGS.NERO_BURN_FLAG_WRITE,
              4,
                 NEROLib.NERO_MEDIA_TYPE.NERO_MEDIA_CDRW);
            }
            catch (COMException ex)
            {
                MessageBox.Show(this, ex.Message);
            }
        }
    }
}

Sorry for the indenting, but the forum width is not enough, it seems. Can anyone comment on it? I don’t know what else to add in order for Nero not to close the session. By the way, media type is set to CD-RW because I’m using a CD-RW for testing purposes.

Thank you so much! Have a nice day!

Andrei Ismail


#17

Hi iandrei

Well, I’m not a C# programmer, maybe there are others that can comment your code much better.

From what I see, in nDrive_OnDoneCDInfo you commonly do not have to import all previous tracks. If you always imported the last track, this would be sufficient. Cosider what happens if you already burned 2 tracks with your example and are just about to burn a third one. Track 0 holds the original files [a,b,c], track 1 holds some more original files [d,e] plus the imported files [->a,->b,->c]. Now you want to add some files [f,g] and import track 0 and 1 to burn track 2 and get [->a,->b,->c] (from track 0), [->a,->b,->c,->d,->e] (from track 1) and finally [f,g]. So the set of files for track 2 would be [->a,->a,->b,->b,->c,->c,->d,->e,f,g], what is most likely not intended.

A comment about alteration of the imported ISO tree(s): You attempt seems to be ok for me, but beware that - until otherwise stated by the NeroAPI - you must explicitely delete (by NeroFreeIsoItem) each imported item that you remove from the final ISO tree, will say, that will not be included in NeroFreeIsoItemTree(…) since it is not chained to the ISO item tree.

About “not closing the session”: You must close the session, not the whole disk. So you should add the NERO_BURN_FLAGS “NERO_BURN_FLAG_CLOSE_SESSION” to BurnIsoAudioCD, I belive like this (a c++'ers attempt):

nDrive.BurnIsoAudioCD(
  "",
  "TEST",
  false,
  nISOTrack,
  null,
  null,
  NEROLib.NERO_BURN_FLAGS.NERO_BURN_FLAG_WRITE + NEROLib.NERO_BURN_FLAGS.NERO_BURN_FLAG_CLOSE_SESSION,
  4,
  NEROLib.NERO_MEDIA_TYPE.NERO_MEDIA_CDRW);

Hope this helps.


#18

(This is off the topic of the thread… [not that it’s not actually very interesting to know])


#19

You’re certainly right, I’m very sorry! Suggest that we switch to another thread and I will not reply anymore to this one.